diff --git a/frontend/src/components/cur-dir-path/dir-tool.js b/frontend/src/components/cur-dir-path/dir-tool.js index 7b0333d08c..a2dbf928db 100644 --- a/frontend/src/components/cur-dir-path/dir-tool.js +++ b/frontend/src/components/cur-dir-path/dir-tool.js @@ -12,7 +12,8 @@ import MetadataViewToolBar from '../../metadata/components/view-toolbar'; import TagsTableSearcher from '../../tag/views/all-tags/tags-table/tags-searcher'; import { PRIVATE_FILE_TYPE } from '../../constants'; import { ALL_TAGS_ID } from '../../tag/constants'; -import SortSetter from '../../tag/views/all-tags/tags-table/sort-setter'; +import AllTagsSortSetter from '../../tag/views/all-tags/tags-table/sort-setter'; +import TagFilesSortSetter from '../../tag/views/tag-files/sort-setter'; const propTypes = { repoID: PropTypes.string.isRequired, @@ -120,8 +121,8 @@ class DirTool extends React.Component { if (isTagView) { return (
- - {isAllTagsView && } + {isAllTagsView && } + {isAllTagsView ? : }
); } @@ -137,50 +138,50 @@ class DirTool extends React.Component { } {menuItems.length > 0 && - - - - - {menuItems.map((menuItem, index) => { - if (menuItem === 'Divider') { - return ; - } else { - return ( - {menuItem.value} - - ); - } - })} - - + + + + + {menuItems.map((menuItem, index) => { + if (menuItem === 'Divider') { + return ; + } else { + return ( + {menuItem.value} + + ); + } + })} + + } {this.state.isRepoTagDialogOpen && - - - + + + } ); diff --git a/frontend/src/metadata/components/cell-formatter/file-tags/index.js b/frontend/src/metadata/components/cell-formatter/file-tags/index.js index bff1e99bed..2f064c375b 100644 --- a/frontend/src/metadata/components/cell-formatter/file-tags/index.js +++ b/frontend/src/metadata/components/cell-formatter/file-tags/index.js @@ -20,7 +20,9 @@ const FileTagsFormatter = ({ tagsData, value: oldValue, className, children: emp const tag = getRowById(tagsData, item); const tagColor = getTagColor(tag); const tagName = getTagName(tag); - if (!showName) return ; + if (!showName) return ( + + ); return (
diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js index 8dda5ca3d1..2d977a37bb 100644 --- a/frontend/src/metadata/constants/event-bus-type.js +++ b/frontend/src/metadata/constants/event-bus-type.js @@ -98,6 +98,7 @@ export const EVENT_BUS_TYPE = { DELETE_TAG_FILES: 'delete_tag_files', SELECT_TAG_FILES: 'select_tag_files', UNSELECT_TAG_FILES: 'unselect_tag_files', + MODIFY_TAG_FILES_SORT: 'modify_tag_files_sort', // tags SELECT_TAGS: 'select_tags', diff --git a/frontend/src/tag/constants/sort.js b/frontend/src/tag/constants/sort.js index 64eb8ff106..43d4ecabb4 100644 --- a/frontend/src/tag/constants/sort.js +++ b/frontend/src/tag/constants/sort.js @@ -10,3 +10,16 @@ export const ALL_TAGS_SORT_KEY = { CHILD_TAGS_COUNT: 'child_tags_count', TAG_FILE_COUNT: 'tag_file_count' }; + +export const TAG_FILES_SORT = 'tag_files_sort'; + +export const TAG_FILES_DEFAULT_SORT = { + sort_by: 'name', + order: 'asc' +}; + +export const TAG_FILES_SORT_KEY = { + NAME: 'name', + SIZE: 'size', + TIME: 'time' +}; diff --git a/frontend/src/tag/hooks/tag-view.js b/frontend/src/tag/hooks/tag-view.js index e7edfaa22c..580569a3aa 100644 --- a/frontend/src/tag/hooks/tag-view.js +++ b/frontend/src/tag/hooks/tag-view.js @@ -9,12 +9,13 @@ import { getAllChildTagsIdsFromNode } from '../utils/tree'; import { seafileAPI } from '../../utils/seafile-api'; import { TAG_FILE_KEY } from '../constants/file'; import { EVENT_BUS_TYPE } from '../../metadata/constants'; -import { getFileById } from '../utils/file'; +import { getFileById, sortTagFiles } from '../utils/file'; import { getRowById } from '../../components/sf-table/utils/table'; import { getTagFilesLinks } from '../utils/cell'; import { PRIVATE_COLUMN_KEY } from '../constants'; import URLDecorator from '../../utils/url-decorator'; import { fileServerRoot, useGoFileserver } from '../../utils/constants'; +import { TAG_FILES_DEFAULT_SORT, TAG_FILES_SORT } from '../constants/sort'; // This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc. const TagViewContext = React.createContext(null); @@ -156,6 +157,14 @@ export const TagViewProvider = ({ }); }, [repoID, convertFileCallback]); + const sortFiles = useCallback((sort) => { + const sorted = sortTagFiles(tagFiles, sort); + setTagFiles({ + ...tagFiles, + rows: sorted, + }); + }, [tagFiles]); + useEffect(() => { setLoading(true); const childTagsIds = getChildTagsIds(tagID, nodeKey); @@ -165,7 +174,10 @@ export const TagViewProvider = ({ } tagsAPI.getTagsFiles(repoID, tagsIds).then(res => { const rows = res.data?.results || []; - setTagFiles({ columns: res.data?.metadata || [], rows }); + const savedSort = window.sfTagsDataContext?.localStorage?.getItem(TAG_FILES_SORT); + const sort = savedSort ? JSON.parse(savedSort) : TAG_FILES_DEFAULT_SORT; + const sorted = sortTagFiles(rows, sort); + setTagFiles({ columns: res.data?.metadata || [], rows: sorted }); setLoading(false); }).catch(error => { const errorMessage = Utils.getErrorMsg(error); @@ -175,6 +187,21 @@ export const TagViewProvider = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [repoID, tagID, nodeKey]); + useEffect(() => { + const unsubscribeModifyTagFilesSort = window.sfTagsDataContext?.eventBus?.subscribe(EVENT_BUS_TYPE.MODIFY_TAG_FILES_SORT, (sort) => { + const files = tagFiles?.rows || []; + const sorted = sortTagFiles(files, sort); + setTagFiles({ + ...tagFiles, + rows: sorted, + }); + }); + + return () => { + unsubscribeModifyTagFilesSort && unsubscribeModifyTagFilesSort(); + }; + }, [tagFiles]); + return ( {children} diff --git a/frontend/src/tag/utils/file.js b/frontend/src/tag/utils/file.js index 64c1591e0e..aa48b9fd6d 100644 --- a/frontend/src/tag/utils/file.js +++ b/frontend/src/tag/utils/file.js @@ -1,7 +1,10 @@ +import { compareString } from '../../metadata/utils/sort'; import { enableSeadoc, fileAuditEnabled, isPro } from '../../utils/constants'; import TextTranslation from '../../utils/text-translation'; import { Utils } from '../../utils/utils'; import { TAG_FILE_KEY } from '../constants/file'; +import { TAG_FILES_SORT_KEY } from '../constants/sort'; +import { getSortBy, getSortOrder } from './sort'; export const getFileById = (tagFiles, fileId) => { return fileId ? tagFiles.rows.find(file => file._id === fileId) : ''; @@ -15,6 +18,14 @@ export const getFileParentDir = (file) => { return file ? file[TAG_FILE_KEY.PARENT_DIR] : ''; }; +export const getFileMTime = (file) => { + return file ? file[TAG_FILE_KEY.FILE_MTIME] : ''; +}; + +export const getFileSize = (file) => { + return file ? file[TAG_FILE_KEY.SIZE] : ''; +}; + export const getTagFileOperationList = (fileName, repo, canModify) => { const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, CONVERT_AND_EXPORT, CONVERT_TO_MARKDOWN, CONVERT_TO_DOCX, EXPORT_DOCX, CONVERT_TO_SDOC, EXPORT_SDOC @@ -60,3 +71,38 @@ export const getTagFileOperationList = (fileName, repo, canModify) => { } return menuList; }; + +export const sortTagFiles = (files, sort) => { + const sortBy = getSortBy(sort); + const order = getSortOrder(sort); + + const compare = (a, b) => { + let valueA = ''; + let valueB = ''; + switch (sortBy) { + case TAG_FILES_SORT_KEY.NAME: + valueA = getFileName(a); + valueB = getFileName(b); + break; + case TAG_FILES_SORT_KEY.SIZE: + valueA = getFileSize(a); + valueB = getFileSize(b); + break; + case TAG_FILES_SORT_KEY.TIME: + valueA = getFileMTime(a); + valueB = getFileMTime(b); + break; + default: + break; + } + + const result = + sortBy === TAG_FILES_SORT_KEY.SIZE + ? valueA - valueB + : compareString(valueA, valueB); + + return order === 'asc' ? result : -result; + }; + + return files.sort(compare); +}; diff --git a/frontend/src/tag/views/tag-files/sort-setter.js b/frontend/src/tag/views/tag-files/sort-setter.js new file mode 100644 index 0000000000..e8881f5bd1 --- /dev/null +++ b/frontend/src/tag/views/tag-files/sort-setter.js @@ -0,0 +1,31 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import SortMenu from '../../../components/sort-menu'; +import { EVENT_BUS_TYPE } from '../../../metadata/constants'; +import { TAG_FILES_SORT, TAG_FILES_DEFAULT_SORT } from '../../constants/sort'; +import { getSortBy, getSortOrder } from '../../utils/sort'; + +const SortSetter = () => { + const [sort, setSort] = useState(TAG_FILES_DEFAULT_SORT); + + const eventBus = useMemo(() => window.sfTagsDataContext?.eventBus, []); + const localStorage = useMemo(() => window.sfTagsDataContext?.localStorage, []); + + const onSelectSortOption = useCallback((item) => { + const [sortBy, order] = item.value.split('-'); + const newSort = { sort_by: sortBy, order }; + setSort(newSort); + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_TAG_FILES_SORT, newSort); + }, [eventBus]); + + useEffect(() => { + const storedSort = localStorage && localStorage.getItem(TAG_FILES_SORT); + const sort = storedSort ? JSON.parse(storedSort) : TAG_FILES_DEFAULT_SORT; + setSort(sort); + }, [localStorage]); + + return ( + + ); +}; + +export default SortSetter;