1
0
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:
杨顺强
2025-06-17 18:27:35 +08:00
committed by GitHub
parent 2b9fe4acc8
commit 2e36c37e43
9 changed files with 180 additions and 175 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View 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;

View 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;

View File

@@ -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 */

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
};