diff --git a/frontend/src/pages/excalidraw-editor/editor-api.js b/frontend/src/pages/excalidraw-editor/api/editor-api.js
similarity index 88%
rename from frontend/src/pages/excalidraw-editor/editor-api.js
rename to frontend/src/pages/excalidraw-editor/api/editor-api.js
index fb92916139..c17e0239e4 100644
--- a/frontend/src/pages/excalidraw-editor/editor-api.js
+++ b/frontend/src/pages/excalidraw-editor/api/editor-api.js
@@ -1,5 +1,5 @@
-import { seafileAPI } from '../../utils/seafile-api';
-import { Utils } from '../../utils/utils';
+import { seafileAPI } from '../../../utils/seafile-api';
+import { Utils } from '../../../utils/utils';
const { repoID, filePath, fileName } = window.app.pageOptions;
let dirPath = Utils.getDirName(filePath);
diff --git a/frontend/src/pages/excalidraw-editor/collab/exdraw-server-api.js b/frontend/src/pages/excalidraw-editor/api/index.js
similarity index 91%
rename from frontend/src/pages/excalidraw-editor/collab/exdraw-server-api.js
rename to frontend/src/pages/excalidraw-editor/api/index.js
index d01269a55e..b1633bd6f5 100644
--- a/frontend/src/pages/excalidraw-editor/collab/exdraw-server-api.js
+++ b/frontend/src/pages/excalidraw-editor/api/index.js
@@ -1,6 +1,6 @@
import axios from 'axios';
-class ExdrawServerApi {
+class ExcalidrawServerApi {
constructor(options) {
this.server = options.exdrawServer;
@@ -25,4 +25,4 @@ class ExdrawServerApi {
}
}
-export default ExdrawServerApi;
+export default ExcalidrawServerApi;
diff --git a/frontend/src/pages/excalidraw-editor/constants.js b/frontend/src/pages/excalidraw-editor/constants.js
index 2aab0e6017..2e3b506e35 100644
--- a/frontend/src/pages/excalidraw-editor/constants.js
+++ b/frontend/src/pages/excalidraw-editor/constants.js
@@ -17,3 +17,6 @@ export const langList = {
export const STORAGE_KEYS = {
IDB_LIBRARY: 'excalidraw-library',
};
+
+export const DELETED_ELEMENT_TIMEOUT = 24 * 60 * 60 * 1000; // 1 day
+
diff --git a/frontend/src/pages/excalidraw-editor/context.js b/frontend/src/pages/excalidraw-editor/context.js
new file mode 100644
index 0000000000..86fedb1e21
--- /dev/null
+++ b/frontend/src/pages/excalidraw-editor/context.js
@@ -0,0 +1,32 @@
+import ExcalidrawServerApi from './api';
+import editorApi from './api/editor-api';
+
+const { docUuid, excalidrawServerUrl } = window.app.pageOptions;
+
+class Context {
+ constructor() {
+ this.docUuid = '';
+ this.exdrawServer = '';
+ }
+
+ initSettings = async () => {
+ this.docUuid = docUuid;
+ this.exdrawServer = excalidrawServerUrl;
+ const resResult = await editorApi.getExdrawToken();
+ const accessToken = resResult;
+ this.exdrawApi = new ExcalidrawServerApi({ exdrawUuid: docUuid, exdrawServer: excalidrawServerUrl, accessToken });
+ };
+
+ getSceneContent = () => {
+ return this.exdrawApi.getSceneContent();
+ };
+
+ saveSceneContent = (content) => {
+ return this.exdrawApi.saveSceneContent(content);
+ };
+
+}
+
+const context = new Context();
+
+export default context;
diff --git a/frontend/src/pages/excalidraw-editor/editor/index.js b/frontend/src/pages/excalidraw-editor/editor/index.js
new file mode 100644
index 0000000000..576596deb8
--- /dev/null
+++ b/frontend/src/pages/excalidraw-editor/editor/index.js
@@ -0,0 +1,92 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Excalidraw, MainMenu, useHandleLibrary } from '@excalidraw/excalidraw';
+import isHotkey from 'is-hotkey';
+import Loading from '../../../components/loading';
+import { langList } from '../constants';
+import { LibraryIndexedDBAdapter } from './library-adapter';
+
+import '@excalidraw/excalidraw/index.css';
+
+const { docUuid } = window.app.pageOptions;
+window.name = `${docUuid}`;
+
+const UIOptions = {
+ canvasActions: {
+ saveToActiveFile: false,
+ LoadScene: false
+ },
+ tools: { image: false },
+};
+
+const hasChanged = (elements, oldElements) => {
+ if (elements.length !== oldElements.length) return true;
+
+ return elements.some((element, index) => {
+ return element.version !== oldElements[index]?.version;
+ });
+};
+
+const SimpleEditor = ({ sceneContent = null, onChangeContent, onSaveContent, isFetching }) => {
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+ const prevElementsRef = useRef();
+
+ useHandleLibrary({ excalidrawAPI, adapter: LibraryIndexedDBAdapter });
+
+ const handleChange = () => {
+ if (!prevElementsRef.current) {
+ prevElementsRef.current = sceneContent?.elements || [];
+ }
+
+ const elements = excalidrawAPI.getSceneElements();
+ if (hasChanged(elements, prevElementsRef.current)) {
+ onChangeContent(elements);
+ prevElementsRef.current = elements;
+ }
+
+ };
+
+ useEffect(() => {
+ const handleHotkeySave = (event) => {
+ if (isHotkey('mod+s', event)) {
+ event.preventDefault();
+ onSaveContent(excalidrawAPI.getSceneElements());
+ }
+ };
+
+ document.addEventListener('keydown', handleHotkeySave, true);
+ return () => {
+ document.removeEventListener('keydown', handleHotkeySave, true);
+ };
+ }, [excalidrawAPI, onSaveContent]);
+
+ if (isFetching) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ setExcalidrawAPI(api)}
+ onChange={handleChange}
+ UIOptions={UIOptions}
+ langCode={langList[window.app.config.lang] || 'en'}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SimpleEditor;
diff --git a/frontend/src/pages/excalidraw-editor/library-adapter.js b/frontend/src/pages/excalidraw-editor/editor/library-adapter.js
similarity index 85%
rename from frontend/src/pages/excalidraw-editor/library-adapter.js
rename to frontend/src/pages/excalidraw-editor/editor/library-adapter.js
index a915b17e78..31baa7d69e 100644
--- a/frontend/src/pages/excalidraw-editor/library-adapter.js
+++ b/frontend/src/pages/excalidraw-editor/editor/library-adapter.js
@@ -1,9 +1,5 @@
-import {
- createStore,
- get,
- set
-} from 'idb-keyval';
-import { STORAGE_KEYS } from './constants';
+import { createStore, get, set } from 'idb-keyval';
+import { STORAGE_KEYS } from '../constants';
export class LibraryIndexedDBAdapter {
/** IndexedDB database and store name */
diff --git a/frontend/src/pages/excalidraw-editor/index.js b/frontend/src/pages/excalidraw-editor/index.js
index 7a6e0cd267..0be3e4c7af 100644
--- a/frontend/src/pages/excalidraw-editor/index.js
+++ b/frontend/src/pages/excalidraw-editor/index.js
@@ -1,117 +1,85 @@
import React, { useCallback, useState, useEffect, useRef } from 'react';
-import SimpleEditor from './simple-editor';
-import editorApi from './editor-api';
-import isHotkey from 'is-hotkey';
+import SimpleEditor from './editor';
import { gettext } from '../../utils/constants';
import toaster from '../../components/toast';
import { SAVE_INTERVAL_TIME } from './constants';
-import { Utils } from '../../utils/utils';
-import ExdrawServerApi from './collab/exdraw-server-api';
+import { updateAppIcon } from './utils/common-utils';
+import context from './context';
import './index.css';
-const { docUuid, excalidrawServerUrl } = window.app.pageOptions;
-window.name = `${docUuid}`;
-
const ExcaliEditor = () => {
- const [fileContent, setFileContent] = useState(null);
const editorRef = useRef(null);
const isChangedRef = useRef(false);
const [isFetching, setIsFetching] = useState(true);
- const exdrawServerConfigRef = useRef({
- exdrawServer: '',
- exdrawUuid: '',
- accessToken: ''
- });
-
- useEffect(() => {
- editorApi.getExdrawToken().then(res => {
- exdrawServerConfigRef.current = {
- exdrawServer: excalidrawServerUrl,
- exdrawUuid: docUuid,
- accessToken: res
- };
- const exdrawServerApi = new ExdrawServerApi(exdrawServerConfigRef.current);
- exdrawServerApi.getSceneContent().then(res => {
- setFileContent(res.data);
- setIsFetching(false);
- });
- });
- onSetFavicon();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- const saveSceneContent = useCallback(async () => {
- if (isChangedRef.current) {
- try {
- const exdrawServerApi = new ExdrawServerApi(exdrawServerConfigRef.current);
- await exdrawServerApi.saveSceneContent(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, true);
- return () => {
- document.removeEventListener('keydown', handleHotkeySave, true);
- };
- }, [saveSceneContent]);
+ const [fileContent, setFileContent] = useState(null);
+ // saved file interval
useEffect(() => {
const saveInterval = setInterval(() => {
if (isChangedRef.current) {
- editorApi.saveContent(JSON.stringify(editorRef.current)).then(res => {
+ context.saveSceneContent(JSON.stringify(editorRef.current)).then(res => {
isChangedRef.current = false;
});
}
}, SAVE_INTERVAL_TIME);
+ return () => {
+ clearInterval(saveInterval);
+ };
+ }, []);
+
+ // tips before refresh current page
+ useEffect(() => {
const handleBeforeUnload = (event) => {
if (isChangedRef.current) {
event.preventDefault();
event.returnValue = '';
}
};
-
window.addEventListener('beforeunload', handleBeforeUnload);
-
return () => {
- clearInterval(saveInterval);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
- }, [saveSceneContent]);
+ }, []);
- const onSaveContent = useCallback(() => {
- saveSceneContent();
- }, [saveSceneContent]);
+ useEffect(() => {
+ updateAppIcon();
+ }, []);
+
+ useEffect(() => {
+ async function loadFileContent() {
+ await context.initSettings();
+ context.getSceneContent().then(res => {
+ setFileContent(res.data);
+ setIsFetching(false);
+ });
+ }
+ loadFileContent();
+ }, []);
+
+ const saveSceneContent = useCallback(async () => {
+ if (isChangedRef.current) {
+ context.saveSceneContent(JSON.stringify(editorRef.current)).then(res => {
+ isChangedRef.current = false;
+ toaster.success(gettext('Successfully saved'), { duration: 2 });
+ }).catch(error => {
+ toaster.danger(gettext('Failed to save'), { duration: 2 });
+ });
+ }
+ }, []);
const onChangeContent = useCallback((elements) => {
editorRef.current = { elements };
isChangedRef.current = true;
}, []);
- const onSetFavicon = useCallback(() => {
- const { docName } = window.app.pageOptions;
- const fileIcon = Utils.getFileIconUrl(docName);
- document.getElementById('favicon').href = fileIcon;
- }, []);
-
return (
diff --git a/frontend/src/pages/excalidraw-editor/simple-editor.js b/frontend/src/pages/excalidraw-editor/simple-editor.js
deleted file mode 100644
index 4e109089dc..0000000000
--- a/frontend/src/pages/excalidraw-editor/simple-editor.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { useEffect, useRef, useState } from 'react';
-import { Excalidraw, MainMenu, useHandleLibrary } from '@excalidraw/excalidraw';
-import isHotkey from 'is-hotkey';
-import CodeMirrorLoading from '../../components/code-mirror-loading';
-import { langList } from './constants';
-import { LibraryIndexedDBAdapter } from './library-adapter';
-
-import '@excalidraw/excalidraw/index.css';
-
-const SimpleEditor = ({
- sceneContent = null,
- onChangeContent,
- onSaveContent,
- isFetching
-}) => {
- const [excalidrawAPI, setExcalidrawAPI] = useState(null);
- const prevElementsRef = useRef([]);
- const UIOptions = {
- canvasActions: {
- saveToActiveFile: false,
- LoadScene: false
- },
- tools: { image: false },
- };
-
- useHandleLibrary({
- excalidrawAPI,
- adapter: LibraryIndexedDBAdapter
- });
-
- const handleChange = () => {
- const elements = excalidrawAPI.getSceneElements();
- if (hasChanged(elements, prevElementsRef.current)) {
- onChangeContent(elements);
- }
- prevElementsRef.current = elements;
- };
-
- useEffect(() => {
- const handleHotkeySave = (event) => {
- if (isHotkey('mod+s', event)) {
- event.preventDefault();
- onSaveContent(excalidrawAPI.getSceneElements());
- }
- };
-
- document.addEventListener('keydown', handleHotkeySave, true);
- return () => {
- document.removeEventListener('keydown', handleHotkeySave, true);
- };
- }, [excalidrawAPI, onSaveContent]);
-
- const hasChanged = (prev, current) => {
- if (prev.length !== current.length) return true;
-
- return current.some((element, index) => {
- return element.version !== prev[index]?.version;
- });
- };
-
- if (isFetching) {
- return (
-
-
-
- );
- }
-
- return (
- <>
-
- setExcalidrawAPI(api)}
- onChange={handleChange}
- UIOptions={UIOptions}
- langCode={langList[window.app.config.lang] || 'en'}
- >
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export default SimpleEditor;
diff --git a/frontend/src/pages/excalidraw-editor/utils/common-utils.js b/frontend/src/pages/excalidraw-editor/utils/common-utils.js
new file mode 100644
index 0000000000..3d190a01a4
--- /dev/null
+++ b/frontend/src/pages/excalidraw-editor/utils/common-utils.js
@@ -0,0 +1,7 @@
+import { Utils } from '../../../utils/utils';
+
+export const updateAppIcon = () => {
+ const { docName } = window.app.pageOptions;
+ const fileIcon = Utils.getFileIconUrl(docName);
+ document.getElementById('favicon').href = fileIcon;
+};