1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-05 17:02:47 +00:00

feat: all tags (#7099)

* feat: all tags

* feat: optimize code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-11-25 20:30:46 +08:00
committed by GitHub
parent d407c9fd63
commit 03fd3b8f55
24 changed files with 324 additions and 305 deletions

View File

@@ -157,7 +157,7 @@ class DirPath extends React.Component {
return (
<Fragment key={index}>
<span className="path-split">/</span>
<span className="path-item"><TagViewName id={item} /></span>
<TagViewName id={item} />
</Fragment>
);
}

View File

@@ -6,17 +6,21 @@ import { gettext } from '../../utils/constants';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { FACE_RECOGNITION_VIEW_ID, VIEW_TYPE } from '../constants';
import { useMetadataStatus } from '../../hooks';
import { updateFavicon } from '../utils/favicon';
// 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, repoInfo, hideMetadataView, selectMetadataView, children }) => {
export const MetadataProvider = ({ repoID, currentPath, repoInfo, hideMetadataView, selectMetadataView, children }) => {
const [isLoading, setLoading] = useState(true);
const [enableFaceRecognition, setEnableFaceRecognition] = useState(false);
const [showFirstView, setShowFirstView] = useState(false);
const [navigation, setNavigation] = useState([]);
const [staticView, setStaticView] = useState([]);
const [, setCount] = useState(0);
const viewsMap = useRef({});
const originalTitleRef = useRef(document.title);
const isEmptyRepo = useMemo(() => repoInfo.file_count === 0, [repoInfo]);
@@ -32,6 +36,7 @@ export const MetadataProvider = ({ repoID, repoInfo, hideMetadataView, selectMet
// views
useEffect(() => {
setLoading(true);
if (enableMetadata) {
metadataAPI.listViews(repoID).then(res => {
const { navigation, views } = res.data;
@@ -46,9 +51,11 @@ export const MetadataProvider = ({ repoID, repoInfo, hideMetadataView, selectMet
type: VIEW_TYPE.FACE_RECOGNITION,
};
setNavigation(navigation);
setLoading(false);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
setLoading(false);
});
return;
}
@@ -57,8 +64,9 @@ export const MetadataProvider = ({ repoID, repoInfo, hideMetadataView, selectMet
viewsMap.current = {};
setStaticView([]);
setNavigation([]);
setLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [repoID, enableMetadata, hideMetadataView]);
}, [repoID, enableMetadata]);
useEffect(() => {
if (!enableMetadata) {
@@ -171,6 +179,43 @@ export const MetadataProvider = ({ repoID, repoInfo, hideMetadataView, selectMet
});
}, [repoID]);
useEffect(() => {
if (isLoading) return;
const { origin, pathname, search } = window.location;
const urlParams = new URLSearchParams(search);
if (!urlParams.has('view')) return;
const viewID = urlParams.get('view');
if (viewID) {
const lastOpenedView = viewsMap.current[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.current[firstViewObject._id] : '';
if (firstView) {
selectView(firstView);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading]);
useEffect(() => {
if (!currentPath.includes('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/')) return;
const currentViewId = currentPath.split('/').pop();
const currentView = viewsMap.current[currentViewId];
if (currentView) {
document.title = `${currentView.name} - Seafile`;
updateFavicon(currentView.type);
return;
}
document.title = originalTitleRef.current;
updateFavicon('default');
}, [currentPath, viewsMap]);
return (
<MetadataContext.Provider value={{
isEmptyRepo,

View File

@@ -6,7 +6,7 @@ import toaster from '../../components/toast';
import Icon from '../../components/icon';
import ViewItem from './view-item';
import { AddView } from '../components/popover/view-popover';
import { gettext, mediaUrl } from '../../utils/constants';
import { gettext } from '../../utils/constants';
import { useMetadata } from '../hooks';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { VIEW_TYPE, VIEW_TYPE_ICON } from '../constants';
@@ -15,32 +15,6 @@ import { isEnter } from '../utils/hotkey';
import './index.css';
const updateFavicon = (type) => {
const favicon = document.getElementById('favicon');
if (favicon) {
switch (type) {
case VIEW_TYPE.GALLERY:
case 'image':
favicon.href = `${mediaUrl}favicons/gallery.png`;
break;
case VIEW_TYPE.TABLE:
favicon.href = `${mediaUrl}favicons/table.png`;
break;
case VIEW_TYPE.FACE_RECOGNITION:
favicon.href = `${mediaUrl}favicons/face-recognition-view.png`;
break;
case VIEW_TYPE.KANBAN:
favicon.href = `${mediaUrl}favicons/kanban.png`;
break;
case VIEW_TYPE.MAP:
favicon.href = `${mediaUrl}favicons/map.png`;
break;
default:
favicon.href = `${mediaUrl}favicons/favicon.png`;
}
}
};
const MetadataTreeView = ({ userPerm, currentPath }) => {
const canAdd = useMemo(() => {
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
@@ -49,7 +23,6 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
const [, setState] = useState(0);
const {
enableFaceRecognition,
showFirstView,
navigation,
staticView,
viewsMap,
@@ -64,55 +37,9 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
const [showAddViewPopover, setShowAddViewPopover] = useState(false);
const [showInput, setShowInput] = useState(false);
const [inputValue, setInputValue] = useState('');
const [originalTitle, setOriginalTitle] = useState('');
const inputRef = useRef(null);
useEffect(() => {
setOriginalTitle(document.title);
}, []);
useEffect(() => {
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);
document.title = `${lastOpenedView.name} - Seafile`;
updateFavicon(lastOpenedView.type);
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);
document.title = `${firstView.name} - Seafile`;
updateFavicon(firstView.type);
} else {
document.title = originalTitle;
updateFavicon('default');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!currentPath.includes('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/')) return;
const currentViewId = currentPath.split('/').pop();
const currentView = viewsMap[currentViewId];
if (currentView) {
document.title = `${currentView.name} - Seafile`;
updateFavicon(currentView.type);
return;
}
document.title = originalTitle;
updateFavicon('default');
}, [currentPath, viewsMap, originalTitle]);
const onUpdateView = useCallback((viewId, update, successCallback, failCallback) => {
updateView(viewId, update, () => {
setState(n => n + 1);

View File

@@ -0,0 +1,28 @@
import { VIEW_TYPE } from '../constants';
import { mediaUrl } from '../../utils/constants';
export const updateFavicon = (type) => {
const favicon = document.getElementById('favicon');
if (favicon) {
switch (type) {
case VIEW_TYPE.GALLERY:
case 'image':
favicon.href = `${mediaUrl}favicons/gallery.png`;
break;
case VIEW_TYPE.TABLE:
favicon.href = `${mediaUrl}favicons/table.png`;
break;
case VIEW_TYPE.FACE_RECOGNITION:
favicon.href = `${mediaUrl}favicons/face-recognition-view.png`;
break;
case VIEW_TYPE.KANBAN:
favicon.href = `${mediaUrl}favicons/kanban.png`;
break;
case VIEW_TYPE.MAP:
favicon.href = `${mediaUrl}favicons/map.png`;
break;
default:
favicon.href = `${mediaUrl}favicons/favicon.png`;
}
}
};

View File

@@ -2197,8 +2197,8 @@ class LibContentView extends React.Component {
return (
<MetadataStatusProvider repoID={repoID} currentRepoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView}>
<TagsProvider repoID={repoID} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick}>
<MetadataProvider repoID={repoID} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} hideMetadataView={this.hideMetadataView} >
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick}>
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} hideMetadataView={this.hideMetadataView} >
<CollaboratorsProvider repoID={repoID}>
<div className="main-panel-center flex-row">
<div className="cur-view-container">

View File

@@ -1,22 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTags } from '../hooks';
import { getRowById } from '../../metadata/utils/table';
import { getTagName } from '../utils';
import { TAG_MANAGEMENT_ID } from '../constants';
import { gettext } from '../../utils/constants';
const TagViewName = ({ id }) => {
const { tagsData } = useTags();
if (!id) return null;
if (id === TAG_MANAGEMENT_ID) return gettext('Tags management');
const tag = getRowById(tagsData, id);
if (!tag) return null;
return (<>{getTagName(tag)}</>);
};
TagViewName.propTypes = {
id: PropTypes.string,
};
export default TagViewName;

View File

@@ -0,0 +1,76 @@
import React, { useCallback, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { isEnter, isSpace } from '../../../metadata/utils/hotkey';
import { gettext } from '../../../utils/constants';
import { useTags } from '../../hooks';
import EditTagDialog from '../dialog/edit-tag-dialog';
const AllTagsOperationToolbar = ({ children }) => {
const [isMenuOpen, setMenuOpen] = useState(false);
const [isShowEditTagDialog, setShowEditTagDialog] = useState(false);
const { tagsData, addTag } = useTags();
const tags = useMemo(() => {
if (!tagsData) return [];
return tagsData.rows;
}, [tagsData]);
const toggleMenuOpen = useCallback(() => {
setMenuOpen(!isMenuOpen);
}, [isMenuOpen]);
const onDropdownKeyDown = useCallback((event) => {
if (isEnter(event) || isSpace(event)) {
toggleMenuOpen();
}
}, [toggleMenuOpen]);
const openAddTag = useCallback(() => {
setShowEditTagDialog(true);
}, []);
const closeAddTag = useCallback(() => {
setShowEditTagDialog(false);
}, []);
const handelAddTags = useCallback((tag, callback) => {
addTag(tag, callback);
}, [addTag]);
return (
<>
<div className="dir-operation">
<Dropdown isOpen={isMenuOpen} toggle={toggleMenuOpen}>
<DropdownToggle
tag="div"
role="button"
className="path-item"
onClick={toggleMenuOpen}
onKeyDown={onDropdownKeyDown}
data-toggle="dropdown"
>
{children}
<i className="sf3-font-down sf3-font ml-1 path-item-dropdown-toggle"></i>
</DropdownToggle>
<DropdownMenu positionFixed={true}>
<DropdownItem onClick={openAddTag}>
<i className="sf3-font sf3-font-new mr-2 dropdown-item-icon"></i>
{gettext('New tag')}
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
{isShowEditTagDialog && (
<EditTagDialog tags={tags} title={gettext('New tag')} onToggle={closeAddTag} onSubmit={handelAddTags} />
)}
</>
);
};
AllTagsOperationToolbar.propTypes = {
children: PropTypes.node,
};
export default AllTagsOperationToolbar;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTags } from '../../hooks';
import { getRowById } from '../../../metadata/utils/table';
import { getTagName } from '../../utils';
import { ALL_TAGS_ID } from '../../constants';
import { gettext } from '../../../utils/constants';
import AllTagsOperationToolbar from './all-tags-operation-toolbar';
const TagViewName = ({ id }) => {
const { tagsData, context } = useTags();
if (!id) return null;
if (id === ALL_TAGS_ID) {
const canModify = context.canModify();
if (!canModify) return (<span className="path-item">{gettext('All tags')}</span>);
const canAddTag = context.canAddTag();
if (!canAddTag) return (<span className="path-item">{gettext('All tags')}</span>);
return (<AllTagsOperationToolbar>{gettext('All tags')}</AllTagsOperationToolbar>);
}
const tag = getRowById(tagsData, id);
if (!tag) return null;
return (<span className="path-item">{getTagName(tag)}</span>);
};
TagViewName.propTypes = {
id: PropTypes.string,
};
export default TagViewName;

View File

@@ -1 +1 @@
export const TAG_MANAGEMENT_ID = '__tag_management';
export const ALL_TAGS_ID = '__all_tags';

View File

@@ -3,25 +3,26 @@ import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import { useMetadataStatus } from '../../hooks';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { getTagColor, getTagId, getTagName, getCellValueByColumn } from '../utils/cell/core';
import { getTagColor, getTagId, getTagName, getCellValueByColumn, updateFavicon } from '../utils';
import Context from '../context';
import Store from '../store';
import { PER_LOAD_NUMBER, EVENT_BUS_TYPE } from '../../metadata/constants';
import { getRowById } from '../../metadata/utils/table';
import { gettext } from '../../utils/constants';
import { PRIVATE_COLUMN_KEY } from '../constants';
import { PRIVATE_COLUMN_KEY, ALL_TAGS_ID } from '../constants';
import { getColumnOriginName } from '../../metadata/utils/column';
// This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc.
const TagsContext = React.createContext(null);
export const TagsProvider = ({ repoID, selectTagsView, children, ...params }) => {
export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ...params }) => {
const [isLoading, setLoading] = useState(true);
const [tagsData, setTagsData] = useState(null);
const storeRef = useRef(null);
const contextRef = useRef(null);
const originalTitleRef = useRef(document.title);
const { enableMetadata, enableTags } = useMetadataStatus();
@@ -179,6 +180,48 @@ export const TagsProvider = ({ repoID, selectTagsView, children, ...params }) =>
modifyLocalTags(tagIds, idTagUpdates, { [tagId]: originalRowUpdates }, { [tagId]: oldRowData }, { [tagId]: originalOldRowData }, { success_callback, fail_callback });
}, [tagsData, modifyLocalTags]);
useEffect(() => {
if (isLoading) return;
const { search } = window.location;
const urlParams = new URLSearchParams(search);
if (!urlParams.has('tag')) return;
const tagId = urlParams.get('tag');
if (tagId) {
if (tagId === ALL_TAGS_ID) {
handelSelectTag({ [PRIVATE_COLUMN_KEY.ID]: ALL_TAGS_ID });
return;
}
const lastOpenedTag = getRowById(tagsData, tagId);
if (lastOpenedTag) {
handelSelectTag(lastOpenedTag);
return;
}
handelSelectTag({ [PRIVATE_COLUMN_KEY.ID]: ALL_TAGS_ID });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading]);
useEffect(() => {
if (!currentPath.includes('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/')) return;
const currentTagId = currentPath.split('/').pop();
if (currentTagId === ALL_TAGS_ID) {
document.title = `${gettext('All tags')} - Seafile`;
return;
}
const currentTag = getRowById(tagsData, currentTagId);
if (currentTag) {
const tagName = getTagName(currentTag);
document.title = `${tagName} - Seafile`;
updateFavicon('default');
return;
}
document.title = originalTitleRef.current;
updateFavicon('default');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPath, tagsData]);
return (
<TagsContext.Provider value={{
isLoading,

View File

@@ -1,12 +1,12 @@
.tag-management-tree-node-inner:hover {
.all-tags-tree-node-inner:hover {
background-color: #f0f0f0;
border-radius: 0.25rem;
}
.tag-management-tree-node-inner .sf3-font-tag {
.all-tags-tree-node-inner .sf3-font-tag {
font-size: 12px;
color: #666666;
line-height: 1.625;
line-height: 1.5;
width: 1.5rem;
display: flex;
justify-content: center;

View File

@@ -2,30 +2,30 @@ import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { PRIVATE_FILE_TYPE } from '../../../constants';
import { PRIVATE_COLUMN_KEY, TAG_MANAGEMENT_ID } from '../../constants';
import { PRIVATE_COLUMN_KEY, ALL_TAGS_ID } from '../../constants';
import { useTags } from '../../hooks';
import { gettext } from '../../../utils/constants';
import './index.css';
const TagsManagement = ({ currentPath }) => {
const AllTags = ({ currentPath }) => {
const { selectTag } = useTags();
const path = useMemo(() => '/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/' + TAG_MANAGEMENT_ID, []);
const path = useMemo(() => '/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/' + ALL_TAGS_ID, []);
const isSelected = useMemo(() => currentPath === path, [currentPath, path]);
const selectTagManagement = useCallback(() => {
const handelClick = useCallback(() => {
selectTag({
[PRIVATE_COLUMN_KEY.ID]: TAG_MANAGEMENT_ID,
[PRIVATE_COLUMN_KEY.ID]: ALL_TAGS_ID,
}, isSelected);
}, [isSelected, selectTag]);
return (
<div
className={classnames('tree-node-inner text-nowrap tag-management-tree-node-inner', { 'tree-node-hight-light': isSelected })}
onClick={selectTagManagement}
className={classnames('tree-node-inner text-nowrap all-tags-tree-node-inner', { 'tree-node-hight-light': isSelected })}
onClick={handelClick}
>
<div className="tree-node-text">{gettext('Tags management')}</div>
<div className="tree-node-text">{gettext('All tags')}</div>
<div className="left-icon">
<div className="tree-node-icon">
<i className="sf3-font sf3-font-tag"></i>
@@ -35,8 +35,8 @@ const TagsManagement = ({ currentPath }) => {
);
};
TagsManagement.propTypes = {
AllTags.propTypes = {
currentPath: PropTypes.string.isRequired,
};
export default TagsManagement;
export default AllTags;

View File

@@ -1,24 +1,12 @@
import React, { useEffect, useMemo, useRef } from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { useTags } from '../hooks';
import Tag from './tag';
import { getTagId, getTagName } from '../utils';
import { getTagId } from '../utils';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { gettext, mediaUrl } from '../../utils/constants';
import { getRowById } from '../../metadata/utils/table';
import TagsManagement from './tags-management';
import { PRIVATE_COLUMN_KEY, TAG_MANAGEMENT_ID } from '../constants';
const updateFavicon = () => {
const favicon = document.getElementById('favicon');
if (favicon) {
favicon.href = `${mediaUrl}favicons/favicon.png`;
}
};
const TagsTreeView = ({ userPerm, currentPath }) => {
const originalTitle = useRef('');
import AllTags from './all-tags';
const TagsTreeView = ({ currentPath }) => {
const { tagsData, selectTag } = useTags();
const tags = useMemo(() => {
@@ -26,67 +14,11 @@ const TagsTreeView = ({ userPerm, currentPath }) => {
return tagsData.rows;
}, [tagsData]);
const canUpdate = useMemo(() => {
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
return true;
}, [userPerm]);
useEffect(() => {
originalTitle.current = document.title;
}, []);
useEffect(() => {
const { origin, pathname, search } = window.location;
const urlParams = new URLSearchParams(search);
const tagId = urlParams.get('tag');
if (tagId) {
if (tagId === TAG_MANAGEMENT_ID) {
if (!canUpdate) return;
selectTag({ [PRIVATE_COLUMN_KEY.ID]: TAG_MANAGEMENT_ID });
return;
}
const lastOpenedTag = getRowById(tagsData, tagId);
if (lastOpenedTag) {
selectTag(lastOpenedTag);
const lastOpenedTagName = getTagName(lastOpenedTag);
document.title = `${lastOpenedTagName} - Seafile`;
updateFavicon();
return;
}
const url = `${origin}${pathname}`;
window.history.pushState({ url: url, path: '' }, '', url);
}
updateFavicon();
document.title = originalTitle.current;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!currentPath.includes('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/')) return;
const currentTagId = currentPath.split('/').pop();
if (currentTagId === TAG_MANAGEMENT_ID) {
if (!canUpdate) return;
document.title = `${gettext('Tags management')} - Seafile`;
return;
}
const currentTag = getRowById(tagsData, currentTagId);
if (currentTag) {
const tagName = getTagName(currentTag);
document.title = `${tagName} - Seafile`;
updateFavicon('default');
return;
}
document.title = originalTitle;
updateFavicon('default');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPath, tagsData]);
return (
<div className="tree-view tree metadata-tree-view">
<div className="tree-node">
<div className="children">
{tags.map(tag => {
{tags.slice(0, 20).map(tag => {
const id = getTagId(tag);
const tagPath = '/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/' + id;
const isSelected = currentPath === tagPath;
@@ -99,16 +31,14 @@ const TagsTreeView = ({ userPerm, currentPath }) => {
/>
);
})}
{canUpdate && (<TagsManagement currentPath={currentPath} />)}
<AllTags currentPath={currentPath} />
</div>
</div>
</div>
);
};
TagsTreeView.propTypes = {
userPerm: PropTypes.string,
currentPath: PropTypes.string,
};

View File

@@ -0,0 +1,8 @@
import { mediaUrl } from '../../utils/constants';
export const updateFavicon = () => {
const favicon = document.getElementById('favicon');
if (favicon) {
favicon.href = `${mediaUrl}favicons/favicon.png`;
}
};

View File

@@ -1,2 +1,3 @@
export * from './cell';
export * from './validate';
export * from './favicon';

View File

@@ -0,0 +1,19 @@
.sf-metadata-tags-wrapper {
height: 100%;
width: 100%;
overflow: hidden;
}
.sf-metadata-tags-wrapper .sf-metadata-tags-main {
height: 100%;
width: 100%;
overflow: hidden;
}
.sf-metadata-all-tags-container {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
width: 100%;
}

View File

@@ -0,0 +1,38 @@
import React, { useEffect, useMemo } from 'react';
import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
import { useTags } from '../../hooks';
import Main from './main';
import { EVENT_BUS_TYPE } from '../../../metadata/constants';
import './index.css';
const AllTags = () => {
const { isLoading, tagsData, context } = useTags();
useEffect(() => {
const eventBus = context.eventBus;
eventBus.dispatch(EVENT_BUS_TYPE.RELOAD_DATA);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const tags = useMemo(() => {
if (!tagsData) return [];
return tagsData.rows;
}, [tagsData]);
if (isLoading) return (<CenteredLoading />);
return (
<>
<div className="sf-metadata-tags-wrapper sf-metadata-all-tags-wrapper">
<div className="sf-metadata-tags-main">
<div className="sf-metadata-all-tags-container">
<Main tags={tags} context={context} />
</div>
</div>
</div>
</>
);
};
export default AllTags;

View File

@@ -5,6 +5,7 @@
flex-direction: column;
overflow-y: scroll;
width: 100%;
padding: 0 16px 16px;
}
.sf-metadata-tags-table .sf-metadata-tags-table-header {

View File

@@ -19,7 +19,7 @@ const Main = ({ context, tags }) => {
return (
<div className="sf-metadata-tags-table">
<div className="sf-metadata-tags-table-header sf-metadata-tags-table-row">
<div className="sf-metadata-tags-table-cell">{gettext('tag')}</div>
<div className="sf-metadata-tags-table-cell">{gettext('Tag')}</div>
<div className="sf-metadata-tags-table-cell">{gettext('File count')}</div>
<div className="sf-metadata-tags-table-cell"></div>
</div>

View File

@@ -65,7 +65,7 @@ const Tag = ({ tags, tag, context }) => {
</div>
</div>
{isShowEditTagDialog && (
<EditTagDialog tags={tags} title={gettext('Add tag')} tag={tag} onToggle={closeEditTagDialog} onSubmit={handelEditTag} />
<EditTagDialog tags={tags} title={gettext('Edit tag')} tag={tag} onToggle={closeEditTagDialog} onSubmit={handelEditTag} />
)}
{isShowDeleteDialog && (
<DeleteConfirmDialog title={gettext('Delete tag')} content={tagName} onToggle={closeDeleteConfirmDialog} onSubmit={handelDelete} />

View File

@@ -1,12 +1,12 @@
import React from 'react';
import { TagViewProvider } from '../hooks';
import View from './view';
import TagsManagement from './tags-management';
import { TAG_MANAGEMENT_ID } from '../constants';
import AllTags from './all-tags';
import { ALL_TAGS_ID } from '../constants';
const Views = ({ ...params }) => {
if (params.tagID === TAG_MANAGEMENT_ID) {
return (<TagsManagement { ...params } />);
if (params.tagID === ALL_TAGS_ID) {
return (<AllTags { ...params } />);
}
return (

View File

@@ -1,37 +0,0 @@
.sf-metadata-tags-wrapper {
height: 100%;
width: 100%;
overflow: hidden;
}
.sf-metadata-tags-wrapper .sf-metadata-tags-main {
height: 100%;
width: 100%;
overflow: hidden;
}
.sf-metadata-tags-management-container {
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
width: 100%;
}
.sf-metadata-tags-management-container .sf-metadata-container-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 6px;
padding-top: 2px;
border-bottom: 1px solid #eaeaea;
}
.sf-metadata-tags-management-container .sf-metadata-container-header .sf-metadata-container-header-add-tag {
font-size: 12px;
font-weight: 400;
height: 26px;
padding-bottom: 0;
padding-top: 0;
}

View File

@@ -1,67 +0,0 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
import { Button } from 'reactstrap';
import { useTags } from '../../hooks';
import Main from './main';
import { gettext } from '../../../utils/constants';
import EditTagDialog from '../../components/dialog/edit-tag-dialog';
import './index.css';
import { EVENT_BUS_TYPE } from '../../../metadata/constants';
const TagsManagement = () => {
const [isShowEditTagDialog, setShowEditTagDialog] = useState(false);
const { isLoading, tagsData, addTag, context } = useTags();
useEffect(() => {
const eventBus = context.eventBus;
eventBus.dispatch(EVENT_BUS_TYPE.RELOAD_DATA);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const tags = useMemo(() => {
if (!tagsData) return [];
return tagsData.rows;
}, [tagsData]);
const openAddTag = useCallback(() => {
setShowEditTagDialog(true);
}, []);
const closeAddTag = useCallback(() => {
setShowEditTagDialog(false);
}, []);
const handelAddTags = useCallback((tag, callback) => {
addTag(tag, callback);
}, [addTag]);
if (isLoading) return (<CenteredLoading />);
return (
<>
<div className="sf-metadata-tags-wrapper sf-metadata-tags-management-wrapper">
<div className="sf-metadata-tags-main">
<div className="sf-metadata-tags-management-container">
<div className="sf-metadata-container-header">
<div className="sf-metadata-container-header-title">{gettext('Tags management')}</div>
<div className="sf-metadata-container-header-actions">
{context.canAddTag() && (
<Button color="primary" className="sf-metadata-container-header-add-tag" onClick={openAddTag}>
{gettext('New Tag')}
</Button>
)}
</div>
</div>
<Main tags={tags} context={context} />
</div>
</div>
</div>
{isShowEditTagDialog && (
<EditTagDialog tags={tags} title={gettext('Add tag')} onToggle={closeAddTag} onSubmit={handelAddTags} />
)}
</>
);
};
export default TagsManagement;