From 27bf8792dac6a4de805b9685d53c7115d5e862e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= <37972689+YangGuoXuan-0503@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:15:56 +0800 Subject: [PATCH] fix: metadata status toggle (#6530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: metadata status toggle * feat: optimize code --------- Co-authored-by: 杨国璇 --- .../src/components/cur-dir-path/dir-tool.js | 6 +- frontend/src/components/cur-dir-path/index.js | 4 +- .../dir-view-mode/dir-column-file.js | 6 +- .../dir-view-mode/dir-column-nav.js | 7 +- .../dir-view-mode/dir-column-view.js | 4 +- .../src/components/dir-view-mode/dir-views.js | 16 +- .../dirent-details/dir-details.js | 6 +- .../dirent-details/file-details.js | 6 +- frontend/src/metadata/hooks/index.js | 2 +- .../src/metadata/hooks/metadata-status.js | 50 ---- frontend/src/metadata/hooks/metadata.js | 179 ++++++++++++ .../src/metadata/metadata-tree-view/index.js | 139 +++------ .../metadata-tree-view/name-dialog/index.js | 4 +- .../metadata-tree-view/view-item/index.js | 8 +- .../components/view-toolbar/index.js | 6 +- .../lib-content-view/lib-content-container.js | 273 +++++++++--------- .../lib-content-view/lib-content-view.js | 58 ++-- 17 files changed, 426 insertions(+), 348 deletions(-) delete mode 100644 frontend/src/metadata/hooks/metadata-status.js create mode 100644 frontend/src/metadata/hooks/metadata.js diff --git a/frontend/src/components/cur-dir-path/dir-tool.js b/frontend/src/components/cur-dir-path/dir-tool.js index 298819fe4a..fd45d0713c 100644 --- a/frontend/src/components/cur-dir-path/dir-tool.js +++ b/frontend/src/components/cur-dir-path/dir-tool.js @@ -23,7 +23,7 @@ const propTypes = { sortBy: PropTypes.string, sortOrder: PropTypes.string, sortItems: PropTypes.func, - metadataViewId: PropTypes.string, + viewId: PropTypes.string, }; class DirTool extends React.Component { @@ -100,7 +100,7 @@ class DirTool extends React.Component { render() { const menuItems = this.getMenu(); const { isDropdownMenuOpen } = this.state; - const { repoID, currentMode, currentPath, sortBy, sortOrder, metadataViewId } = this.props; + const { repoID, currentMode, currentPath, sortBy, sortOrder, viewId } = this.props; const propertiesText = TextTranslation.PROPERTIES.value; const isFileExtended = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/'); @@ -114,7 +114,7 @@ class DirTool extends React.Component { if (isFileExtended) { return (
- +
); } diff --git a/frontend/src/components/cur-dir-path/index.js b/frontend/src/components/cur-dir-path/index.js index 4f59e3787f..22df8e4b54 100644 --- a/frontend/src/components/cur-dir-path/index.js +++ b/frontend/src/components/cur-dir-path/index.js @@ -38,7 +38,7 @@ const propTypes = { filePermission: PropTypes.string, repoTags: PropTypes.array.isRequired, onFileTagChanged: PropTypes.func.isRequired, - metadataViewId: PropTypes.string, + viewId: PropTypes.string, onItemMove: PropTypes.func.isRequired, }; @@ -101,7 +101,7 @@ class CurDirPath extends React.Component { sortBy={this.props.sortBy} sortOrder={this.props.sortOrder} sortItems={this.props.sortItems} - metadataViewId={this.props.metadataViewId} + viewId={this.props.viewId} />} {!isDesktop && this.props.direntList.length > 0 && } diff --git a/frontend/src/components/dir-view-mode/dir-column-file.js b/frontend/src/components/dir-view-mode/dir-column-file.js index 54ec0a5170..d63832f0a8 100644 --- a/frontend/src/components/dir-view-mode/dir-column-file.js +++ b/frontend/src/components/dir-view-mode/dir-column-file.js @@ -13,7 +13,7 @@ const propTypes = { isFileLoadedErr: PropTypes.bool.isRequired, filePermission: PropTypes.string, content: PropTypes.string, - metadataViewId: PropTypes.string, + viewId: PropTypes.string, lastModified: PropTypes.string, latestContributor: PropTypes.string, onLinkClick: PropTypes.func.isRequired, @@ -53,9 +53,9 @@ class DirColumnFile extends React.Component { } if (this.props.content === '__sf-metadata') { - const { repoID, currentRepoInfo, metadataViewId } = this.props; + const { repoID, currentRepoInfo, viewId } = this.props; - return (); + return (); } return ( diff --git a/frontend/src/components/dir-view-mode/dir-column-nav.js b/frontend/src/components/dir-view-mode/dir-column-nav.js index 6c9e465ab2..018c3b026b 100644 --- a/frontend/src/components/dir-view-mode/dir-column-nav.js +++ b/frontend/src/components/dir-view-mode/dir-column-nav.js @@ -312,12 +312,7 @@ class DirColumnNav extends React.Component { getMenuContainerSize={getMenuContainerSize} /> - + { +const DirViews = ({ userPerm, repoID, currentPath }) => { const enableMetadataManagement = useMemo(() => { return window.app.pageOptions.enableMetadataManagement; // eslint-disable-next-line react-hooks/exhaustive-deps }, [window.app.pageOptions.enableMetadataManagement]); const [showMetadataStatusManagementDialog, setShowMetadataStatusManagementDialog] = useState(false); - const { enableExtendedProperties, updateEnableExtendedProperties } = useMetadataStatus(); + const { enableMetadata, updateEnableMetadata, navigation } = useMetadata(); const moreOperations = useMemo(() => { if (!enableMetadataManagement) return []; if (userPerm !== 'rw' && userPerm !== 'admin') return []; @@ -32,8 +32,8 @@ const DirViews = ({ userPerm, repoID, currentPath, onNodeClick }) => { }, []); const toggleMetadataStatus = useCallback((value) => { - updateEnableExtendedProperties(value); - }, [updateEnableExtendedProperties]); + updateEnableMetadata(value); + }, [updateEnableMetadata]); return ( <> @@ -43,11 +43,13 @@ const DirViews = ({ userPerm, repoID, currentPath, onNodeClick }) => { moreOperations={moreOperations} moreOperationClick={moreOperationClick} > - {enableExtendedProperties && ()} + {enableMetadata && Array.isArray(navigation) && navigation.length > 0 && ( + + )} {showMetadataStatusManagementDialog && ( { const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]); - const { enableExtendedProperties } = useMetadataStatus(); + const { enableMetadata } = useMetadata(); return ( <> - {window.app.pageOptions.enableMetadataManagement && enableExtendedProperties && ( + {window.app.pageOptions.enableMetadataManagement && enableMetadata && ( )} diff --git a/frontend/src/components/dirent-detail/dirent-details/file-details.js b/frontend/src/components/dirent-detail/dirent-details/file-details.js index a9d72250e5..a075a2679f 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -8,12 +8,12 @@ import { gettext } from '../../../utils/constants'; import EditFileTagPopover from '../../popover/edit-filetag-popover'; import FileTagList from '../../file-tag-list'; import { Utils } from '../../../utils/utils'; -import { MetadataDetails, useMetadataStatus } from '../../../metadata'; +import { MetadataDetails, useMetadata } from '../../../metadata'; import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils'; const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList, ...params }) => { const [isEditFileTagShow, setEditFileTagShow] = useState(false); - const { enableExtendedProperties } = useMetadataStatus(); + const { enableMetadata } = useMetadata(); const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]); const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []); @@ -36,7 +36,7 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, avatar_url: direntDetail.last_modifier_avatar, }]} /> - {!window.app.pageOptions.enableMetadataManagement && enableExtendedProperties && ( + {!window.app.pageOptions.enableMetadataManagement && enableMetadata && ( {Array.isArray(fileTagList) && fileTagList.length > 0 ? ( diff --git a/frontend/src/metadata/hooks/index.js b/frontend/src/metadata/hooks/index.js index 68b307d548..ac4c71cb99 100644 --- a/frontend/src/metadata/hooks/index.js +++ b/frontend/src/metadata/hooks/index.js @@ -1 +1 @@ -export { MetadataStatusProvider, useMetadataStatus } from './metadata-status'; +export { MetadataProvider, useMetadata } from './metadata'; diff --git a/frontend/src/metadata/hooks/metadata-status.js b/frontend/src/metadata/hooks/metadata-status.js deleted file mode 100644 index 9a6b946b14..0000000000 --- a/frontend/src/metadata/hooks/metadata-status.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import metadataAPI from '../api'; -import { Utils } from '../../utils/utils'; -import toaster from '../../components/toast'; - -const MetadataStatusContext = React.createContext(null); - -export const MetadataStatusProvider = ({ repoID, children }) => { - const enableMetadataManagement = useMemo(() => { - return window.app.pageOptions.enableMetadataManagement; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [window.app.pageOptions.enableMetadataManagement]); - - - const [enableExtendedProperties, setEnableExtendedProperties] = useState(false); - - useEffect(() => { - if (!enableMetadataManagement) { - setEnableExtendedProperties(false); - return; - } - metadataAPI.getMetadataStatus(repoID).then(res => { - setEnableExtendedProperties(res.data.enabled); - }).catch(error => { - const errorMsg = Utils.getErrorMsg(error, true); - toaster.danger(errorMsg); - setEnableExtendedProperties(false); - }); - }, [repoID, enableMetadataManagement]); - - const updateEnableExtendedProperties = useCallback((newValue) => { - if (newValue === enableExtendedProperties) return; - setEnableExtendedProperties(newValue); - }, [enableExtendedProperties]); - - return ( - - {children} - - ); -}; - -export const useMetadataStatus = () => { - const context = useContext(MetadataStatusContext); - if (!context) { - throw new Error('\'MetadataStatusContext\' is null'); - } - const { enableExtendedProperties, updateEnableExtendedProperties } = context; - return { enableExtendedProperties, updateEnableExtendedProperties }; -}; diff --git a/frontend/src/metadata/hooks/metadata.js b/frontend/src/metadata/hooks/metadata.js new file mode 100644 index 0000000000..56ded1200e --- /dev/null +++ b/frontend/src/metadata/hooks/metadata.js @@ -0,0 +1,179 @@ +import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import metadataAPI from '../api'; +import { Utils } from '../../utils/utils'; +import toaster from '../../components/toast'; +import { gettext } from '../../utils/constants'; +import { PRIVATE_FILE_TYPE } from '../../constants'; + +// This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc. +const MetadataContext = React.createContext(null); + +export const MetadataProvider = ({ repoID, hideMetadataView, selectMetadataView, renameMetadataView, children }) => { + const enableMetadataManagement = useMemo(() => { + return window.app.pageOptions.enableMetadataManagement; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [window.app.pageOptions.enableMetadataManagement]); + + const [enableMetadata, setEnableExtendedProperties] = useState(false); + const [showFirstView, setShowFirstView] = useState(false); + const [navigation, setNavigation] = useState([]); + const viewsMap = useRef({}); + + useEffect(() => { + if (!enableMetadataManagement) { + setEnableExtendedProperties(false); + return; + } + metadataAPI.getMetadataStatus(repoID).then(res => { + setEnableExtendedProperties(res.data.enabled); + }).catch(error => { + const errorMsg = Utils.getErrorMsg(error, true); + toaster.danger(errorMsg); + setEnableExtendedProperties(false); + }); + }, [repoID, enableMetadataManagement]); + + const updateEnableMetadata = useCallback((newValue) => { + if (newValue === enableMetadata) return; + if (!newValue) { + hideMetadataView && hideMetadataView(); + } else { + setShowFirstView(true); + } + setEnableExtendedProperties(newValue); + }, [enableMetadata, hideMetadataView]); + + // views + useEffect(() => { + if (enableMetadata) { + metadataAPI.listViews(repoID).then(res => { + const { navigation, views } = res.data; + if (Array.isArray(views)) { + views.forEach(view => { + viewsMap.current[view._id] = view; + }); + } + setNavigation(navigation); + }).catch(error => { + const errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + }); + return; + } + + // If attribute extension is turned off, unmark the URL + const { origin, pathname, search } = window.location; + const urlParams = new URLSearchParams(search); + const viewID = urlParams.get('view'); + if (viewID) { + const url = `${origin}${pathname}`; + window.history.pushState({ url: url, path: '' }, '', url); + } + viewsMap.current = {}; + setNavigation([]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [repoID, enableMetadata]); + + const selectView = useCallback((view, isSelected) => { + if (isSelected) return; + const node = { + children: [], + path: '/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/' + view.name, + isExpanded: false, + isLoaded: true, + isPreload: true, + object: { + file_tags: [], + id: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES, + name: gettext('File extended properties'), + type: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES, + isDir: () => false, + }, + parentNode: {}, + key: repoID, + view_id: view._id, + }; + selectMetadataView(node); + setShowFirstView(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [repoID, selectMetadataView]); + + const addView = useCallback((name, successCallback, failCallback) => { + metadataAPI.addView(repoID, name).then(res => { + const view = res.data.view; + let newNavigation = navigation.slice(0); + newNavigation.push({ _id: view._id, type: 'view' }); + viewsMap.current[view._id] = view; + setNavigation(newNavigation); + selectView(view); + successCallback && successCallback(); + }).catch(error => { + failCallback && failCallback(error); + }); + }, [navigation, repoID, viewsMap, selectView]); + + const deleteView = useCallback((viewId, isSelected) => { + metadataAPI.deleteView(repoID, viewId).then(res => { + const newNavigation = navigation.filter(item => item._id !== viewId); + delete viewsMap.current[viewId]; + setNavigation(newNavigation); + if (isSelected) { + const currentViewIndex = navigation.findIndex(item => item._id === viewId); + const lastViewId = navigation[currentViewIndex - 1]._id; + const lastView = viewsMap.current[lastViewId]; + selectView(lastView); + } + }).catch((error => { + const errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + })); + }, [repoID, navigation, selectView, viewsMap]); + + const updateView = useCallback((viewId, update, successCallback, failCallback) => { + metadataAPI.modifyView(repoID, viewId, update).then(res => { + const currentView = viewsMap.current[viewId]; + viewsMap.current[viewId] = { ...currentView, ...update }; + if (Object.prototype.hasOwnProperty.call(update, 'name')) { + renameMetadataView(viewId, '/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/' + update['name']); + } + successCallback && successCallback(); + }).catch(error => { + failCallback && failCallback(error); + }); + }, [repoID, viewsMap, renameMetadataView]); + + const moveView = useCallback((sourceViewId, targetViewId) => { + metadataAPI.moveView(repoID, sourceViewId, targetViewId).then(res => { + const { navigation } = res.data; + setNavigation(navigation); + }).catch(error => { + const errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + }); + }, [repoID]); + + return ( + + {children} + + ); +}; + +export const useMetadata = () => { + const context = useContext(MetadataContext); + if (!context) { + throw new Error('\'MetadataContext\' is null'); + } + return context; +}; diff --git a/frontend/src/metadata/metadata-tree-view/index.js b/frontend/src/metadata/metadata-tree-view/index.js index 8568b86631..3d1a6cc953 100644 --- a/frontend/src/metadata/metadata-tree-view/index.js +++ b/frontend/src/metadata/metadata-tree-view/index.js @@ -1,65 +1,54 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; +import { CustomizeAddTool } from '@seafile/sf-metadata-ui-component'; import { gettext } from '../../utils/constants'; import { PRIVATE_FILE_TYPE } from '../../constants'; -import metadataAPI from '../api'; -import { Utils } from '../../utils/utils'; -import toaster from '../../components/toast'; import ViewItem from './view-item'; import NameDialog from './name-dialog'; +import { useMetadata } from '../hooks'; import './index.css'; -import { CustomizeAddTool } from '@seafile/sf-metadata-ui-component'; -const MetadataTreeView = ({ userPerm, repoID, currentPath, onNodeClick }) => { +const MetadataTreeView = ({ userPerm, currentPath }) => { const canAdd = useMemo(() => { if (userPerm !== 'rw' && userPerm !== 'admin') return false; return true; }, [userPerm]); - const [views, setViews] = useState([]); const [showAddViewDialog, setSowAddViewDialog] = useState(false); const [, setState] = useState(0); - const viewsMap = useRef({}); + const { + showFirstView, + navigation, + viewsMap, + selectView, + addView, + deleteView, + updateView, + moveView + } = useMetadata(); useEffect(() => { - metadataAPI.listViews(repoID).then(res => { - const { navigation, views } = res.data; - if (Array.isArray(views)) { - views.forEach(view => { - viewsMap.current[view._id] = view; - }); + const { origin, pathname, search } = window.location; + const urlParams = new URLSearchParams(search); + const viewID = urlParams.get('view'); + if (viewID) { + const lastOpenedView = viewsMap[viewID] || ''; + if (lastOpenedView) { + selectView(lastOpenedView); + return; } - setViews(navigation); - }).catch(error => { - const errorMsg = Utils.getErrorMsg(error); - toaster.danger(errorMsg); - }); + const url = `${origin}${pathname}`; + window.history.pushState({ url: url, path: '' }, '', url); + } + + const firstViewObject = navigation.find(item => item.type === 'view'); + const firstView = firstViewObject ? viewsMap[firstViewObject._id] : ''; + if (showFirstView && firstView) { + selectView(firstView); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const onClick = useCallback((view, isSelected) => { - if (isSelected) return; - const node = { - children: [], - path: '/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/' + view.name, - isExpanded: false, - isLoaded: true, - isPreload: true, - object: { - file_tags: [], - id: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES, - name: gettext('File extended properties'), - type: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES, - isDir: () => false, - }, - parentNode: {}, - key: repoID, - view_id: view._id, - view_name: view.name, - }; - onNodeClick(node); - }, [repoID, onNodeClick]); - const openAddView = useCallback(() => { setSowAddViewDialog(true); }, []); @@ -68,66 +57,20 @@ const MetadataTreeView = ({ userPerm, repoID, currentPath, onNodeClick }) => { setSowAddViewDialog(false); }, []); - const addView = useCallback((name, failCallback) => { - metadataAPI.addView(repoID, name).then(res => { - const view = res.data.view; - let newViews = views.slice(0); - newViews.push({ _id: view._id, type: 'view' }); - viewsMap.current[view._id] = view; - setSowAddViewDialog(false); - setViews(newViews); - onClick(view); - }).catch(error => { - failCallback && failCallback(error); - }); - }, [views, repoID, viewsMap, onClick]); - - const onDeleteView = useCallback((viewId, isSelected) => { - metadataAPI.deleteView(repoID, viewId).then(res => { - const newViews = views.filter(item => item._id !== viewId); - delete viewsMap.current[viewId]; - setViews(newViews); - if (isSelected) { - const currentViewIndex = views.findIndex(item => item._id === viewId); - const lastViewId = views[currentViewIndex - 1]._id; - const lastView = viewsMap.current[lastViewId]; - onClick(lastView); - } - }).catch((error => { - const errorMsg = Utils.getErrorMsg(error); - toaster.danger(errorMsg); - })); - }, [repoID, views, onClick, viewsMap]); - const onUpdateView = useCallback((viewId, update, successCallback, failCallback) => { - metadataAPI.modifyView(repoID, viewId, update).then(res => { - successCallback && successCallback(); - const currentView = viewsMap.current[viewId]; - viewsMap.current[viewId] = { ...currentView, ...update }; + updateView(viewId, update, () => { setState(n => n + 1); - }).catch(error => { - failCallback && failCallback(error); - }); - }, [repoID, viewsMap]); - - const onMoveView = useCallback((sourceViewId, targetViewId) => { - metadataAPI.moveView(repoID, sourceViewId, targetViewId).then(res => { - const { navigation } = res.data; - setViews(navigation); - }).catch(error => { - const errorMsg = Utils.getErrorMsg(error); - toaster.danger(errorMsg); - }); - }, [repoID]); + successCallback && successCallback(); + }, failCallback); + }, [updateView]); return ( <>
- {views.map((item, index) => { - if (item.type !== 'view') return null; - const view = viewsMap.current[item._id]; + {navigation.map((item, index) => { + const view = viewsMap[item._id]; const viewPath = '/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/' + view.name; const isSelected = currentPath === viewPath; return ( @@ -137,10 +80,10 @@ const MetadataTreeView = ({ userPerm, repoID, currentPath, onNodeClick }) => { isSelected={isSelected} userPerm={userPerm} view={view} - onClick={(view) => onClick(view, isSelected)} - onDelete={() => onDeleteView(view._id, isSelected)} + onClick={(view) => selectView(view, isSelected)} + onDelete={() => deleteView(view._id, isSelected)} onUpdate={(update, successCallback, failCallback) => onUpdateView(view._id, update, successCallback, failCallback)} - onMove={onMoveView} + onMove={moveView} />); })} {canAdd && @@ -161,9 +104,7 @@ const MetadataTreeView = ({ userPerm, repoID, currentPath, onNodeClick }) => { MetadataTreeView.propTypes = { userPerm: PropTypes.string, - repoID: PropTypes.string.isRequired, currentPath: PropTypes.string, - onNodeClick: PropTypes.func, }; export default MetadataTreeView; diff --git a/frontend/src/metadata/metadata-tree-view/name-dialog/index.js b/frontend/src/metadata/metadata-tree-view/name-dialog/index.js index f6eb481ee6..9f2710fa41 100644 --- a/frontend/src/metadata/metadata-tree-view/name-dialog/index.js +++ b/frontend/src/metadata/metadata-tree-view/name-dialog/index.js @@ -45,7 +45,9 @@ const NameDialog = ({ value: oldName, title, onSubmit, onToggle }) => { onToggle(); return; } - onSubmit(message, (error) => { + onSubmit(message, () => { + onToggle(); + }, (error) => { const errorMsg = Utils.getErrorMsg(error); setErrorMessage(errorMsg); setSubmitting(false); diff --git a/frontend/src/metadata/metadata-tree-view/view-item/index.js b/frontend/src/metadata/metadata-tree-view/view-item/index.js index 72acd3b0bd..dde1c184de 100644 --- a/frontend/src/metadata/metadata-tree-view/view-item/index.js +++ b/frontend/src/metadata/metadata-tree-view/view-item/index.js @@ -92,7 +92,7 @@ const ViewItem = ({ if (!canDrop) return false; const dragData = JSON.stringify({ type: 'sf-metadata-view', view_id: view._id }); event.dataTransfer.effectAllowed = 'move'; - event.dataTransfer.setData('application/drag-sf-metadata-view-info', dragData); + event.dataTransfer.setData('application/drag-sf-metadata-view', dragData); }, [canDrop, view]); const onDragEnter = useCallback((event) => { @@ -105,8 +105,10 @@ const ViewItem = ({ setDropShow(false); }, [canDrop]); - const onDragMove = useCallback(() => { + const onDragMove = useCallback((event) => { if (!canDrop) return false; + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; }, [canDrop]); const onDrop = useCallback((event) => { @@ -114,7 +116,7 @@ const ViewItem = ({ event.stopPropagation(); setDropShow(false); - let dragData = event.dataTransfer.getData('application/drag-sf-metadata-view-info'); + let dragData = event.dataTransfer.getData('application/drag-sf-metadata-view'); if (!dragData) return; dragData = JSON.parse(dragData); if (dragData.type !== 'sf-metadata-view') return false; diff --git a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js index 917890a8b6..b3430c6d8d 100644 --- a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js +++ b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js @@ -5,7 +5,7 @@ import { EVENT_BUS_TYPE } from '../../constants'; import './index.css'; -const ViewToolBar = ({ metadataViewId }) => { +const ViewToolBar = ({ viewId }) => { const [view, setView] = useState(null); const [collaborators, setCollaborators] = useState([]); @@ -59,7 +59,7 @@ const ViewToolBar = ({ metadataViewId }) => { unsubscribeViewChange && unsubscribeViewChange(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [metadataViewId]); + }, [viewId]); if (!view) return null; @@ -116,7 +116,7 @@ const ViewToolBar = ({ metadataViewId }) => { }; ViewToolBar.propTypes = { - metadataViewId: PropTypes.string, + viewId: PropTypes.string, }; export default ViewToolBar; diff --git a/frontend/src/pages/lib-content-view/lib-content-container.js b/frontend/src/pages/lib-content-view/lib-content-container.js index 61de0cf0cd..7d637c43e5 100644 --- a/frontend/src/pages/lib-content-view/lib-content-container.js +++ b/frontend/src/pages/lib-content-view/lib-content-container.js @@ -6,7 +6,6 @@ import CurDirPath from '../../components/cur-dir-path'; import Detail from '../../components/dirent-detail'; import DirColumnView from '../../components/dir-view-mode/dir-column-view'; import ToolbarForSelectedDirents from '../../components/toolbar/selected-dirents-toolbar'; -import { MetadataStatusProvider } from '../../metadata/hooks'; import '../../css/lib-content-view.css'; @@ -40,7 +39,7 @@ const propTypes = { isFileLoading: PropTypes.bool.isRequired, filePermission: PropTypes.string, content: PropTypes.string, - metadataViewId: PropTypes.string, + viewId: PropTypes.string, lastModified: PropTypes.string, latestContributor: PropTypes.string, onLinkClick: PropTypes.func.isRequired, @@ -183,160 +182,158 @@ class LibContentContainer extends React.Component { } return ( - -
- {this.props.currentRepoInfo.status === 'read-only' && -
- {gettext('This library has been set to read-only by admin and cannot be updated.')} -
- } -
- + {this.props.currentRepoInfo.status === 'read-only' && +
+ {gettext('This library has been set to read-only by admin and cannot be updated.')} +
+ } +
+ + +
+
+ {!this.props.pathExist && this.errMessage} + {this.props.pathExist && ( + - -
-
- {!this.props.pathExist && this.errMessage} - {this.props.pathExist && ( - - )} - {this.props.isDirentDetailShow && ( - - )} -
+ )} + {this.props.isDirentDetailShow && ( + + )}
- +
); } } diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 2da461ea8f..d4f63ae3af 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -24,6 +24,7 @@ import CopyMoveDirentProgressDialog from '../../components/dialog/copy-move-dire import DeleteFolderDialog from '../../components/dialog/delete-folder-dialog'; import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; import { PRIVATE_FILE_TYPE } from '../../constants'; +import { MetadataProvider } from '../../metadata/hooks'; const propTypes = { eventBus: PropTypes.object, @@ -89,7 +90,7 @@ class LibContentView extends React.Component { asyncOperationType: 'move', asyncOperationProgress: 0, asyncOperatedFilesLength: 0, - metadataViewId: '0000', + viewId: '0000', }; this.oldonpopstate = window.onpopstate; @@ -148,12 +149,7 @@ class LibContentView extends React.Component { const { repoID, eventBus } = props; this.unsubscribeEvent = eventBus.subscribe(EVENT_BUS_TYPE.SEARCH_LIBRARY_CONTENT, this.onSearchedClick); - const urlParams = new URLSearchParams(window.location.search); - const viewID = urlParams.get('view'); - const viewName = JSON.parse(localStorage.getItem('last_visited_view'))?.viewName; - const path = viewID - ? `/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewName}` - : this.getPathFromLocation(repoID); + const path = this.getPathFromLocation(repoID); try { const repoInfo = await this.fetchRepoInfo(repoID); @@ -410,19 +406,11 @@ class LibContentView extends React.Component { this.loadSidePanel(path); } - if (path.includes(PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES)) { - this.handleFileExtendedProperties(path); - } else { + if (!path.includes(PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES)) { this.showDir(path); } }; - handleFileExtendedProperties = (path) => { - const lastVisitedView = localStorage.getItem('last_visited_view'); - const { viewID, viewName } = lastVisitedView ? JSON.parse(lastVisitedView) : {}; - this.showFileMetadata(path, viewID, viewName); - }; - loadSidePanel = (path) => { let repoID = this.props.repoID; if (path === '/' || path.includes(PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES)) { @@ -523,7 +511,7 @@ class LibContentView extends React.Component { window.history.pushState({ url: url, path: filePath }, filePath, url); }; - showFileMetadata = (filePath, viewId, viewName) => { + showFileMetadata = (filePath, viewId) => { const repoID = this.props.repoID; const repoInfo = this.state.currentRepoInfo; @@ -533,16 +521,33 @@ class LibContentView extends React.Component { isFileLoading: false, isFileLoadedErr: false, content: '__sf-metadata', - metadataViewId: viewId, + viewId: viewId, isDirentDetailShow: false }); const url = `${siteRoot}library/${repoID}/${encodeURIComponent(repoInfo.repo_name)}?view=${encodeURIComponent(viewId)}`; - - localStorage.setItem('last_visited_view', JSON.stringify({ viewID: viewId, viewName: viewName })); window.history.pushState({ url: url, path: '' }, '', url); }; + hideFileMetadata = () => { + this.setState({ + path: '', + isViewFile: false, + isFileLoading: false, + isFileLoadedErr: false, + content: '', + viewId: '', + isDirentDetailShow: false + }); + }; + + renameMetadataView = (renamedViewId, newPath) => { + const { viewId, content } = this.state; + if (content !== '__sf-metadata') return; + if (viewId !== renamedViewId) return; + this.setState({ path: newPath }); + }; + loadDirentList = (path) => { let repoID = this.props.repoID; seafileAPI.listDir(repoID, path, { 'with_thumbnail': true }).then(res => { @@ -1827,7 +1832,7 @@ class LibContentView extends React.Component { } } else if (Utils.isFileMetadata(node?.object?.type)) { if (node.path !== this.state.path) { - this.showFileMetadata(node.path, node.view_id || '0000', node.view_name); + this.showFileMetadata(node.path, node.view_id || '0000'); } } else { let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); @@ -2163,7 +2168,12 @@ class LibContentView extends React.Component { } return ( - +
- + ); } }