diff --git a/frontend/config/webpack.entry.js b/frontend/config/webpack.entry.js index 55ff95fbbb..c0239980df 100644 --- a/frontend/config/webpack.entry.js +++ b/frontend/config/webpack.entry.js @@ -25,6 +25,7 @@ const entryFiles = { sharedFileViewAudio: '/shared-file-view-audio.js', sharedFileViewDocument: '/shared-file-view-document.js', sharedFileViewSpreadsheet: '/shared-file-view-spreadsheet.js', + sharedFileViewExdraw: '/shared-file-view-exdraw.js', sharedFileViewSdoc: '/shared-file-view-sdoc.js', sharedFileViewUnknown: '/shared-file-view-unknown.js', historyTrashFileView: '/history-trash-file-view.js', diff --git a/frontend/src/pages/excalidraw-viewer/constants.js b/frontend/src/pages/excalidraw-viewer/constants.js new file mode 100644 index 0000000000..8407f1f02b --- /dev/null +++ b/frontend/src/pages/excalidraw-viewer/constants.js @@ -0,0 +1,15 @@ +export const SAVE_INTERVAL_TIME = 3 * 60 * 1000; + +export const langList = { + 'zh-cn': 'zh-CN', + 'en': 'en', + 'zh-tw': 'zh-TW', + 'ru': 'ru-RU', + 'it': 'it-IT', + 'fr': 'fr-FR', + 'es-ms': 'en', + 'es-ar': 'en', + 'es': 'es-ES', + 'de': 'de-DE', + 'cs': 'cs-CZ', +}; diff --git a/frontend/src/pages/excalidraw-viewer/editor-api.js b/frontend/src/pages/excalidraw-viewer/editor-api.js new file mode 100644 index 0000000000..cf433f5f60 --- /dev/null +++ b/frontend/src/pages/excalidraw-viewer/editor-api.js @@ -0,0 +1,26 @@ +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; + +const { repoID, filePath, fileName } = window.shared.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; diff --git a/frontend/src/pages/excalidraw-viewer/index.css b/frontend/src/pages/excalidraw-viewer/index.css new file mode 100644 index 0000000000..c91bf42f0a --- /dev/null +++ b/frontend/src/pages/excalidraw-viewer/index.css @@ -0,0 +1,8 @@ +.dropdown-menu { + display: block !important; +} + +.excalidraw .dropdown-menu { + --bs-dropdown-padding-x: auto; + border: unset; +} diff --git a/frontend/src/pages/excalidraw-viewer/index.js b/frontend/src/pages/excalidraw-viewer/index.js new file mode 100644 index 0000000000..3aafbc7ed3 --- /dev/null +++ b/frontend/src/pages/excalidraw-viewer/index.js @@ -0,0 +1,31 @@ +import React, { useState, useEffect } from 'react'; +import SimpleViewer from './simple-viewer'; +import editorApi from './editor-api'; + +import './index.css'; + +const ExcaliViewer = () => { + const [fileContent, setFileContent] = useState(null); + const [isFetching, setIsFetching] = useState(true); + + useEffect(() => { + editorApi.getFileContent().then(res => { + if (res.data?.appState?.collaborators && !Array.isArray(res.data.appState.collaborators)) { + // collaborators.forEach is not a function + res.data['appState']['collaborators'] = []; + } + setFileContent(res.data); + setIsFetching(false); + }); + }, []); + + + return ( + + ); +}; + +export default ExcaliViewer; diff --git a/frontend/src/pages/excalidraw-viewer/simple-viewer.js b/frontend/src/pages/excalidraw-viewer/simple-viewer.js new file mode 100644 index 0000000000..9362d74a2f --- /dev/null +++ b/frontend/src/pages/excalidraw-viewer/simple-viewer.js @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import { Excalidraw, MainMenu } from '@excalidraw/excalidraw'; +import CodeMirrorLoading from '../../components/code-mirror-loading'; +import { langList } from './constants'; + +import '@excalidraw/excalidraw/index.css'; + +const SimpleViewer = ({ sceneContent = null, isFetching }) => { + const [excalidrawAPI, setExcalidrawAPI] = useState(null); + const UIOptions = { + canvasActions: { + saveToActiveFile: false, + LoadScene: false + }, + tools: { image: false }, + }; + + if (isFetching) { + return ( +
+ +
+ ); + } + + return ( + <> +
+ setExcalidrawAPI(api)} + UIOptions={UIOptions} + langCode={langList[window.app.config.lang] || 'en'} + viewModeEnabled={true} + > + + + + + + + + + +
+ + ); +}; + +export default SimpleViewer; diff --git a/frontend/src/shared-file-view-exdraw.js b/frontend/src/shared-file-view-exdraw.js new file mode 100644 index 0000000000..a535372eee --- /dev/null +++ b/frontend/src/shared-file-view-exdraw.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { Utils } from './utils/utils'; +import ExcaliViewer from './pages/excalidraw-viewer'; + +const { siteRoot, avatarURL } = window.app.config; +const { username } = window.app.pageOptions; +const { + repoID, + canDownload, + canEdit, + fileName, + assetsUrl, + sharedFileDownloadURL, +} = window.shared.pageOptions; + +// share permission of this sdoc +const sharePermission = { 'can_edit': canEdit, 'can_download': canDownload, 'can_upload': false }; +const sharePermissionStr = Utils.getShareLinkPermissionStr(sharePermission); +const sharePermissionText = Utils.getShareLinkPermissionObject(sharePermissionStr).text; + +window.seafile = { + repoID, + username, + avatarURL, + siteRoot, + sharePermissionText: sharePermissionText, + downloadURL: sharedFileDownloadURL, + assetsUrl, +}; + +(function () { + const fileIcon = Utils.getFileIconUrl(fileName); + document.getElementById('favicon').href = fileIcon; +})(); + + +const root = createRoot(document.getElementById('wrapper')); +root.render(); diff --git a/seahub/templates/shared_file_view_react.html b/seahub/templates/shared_file_view_react.html index e094d80313..d74be76ffd 100644 --- a/seahub/templates/shared_file_view_react.html +++ b/seahub/templates/shared_file_view_react.html @@ -5,18 +5,18 @@ {% block sub_title %}{{file_name}} - {% endblock %} {% block extra_ogp_tags %} - + - - - - + + + + {% endblock %} {% block extra_style %} {% if filetype == 'Markdown' %} - - {% render_bundle 'sharedFileViewMarkdown' 'css' %} + +{% render_bundle 'sharedFileViewMarkdown' 'css' %} {% elif filetype == 'Text' %} {% render_bundle 'sharedFileViewText' 'css' %} {% elif filetype == 'Image' %} @@ -28,20 +28,22 @@ {% elif filetype == 'Audio' %} {% render_bundle 'sharedFileViewAudio' 'css' %} {% elif filetype == 'PDF' %} - - - - {% render_bundle 'sharedFileViewPDF' 'css' %} + + + +{% render_bundle 'sharedFileViewPDF' 'css' %} {% elif filetype == 'Document' %} - - {% render_bundle 'sharedFileViewDocument' 'css' %} + +{% render_bundle 'sharedFileViewDocument' 'css' %} {% elif filetype == 'SpreadSheet' %} {% render_bundle 'sharedFileViewSpreadsheet' 'css' %} {% elif filetype == 'SDoc' %} - - {% render_bundle 'sharedFileViewSdoc' 'css' %} + +{% render_bundle 'sharedFileViewSdoc' 'css' %} {% elif filetype == 'Unknown' %} {% render_bundle 'sharedFileViewUnknown' 'css' %} +{% elif filetype == 'Excalidraw' %} + {% render_bundle 'sharedFileViewExdraw' 'css' %} {% endif %} {% if not permissions.can_download %}