mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-27 23:56:18 +00:00
Optimize excalidraw code (#7942)
* optimize code * optimize code * optimize code * optimize code --------- Co-authored-by: 小强 <shuntian@Mac.lan>
This commit is contained in:
@@ -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);
|
@@ -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;
|
@@ -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
|
||||
|
||||
|
32
frontend/src/pages/excalidraw-editor/context.js
Normal file
32
frontend/src/pages/excalidraw-editor/context.js
Normal file
@@ -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;
|
92
frontend/src/pages/excalidraw-editor/editor/index.js
Normal file
92
frontend/src/pages/excalidraw-editor/editor/index.js
Normal file
@@ -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 (
|
||||
<div className='excali-container'>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='excali-container' style={{ height: '100%', width: '100%' }}>
|
||||
<Excalidraw
|
||||
initialData={sceneContent}
|
||||
excalidrawAPI={(api) => setExcalidrawAPI(api)}
|
||||
onChange={handleChange}
|
||||
UIOptions={UIOptions}
|
||||
langCode={langList[window.app.config.lang] || 'en'}
|
||||
>
|
||||
<MainMenu>
|
||||
<MainMenu.DefaultItems.Export />
|
||||
<MainMenu.DefaultItems.SaveAsImage />
|
||||
<MainMenu.DefaultItems.Help />
|
||||
<MainMenu.DefaultItems.ClearCanvas />
|
||||
<MainMenu.DefaultItems.ToggleTheme />
|
||||
<MainMenu.DefaultItems.ChangeCanvasBackground />
|
||||
</MainMenu>
|
||||
</Excalidraw>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleEditor;
|
@@ -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 */
|
@@ -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 (
|
||||
<div className="file-view-content flex-1 p-0 border-0">
|
||||
<SimpleEditor
|
||||
isFetching={isFetching}
|
||||
sceneContent={fileContent}
|
||||
onSaveContent={onSaveContent}
|
||||
onSaveContent={saveSceneContent}
|
||||
onChangeContent={onChangeContent}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -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 (
|
||||
<div className='excali-container'>
|
||||
<CodeMirrorLoading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='excali-container' style={{ height: '100%', width: '100%' }}>
|
||||
<Excalidraw
|
||||
initialData={sceneContent}
|
||||
excalidrawAPI={(api) => setExcalidrawAPI(api)}
|
||||
onChange={handleChange}
|
||||
UIOptions={UIOptions}
|
||||
langCode={langList[window.app.config.lang] || 'en'}
|
||||
>
|
||||
<MainMenu>
|
||||
<MainMenu.DefaultItems.Export />
|
||||
<MainMenu.DefaultItems.SaveAsImage />
|
||||
<MainMenu.DefaultItems.Help />
|
||||
<MainMenu.DefaultItems.ClearCanvas />
|
||||
<MainMenu.DefaultItems.ToggleTheme />
|
||||
<MainMenu.DefaultItems.ChangeCanvasBackground />
|
||||
</MainMenu>
|
||||
</Excalidraw>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleEditor;
|
@@ -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;
|
||||
};
|
Reference in New Issue
Block a user