Skip to content

Commit

Permalink
Add tldraw file editor (#7273)
Browse files Browse the repository at this point in the history
* add tldr file type

* add tldraw editor render

* optimize code

* add save mode

* update version

* update dependence

* update dependence

* optimize code

* optimize code

---------

Co-authored-by: 杨顺强 <[email protected]>
  • Loading branch information
stephensu66 and shuntian authored Jan 4, 2025
1 parent 0504d5b commit 155d303
Show file tree
Hide file tree
Showing 21 changed files with 7,691 additions and 6,289 deletions.
1 change: 1 addition & 0 deletions frontend/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const excludedChunkNames = [
'sharedFileViewMarkdown',
'markdownEditor',
'plainMarkdownEditor',
'tldrawEditor',
];

// This is the production and development configuration.
Expand Down
1 change: 1 addition & 0 deletions frontend/config/webpack.entry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const paths = require('./paths');

const entryFiles = {
tldrawEditor: '/tldrawEditor.js',
markdownEditor: '/index.js',
plainMarkdownEditor: '/pages/plain-markdown-editor/index.js',
TCAccept: '/tc-accept.js',
Expand Down
13,742 changes: 7,463 additions & 6,279 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@seafile/seafile-calendar": "0.0.28",
"@seafile/seafile-editor": "1.0.133",
"@seafile/sf-metadata-ui-component": "^0.0.62",
"@seafile/stldraw-editor": "0.1.5",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/codemirror-themes": "^4.23.5",
"@uiw/react-codemirror": "^4.19.4",
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/dirent-grid-view/dirent-grid-view.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { siteRoot, username, enableSeadoc, thumbnailDefaultSize, thumbnailSizeForOriginal, gettext, fileServerRoot } from '../../utils/constants';
import { siteRoot, username, enableSeadoc, thumbnailDefaultSize, thumbnailSizeForOriginal, gettext, fileServerRoot, enableTldraw } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
Expand Down Expand Up @@ -409,6 +409,9 @@ class DirentGridView extends React.Component {
case 'New Word File':
this.onCreateFileToggle('.docx');
break;
case 'New Whiteboard File':
this.onCreateFileToggle('.draw');
break;
case 'New SeaDoc File':
this.onCreateFileToggle('.sdoc');
break;
Expand Down Expand Up @@ -724,7 +727,8 @@ class DirentGridView extends React.Component {
NEW_EXCEL_FILE,
NEW_POWERPOINT_FILE,
NEW_WORD_FILE,
NEW_SEADOC_FILE
NEW_SEADOC_FILE,
NEW_TLDRAW_FILE
} = TextTranslation;

let direntsContainerMenuList = [
Expand All @@ -743,6 +747,10 @@ class DirentGridView extends React.Component {
NEW_WORD_FILE
);

if (enableTldraw) {
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
}

if (selectedDirentList.length === 0) {
if (!hasCustomPermission('create')) return;
this.handleContextClick(event, DIRENT_GRID_CONTAINER_MENU_ID, direntsContainerMenuList);
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/components/dirent-list-view/dirent-list-view.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, thumbnailDefaultSize, fileServerRoot } from '../../utils/constants';
import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, thumbnailDefaultSize, fileServerRoot, enableTldraw } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import TextTranslation from '../../utils/text-translation';
import URLDecorator from '../../utils/url-decorator';
Expand Down Expand Up @@ -418,7 +418,8 @@ class DirentListView extends React.Component {
NEW_EXCEL_FILE,
NEW_POWERPOINT_FILE,
NEW_WORD_FILE,
NEW_SEADOC_FILE
NEW_SEADOC_FILE,
NEW_TLDRAW_FILE
} = TextTranslation;

const direntsContainerMenuList = [
Expand All @@ -432,9 +433,13 @@ class DirentListView extends React.Component {
NEW_MARKDOWN_FILE,
NEW_EXCEL_FILE,
NEW_POWERPOINT_FILE,
NEW_WORD_FILE
NEW_WORD_FILE,
);

if (enableTldraw) {
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
}

if (this.props.selectedDirentList.length === 0) {
let id = 'dirent-container-menu';

Expand Down Expand Up @@ -509,6 +514,9 @@ class DirentListView extends React.Component {
case 'New Word File':
this.onCreateFileToggle('.docx');
break;
case 'New Whiteboard File':
this.onCreateFileToggle('.draw');
break;
case 'New SeaDoc File':
this.onCreateFileToggle('.sdoc');
break;
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/toolbar/dir-operation-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { enableSeadoc, gettext } from '../../utils/constants';
import { enableSeadoc, enableTldraw, gettext } from '../../utils/constants';
import ModalPortal from '../modal-portal';
import CreateFolder from '../../components/dialog/create-folder-dialog';
import CreateFile from '../../components/dialog/create-file-dialog';
Expand Down Expand Up @@ -108,6 +108,13 @@ class DirOperationToolbar extends React.Component {
});
};

onCreateTldrawToggle = () => {
this.setState({
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
fileType: '.draw'
});
};

onCreateSeaDocToggle = () => {
this.setState({
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
Expand Down Expand Up @@ -246,8 +253,11 @@ class DirOperationToolbar extends React.Component {
{ 'text': gettext('New Markdown File'), 'onClick': this.onCreateMarkdownToggle },
{ 'text': gettext('New Excel File'), 'onClick': this.onCreateExcelToggle },
{ 'text': gettext('New PowerPoint File'), 'onClick': this.onCreatePPTToggle },
{ 'text': gettext('New Word File'), 'onClick': this.onCreateWordToggle }
{ 'text': gettext('New Word File'), 'onClick': this.onCreateWordToggle },
);
if (enableTldraw) {
newSubOpList.push({ 'text': gettext('New Whiteboard File'), 'onClick': this.onCreateTldrawToggle });
}
opList.push({
'icon': 'new',
'text': gettext('New'),
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/tldraw-editor/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SAVE_INTERVAL_TIME = 3 * 60 * 1000;
26 changes: 26 additions & 0 deletions frontend/src/pages/tldraw-editor/editor-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';

const { repoID, filePath, fileName } = window.app.pageOptions;
let dirPath = Utils.getDirName(filePath);

class EditorApi {

saveContent(content) {
return seafileAPI.getUpdateLink(repoID, dirPath).then((res) => {
const uploadLink = res.data;
return seafileAPI.updateFile(uploadLink, filePath, fileName, content);
});
}

getFileContent = () => {
return seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
const downLoadUrl = res.data;
return seafileAPI.getFileContent(downLoadUrl);
});
};
}

const editorApi = new EditorApi();

export default editorApi;
89 changes: 89 additions & 0 deletions frontend/src/pages/tldraw-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useCallback, useState, useEffect, useRef } from 'react';
import { SimpleEditor } from '@seafile/stldraw-editor';
import isHotkey from 'is-hotkey';
import editorApi from './editor-api';
import { gettext } from '../../utils/constants';
import toaster from '../../components/toast';
import { SAVE_INTERVAL_TIME } from './constants';

const TldrawEditor = () => {
const editorRef = useRef(null);
const isChangedRef = useRef(false);
const [fileContent, setFileContent] = useState({});
const [isFetching, setIsFetching] = useState(true);

useEffect(() => {
editorApi.getFileContent().then(res => {
setFileContent(res.data);
setIsFetching(false);
});
}, []);

const saveDocument = useCallback(async () => {
if (isChangedRef.current) {
try {
await editorApi.saveContent(JSON.stringify(editorRef.current));
isChangedRef.current = false;
toaster.success(gettext('Successfully saved'), { duration: 2 });
} catch {
toaster.danger(gettext('Failed to save'), { duration: 2 });
}
}
}, []);

useEffect(() => {
const handleHotkeySave = (event) => {
if (isHotkey('mod+s')(event)) {
event.preventDefault();
}
};

document.addEventListener('keydown', handleHotkeySave);
return () => {
document.removeEventListener('keydown', handleHotkeySave);
};
}, [saveDocument]);

useEffect(() => {
const saveInterval = setInterval(() => {
if (isChangedRef.current) {
editorApi.saveContent(JSON.stringify(editorRef.current)).then(res => {
isChangedRef.current = false;
});
}
}, SAVE_INTERVAL_TIME);

const handleBeforeUnload = (event) => {
if (isChangedRef.current) {
event.preventDefault();
event.returnValue = '';
}
};

window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
clearInterval(saveInterval);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [saveDocument]);

const onContentChanged = useCallback((docContent) => {
editorRef.current = docContent;
isChangedRef.current = true;
}, []);

const onSave = useCallback(() => {
saveDocument();
}, [saveDocument]);

return (
<SimpleEditor
isFetching={isFetching}
document={fileContent}
onContentChanged={onContentChanged}
onSave={onSave}
/>
);
};

export default TldrawEditor;
11 changes: 11 additions & 0 deletions frontend/src/tldrawEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import TldrawEditor from './pages/tldraw-editor';
import Loading from './components/loading';

ReactDOM.render(
<Suspense fallback={<Loading />}>
<TldrawEditor />
</Suspense>,
document.getElementById('wrapper')
);
1 change: 1 addition & 0 deletions frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const ocmRemoteServers = window.app.pageOptions.ocmRemoteServers;
export const enableOCMViaWebdav = window.app.pageOptions.enableOCMViaWebdav;
export const enableSSOToThirdpartWebsite = window.app.pageOptions.enableSSOToThirdpartWebsite;
export const enableSeadoc = window.app.pageOptions.enableSeadoc;
export const enableTldraw = window.app.pageOptions.enableTldraw;
export const enableMultipleOfficeSuite = window.app.pageOptions.enableMultipleOfficeSuite;

export const curNoteMsg = window.app.pageOptions.curNoteMsg;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/utils/text-translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const TextTranslation = {
key: 'New Word File',
value: gettext('New Word File')
},
NEW_TLDRAW_FILE: {
key: 'New Whiteboard File',
value: gettext('New Whiteboard File')
},
NEW_SEADOC_FILE: {
key: 'New SeaDoc File',
value: gettext('New SeaDoc File')
Expand Down
5 changes: 3 additions & 2 deletions seahub/base/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
ENABLE_SEAFILE_DOCS, LOGIN_BG_IMAGE_PATH, THUMBNAIL_DEFAULT_SIZE, \
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, THUMBNAIL_SIZE_FOR_GRID, \
FILE_SERVER_ROOT
FILE_SERVER_ROOT, ENABLE_TLDRAW

from seahub.organizations.models import OrgAdminSettings
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
Expand Down Expand Up @@ -173,7 +173,8 @@ def base(request):
'side_nav_footer_custom_html': SIDE_NAV_FOOTER_CUSTOM_HTML,
'about_dialog_custom_html': ABOUT_DIALOG_CUSTOM_HTML,
'enable_repo_auto_del': ENABLE_REPO_AUTO_DEL,
'enable_seadoc': ENABLE_SEADOC
'enable_seadoc': ENABLE_SEADOC,
'enable_tldraw': ENABLE_TLDRAW,
}

if request.user.is_staff:
Expand Down
1 change: 1 addition & 0 deletions seahub/base/templatetags/seahub_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def tsstr_day(value):
'docx': 'word.png',
'odt': 'word.png',
'fodt': 'word.png',
'tldr': 'word.png',

'ppt': 'ppt.png',
'pptx': 'ppt.png',
Expand Down
5 changes: 5 additions & 0 deletions seahub/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,11 @@ def genpassword():
SEADOC_SERVER_URL = 'http://127.0.0.1:7070'
FILE_CONVERTER_SERVER_URL = 'http://127.0.0.1:8888'

##########################
# Settings for tldraw #
##########################

ENABLE_TLDRAW = False

############################
# Settings for Seahub Priv #
Expand Down
1 change: 1 addition & 0 deletions seahub/templates/base_for_react.html
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
enableOnlyoffice: {% if enableOnlyoffice %} true {% else %} false {% endif %},
onlyofficeConverterExtensions: {% if onlyofficeConverterExtensions %} {{onlyofficeConverterExtensions|safe}} {% else %} null {% endif %},
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
enableTldraw: {% if enable_tldraw %} true {% else %} false {% endif %},
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
enableFileTags: {% if enable_file_tags %} true {% else %} false {% endif %},
Expand Down
19 changes: 19 additions & 0 deletions seahub/templates/tldraw_file_view_react.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends 'file_view_react.html' %}
{% load render_bundle from webpack_loader %}


{% block extra_style %}
{% render_bundle 'tldrawEditor' 'css' %}
{% endblock %}

{% block extra_data %}
docPath: '{{ path|escapejs }}',
docName: '{{ filename|escapejs }}',
docUuid: '{{ file_uuid }}',
lang: '{{ lang }}',
rawPath: '{{ raw_path|escapejs }}',
{% endblock %}

{% block render_bundle %}
{% render_bundle 'tldrawEditor' 'js' %}
{% endblock %}
1 change: 1 addition & 0 deletions seahub/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def is_db_sqlite3():
#'3D': ('stl', 'obj'),
XMIND: ('xmind',),
SEADOC: ('sdoc',),
TLDRAW: ('draw',),
}

def get_non_sdoc_file_exts():
Expand Down
1 change: 1 addition & 0 deletions seahub/utils/file_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
SPREADSHEET = 'SpreadSheet'
XMIND = 'XMind'
SEADOC = 'SDoc'
TLDRAW = 'Tldraw'


MARKDOWN_SUPPORT_CONVERT_TYPES = ['sdoc']
Expand Down
Loading

0 comments on commit 155d303

Please sign in to comment.