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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
@@ -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>}
|
||||
|
@@ -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 (
|
||||
|
@@ -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}
|
||||
|
@@ -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}
|
||||
|
@@ -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}
|
||||
|
@@ -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 } />
|
||||
)}
|
||||
</>
|
||||
|
@@ -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} />
|
||||
|
@@ -1 +1 @@
|
||||
export { MetadataStatusProvider, useMetadataStatus } from './metadata-status';
|
||||
export { MetadataProvider, useMetadata } from './metadata';
|
||||
|
@@ -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 };
|
||||
};
|
179
frontend/src/metadata/hooks/metadata.js
Normal file
179
frontend/src/metadata/hooks/metadata.js
Normal 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;
|
||||
};
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user