1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-06 17:33:18 +00:00

fix: metadata status toggle (#6530)

* fix: metadata status toggle

* feat: optimize code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-08-12 17:15:56 +08:00
committed by GitHub
parent 0ec12c0948
commit 27bf8792da
17 changed files with 426 additions and 348 deletions

View File

@@ -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 (
<div className="d-flex">
<MetadataViewToolBar metadataViewId={metadataViewId} />
<MetadataViewToolBar viewId={viewId} />
</div>
);
}

View File

@@ -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 &&
<span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}

View File

@@ -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 (<SeafileMetadata mediaUrl={mediaUrl} repoID={repoID} repoInfo={currentRepoInfo} viewID={metadataViewId} />);
return (<SeafileMetadata mediaUrl={mediaUrl} repoID={repoID} repoInfo={currentRepoInfo} viewID={viewId} />);
}
return (

View File

@@ -312,12 +312,7 @@ class DirColumnNav extends React.Component {
getMenuContainerSize={getMenuContainerSize}
/>
</TreeSection>
<DirViews
repoID={repoID}
currentPath={currentPath}
userPerm={userPerm}
onNodeClick={this.onNodeClick}
/>
<DirViews repoID={repoID} currentPath={currentPath} userPerm={userPerm} />
<DirOthers
repoID={repoID}
userPerm={userPerm}

View File

@@ -37,7 +37,7 @@ const propTypes = {
hash: PropTypes.string,
filePermission: PropTypes.string,
content: PropTypes.string,
metadataViewId: PropTypes.string,
viewId: PropTypes.string,
lastModified: PropTypes.string,
latestContributor: PropTypes.string,
onLinkClick: PropTypes.func.isRequired,
@@ -194,7 +194,7 @@ class DirColumnView extends React.Component {
isFileLoadedErr={this.props.isFileLoadedErr}
filePermission={this.props.filePermission}
content={this.props.content}
metadataViewId={this.props.metadataViewId}
viewId={this.props.viewId}
currentRepoInfo={this.props.currentRepoInfo}
lastModified={this.props.lastModified}
latestContributor={this.props.latestContributor}

View File

@@ -2,16 +2,16 @@ import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import TreeSection from '../tree-section';
import { MetadataStatusManagementDialog, MetadataTreeView, useMetadataStatus } from '../../metadata';
import { MetadataStatusManagementDialog, MetadataTreeView, useMetadata } from '../../metadata';
const DirViews = ({ userPerm, repoID, currentPath, onNodeClick }) => {
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 && (<MetadataTreeView userPerm={userPerm} repoID={repoID} currentPath={currentPath} onNodeClick={onNodeClick} />)}
{enableMetadata && Array.isArray(navigation) && navigation.length > 0 && (
<MetadataTreeView userPerm={userPerm} currentPath={currentPath} />
)}
</TreeSection>
{showMetadataStatusManagementDialog && (
<MetadataStatusManagementDialog
value={enableExtendedProperties}
value={enableMetadata}
repoID={repoID}
toggle={closeMetadataManagementDialog}
submit={toggleMetadataStatus}

View File

@@ -4,16 +4,16 @@ import { getDirentPath } from './utils';
import DetailItem from '../detail-item';
import { CellType } from '../../../metadata/metadata-view/_basic';
import { gettext } from '../../../utils/constants';
import { MetadataDetails, useMetadataStatus } from '../../../metadata';
import { MetadataDetails, useMetadata } from '../../../metadata';
const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail, ...params }) => {
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
const { enableExtendedProperties } = useMetadataStatus();
const { enableMetadata } = useMetadata();
return (
<>
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.mtime} />
{window.app.pageOptions.enableMetadataManagement && enableExtendedProperties && (
{window.app.pageOptions.enableMetadataManagement && enableMetadata && (
<MetadataDetails repoID={repoID} filePath={direntPath} direntType="dir" { ...params } />
)}
</>

View File

@@ -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,
}]} />
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.last_modified} />
{!window.app.pageOptions.enableMetadataManagement && enableExtendedProperties && (
{!window.app.pageOptions.enableMetadataManagement && enableMetadata && (
<DetailItem field={{ type: CellType.SINGLE_SELECT, name: gettext('Tags') }} valueId={tagListTitleID} valueClick={onEditFileTagToggle} >
{Array.isArray(fileTagList) && fileTagList.length > 0 ? (
<FileTagList fileTagList={fileTagList} />

View File

@@ -1 +1 @@
export { MetadataStatusProvider, useMetadataStatus } from './metadata-status';
export { MetadataProvider, useMetadata } from './metadata';

View File

@@ -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 (
<MetadataStatusContext.Provider value={{ enableExtendedProperties, updateEnableExtendedProperties }}>
{children}
</MetadataStatusContext.Provider>
);
};
export const useMetadataStatus = () => {
const context = useContext(MetadataStatusContext);
if (!context) {
throw new Error('\'MetadataStatusContext\' is null');
}
const { enableExtendedProperties, updateEnableExtendedProperties } = context;
return { enableExtendedProperties, updateEnableExtendedProperties };
};

View File

@@ -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 (
<MetadataContext.Provider value={{
enableMetadata,
updateEnableMetadata,
showFirstView,
navigation,
viewsMap: viewsMap.current,
selectView,
addView,
deleteView,
updateView,
moveView,
}}>
{children}
</MetadataContext.Provider>
);
};
export const useMetadata = () => {
const context = useContext(MetadataContext);
if (!context) {
throw new Error('\'MetadataContext\' is null');
}
return context;
};

View File

@@ -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;
}
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);
}
setViews(navigation);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
});
// 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 (
<>
<div className="tree-view tree metadata-tree-view">
<div className="tree-node">
<div className="children">
{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;

View File

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

View File

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

View File

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

View File

@@ -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,7 +182,6 @@ class LibContentContainer extends React.Component {
}
return (
<MetadataStatusProvider repoID={repoID}>
<div className="cur-view-container">
{this.props.currentRepoInfo.status === 'read-only' &&
<div className="readonly-tip-message">
@@ -224,7 +222,7 @@ class LibContentContainer extends React.Component {
filePermission={this.props.filePermission}
onFileTagChanged={this.props.onToolbarFileTagChanged}
repoTags={this.props.repoTags}
metadataViewId={this.props.metadataViewId}
viewId={this.props.viewId}
onItemMove={this.props.onItemMove}
/>
<ToolbarForSelectedDirents
@@ -284,7 +282,7 @@ class LibContentContainer extends React.Component {
hash={this.props.hash}
filePermission={this.props.filePermission}
content={this.props.content}
metadataViewId={this.props.metadataViewId}
viewId={this.props.viewId}
lastModified={this.props.lastModified}
latestContributor={this.props.latestContributor}
onLinkClick={this.props.onLinkClick}
@@ -336,7 +334,6 @@ class LibContentContainer extends React.Component {
)}
</div>
</div>
</MetadataStatusProvider>
);
}
}

View File

@@ -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 (
<Fragment>
<MetadataProvider
repoID={this.props.repoID}
selectMetadataView={this.onTreeNodeClick}
renameMetadataView={this.renameMetadataView}
hideMetadataView={this.hideFileMetadata}
>
<div className="main-panel-center flex-row">
<LibContentContainer
isSidePanelFolded={this.props.isSidePanelFolded}
@@ -2193,7 +2203,7 @@ class LibContentView extends React.Component {
isFileLoadedErr={this.state.isFileLoadedErr}
filePermission={this.state.filePermission}
content={this.state.content}
metadataViewId={this.state.metadataViewId}
viewId={this.state.viewId}
lastModified={this.state.lastModified}
latestContributor={this.state.latestContributor}
onLinkClick={this.onLinkClick}
@@ -2281,7 +2291,7 @@ class LibContentView extends React.Component {
<MediaQuery query="(max-width: 767.8px)">
<Modal zIndex="1030" isOpen={!Utils.isDesktop() && this.state.isTreePanelShown} toggle={this.toggleTreePanel} contentClassName="d-none"></Modal>
</MediaQuery>
</Fragment>
</MetadataProvider>
);
}
}