1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-24 12:58:34 +00:00

sortable directory tree (#8069)

Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
Aries
2025-07-22 13:46:29 +08:00
committed by GitHub
parent cf22d27d4a
commit 37a422d1ef
7 changed files with 65 additions and 15 deletions

View File

@@ -29,7 +29,8 @@ const propTypes = {
getMenuContainerSize: PropTypes.func, getMenuContainerSize: PropTypes.func,
updateDirent: PropTypes.func, updateDirent: PropTypes.func,
updateTreeNode: PropTypes.func, updateTreeNode: PropTypes.func,
updateRepoInfo: PropTypes.func updateRepoInfo: PropTypes.func,
sortTreeNode: PropTypes.func,
}; };
class DirColumnNav extends React.Component { class DirColumnNav extends React.Component {
@@ -67,6 +68,7 @@ class DirColumnNav extends React.Component {
onItemsMove={this.props.onItemsMove} onItemsMove={this.props.onItemsMove}
updateDirent={this.props.updateDirent} updateDirent={this.props.updateDirent}
updateTreeNode={this.props.updateTreeNode} updateTreeNode={this.props.updateTreeNode}
sortTreeNode={this.props.sortTreeNode}
/> />
<DirViews repoID={repoID} currentPath={currentPath} userPerm={userPerm} currentRepoInfo={currentRepoInfo} /> <DirViews repoID={repoID} currentPath={currentPath} userPerm={userPerm} currentRepoInfo={currentRepoInfo} />
<DirTags repoID={repoID} currentPath={currentPath} userPerm={userPerm} currentRepoInfo={currentRepoInfo} /> <DirTags repoID={repoID} currentPath={currentPath} userPerm={userPerm} currentRepoInfo={currentRepoInfo} />

View File

@@ -79,7 +79,8 @@ const propTypes = {
updateCurrentPath: PropTypes.func, updateCurrentPath: PropTypes.func,
toggleShowDirentToolbar: PropTypes.func, toggleShowDirentToolbar: PropTypes.func,
updateTreeNode: PropTypes.func, updateTreeNode: PropTypes.func,
updateRepoInfo: PropTypes.func updateRepoInfo: PropTypes.func,
sortTreeNode: PropTypes.func,
}; };
class DirColumnView extends React.Component { class DirColumnView extends React.Component {
@@ -177,6 +178,7 @@ class DirColumnView extends React.Component {
direntList={this.props.direntList} direntList={this.props.direntList}
updateDirent={this.props.updateDirent} updateDirent={this.props.updateDirent}
updateTreeNode={this.props.updateTreeNode} updateTreeNode={this.props.updateTreeNode}
sortTreeNode={this.props.sortTreeNode}
/> />
<ResizeBar <ResizeBar
resizeBarRef={this.resizeBarRef} resizeBarRef={this.resizeBarRef}

View File

@@ -1,11 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cookie from 'react-cookies';
import TreeView from '../tree-view/tree-view'; import TreeView from '../tree-view/tree-view';
import ModalPortal from '../modal-portal'; import ModalPortal from '../modal-portal';
import ImageDialog from '../dialog/image-dialog'; import ImageDialog from '../dialog/image-dialog';
import toaster from '../toast'; import toaster from '../toast';
import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu';
import { fileServerRoot, gettext, siteRoot, thumbnailSizeForOriginal, thumbnailDefaultSize } from '../../utils/constants'; import { fileServerRoot, gettext, siteRoot, thumbnailSizeForOriginal, thumbnailDefaultSize, SF_DIRECTORY_TREE_SORT_BY_KEY, SF_DIRECTORY_TREE_SORT_ORDER_KEY } from '../../utils/constants';
import { isMobile, Utils } from '../../utils/utils'; import { isMobile, Utils } from '../../utils/utils';
import TextTranslation from '../../utils/text-translation'; import TextTranslation from '../../utils/text-translation';
import TreeSection from '../tree-section'; import TreeSection from '../tree-section';
@@ -31,6 +32,12 @@ const propTypes = {
onItemMove: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired,
onItemsMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired,
updateDirent: PropTypes.func, updateDirent: PropTypes.func,
sortTreeNode: PropTypes.func,
};
const SORT_KEY_MAP = {
'name-asc': TextTranslation.ASCENDING_BY_NAME.key,
'name-desc': TextTranslation.DESCENDING_BY_NAME.key,
}; };
class DirFiles extends React.Component { class DirFiles extends React.Component {
@@ -46,12 +53,20 @@ class DirFiles extends React.Component {
imageIndex: 0, imageIndex: 0,
operationList: [], operationList: [],
isDisplayFiles: localStorage.getItem('sf_display_files') === 'true' || false, isDisplayFiles: localStorage.getItem('sf_display_files') === 'true' || false,
sortKey: TextTranslation.ASCENDING_BY_NAME.key,
}; };
this.isNodeMenuShow = true; this.isNodeMenuShow = true;
this.imageItemsSnapshot = []; this.imageItemsSnapshot = [];
this.imageIndexSnapshot = 0; this.imageIndexSnapshot = 0;
} }
componentDidMount() {
const sortBy = cookie.load(SF_DIRECTORY_TREE_SORT_BY_KEY) || 'name';
const sortOrder = cookie.load(SF_DIRECTORY_TREE_SORT_ORDER_KEY) || 'asc';
const sortKey = SORT_KEY_MAP[`${sortBy}-${sortOrder}`];
this.setState({ sortKey });
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps.direntList.length < this.props.direntList.length && this.state.isNodeImagePopupOpen) { if (prevProps.direntList.length < this.props.direntList.length && this.state.isNodeImagePopupOpen) {
if (this.state.imageNodeItems.length === 0) { if (this.state.imageNodeItems.length === 0) {
@@ -73,6 +88,7 @@ class DirFiles extends React.Component {
getMenuList = () => { getMenuList = () => {
const { userPerm } = this.props; const { userPerm } = this.props;
const { sortKey } = this.state;
const list = []; const list = [];
if (userPerm == 'rw') { if (userPerm == 'rw') {
list.push( list.push(
@@ -80,7 +96,10 @@ class DirFiles extends React.Component {
TextTranslation.NEW_FILE TextTranslation.NEW_FILE
); );
} }
list.push(TextTranslation.DISPLAY_FILES); list.push({ ...TextTranslation.DISPLAY_FILES, tick: this.state.isDisplayFiles });
list.push('Divider');
list.push({ ...TextTranslation.ASCENDING_BY_NAME, tick: sortKey === TextTranslation.ASCENDING_BY_NAME.key });
list.push({ ...TextTranslation.DESCENDING_BY_NAME, tick: sortKey === TextTranslation.DESCENDING_BY_NAME.key });
return list; return list;
}; };
@@ -142,6 +161,16 @@ class DirFiles extends React.Component {
case 'Display files': case 'Display files':
this.onDisplayFilesToggle(); this.onDisplayFilesToggle();
break; break;
case 'Ascending by name': {
this.props.sortTreeNode('name', 'asc');
this.setState({ sortKey: TextTranslation.ASCENDING_BY_NAME.key });
break;
}
case 'Descending by name': {
this.props.sortTreeNode('name', 'desc');
this.setState({ sortKey: TextTranslation.DESCENDING_BY_NAME.key });
break;
}
} }
}; };
@@ -306,7 +335,6 @@ class DirFiles extends React.Component {
item={{ name: 'files' }} item={{ name: 'files' }}
toggleClass="sf3-font sf3-font-more" toggleClass="sf3-font sf3-font-more"
menuStyle={isMobile ? { zIndex: 1050 } : {}} menuStyle={isMobile ? { zIndex: 1050 } : {}}
isDisplayFiles={this.state.isDisplayFiles}
getMenuList={this.getMenuList} getMenuList={this.getMenuList}
onMenuItemClick={this.onMoreOperationClick} onMenuItemClick={this.onMoreOperationClick}
/> />

View File

@@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
import listener from '../context-menu/globalEventListener'; import listener from '../context-menu/globalEventListener';
import { gettext } from '../../utils/constants'; import { gettext } from '../../utils/constants';
@@ -20,7 +19,6 @@ const propTypes = {
freezeItem: PropTypes.func, freezeItem: PropTypes.func,
unfreezeItem: PropTypes.func, unfreezeItem: PropTypes.func,
menuStyle: PropTypes.object, menuStyle: PropTypes.object,
isDisplayFiles: PropTypes.bool,
}; };
class ItemDropdownMenu extends React.Component { class ItemDropdownMenu extends React.Component {
@@ -279,16 +277,13 @@ class ItemDropdownMenu extends React.Component {
return ( return (
<DropdownItem <DropdownItem
key={index} key={index}
className={classnames({ className="position-relative pl-5"
'pl-5': this.props.isDisplayFiles != undefined,
'position-relative': this.props.isDisplayFiles
})}
data-toggle={menuItem.key} data-toggle={menuItem.key}
onClick={this.onMenuItemClick} onClick={this.onMenuItemClick}
onKeyDown={this.onMenuItemKeyDown} onKeyDown={this.onMenuItemKeyDown}
onMouseMove={this.onDropDownMouseMove} onMouseMove={this.onDropDownMouseMove}
> >
{menuItem.key === 'Display files' && this.props.isDisplayFiles && ( {menuItem.tick && (
<i className="dropdown-item-tick sf2-icon-tick"></i> <i className="dropdown-item-tick sf2-icon-tick"></i>
)} )}
{menuItem.icon_dom || null} {menuItem.icon_dom || null}

View File

@@ -9,7 +9,7 @@ import { Modal } from 'reactstrap';
import { navigate } from '@gatsbyjs/reach-router'; import { navigate } from '@gatsbyjs/reach-router';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import { gettext, siteRoot, username } from '../../utils/constants'; import { gettext, SF_DIRECTORY_TREE_SORT_BY_KEY, SF_DIRECTORY_TREE_SORT_ORDER_KEY, siteRoot, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { Dirent, FileTag, RepoTag, RepoInfo } from '../../models'; import { Dirent, FileTag, RepoTag, RepoInfo } from '../../models';
@@ -2095,7 +2095,9 @@ class LibContentView extends React.Component {
let direntList = list.map(item => { let direntList = list.map(item => {
return new Dirent(item); return new Dirent(item);
}); });
direntList = Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder); const sortBy = cookie.load(SF_DIRECTORY_TREE_SORT_BY_KEY) || 'name';
const sortOrder = cookie.load(SF_DIRECTORY_TREE_SORT_ORDER_KEY) || 'asc';
direntList = Utils.sortDirents(direntList, sortBy, sortOrder);
let nodeList = direntList.map(object => { let nodeList = direntList.map(object => {
return new TreeNode({ object }); return new TreeNode({ object });
@@ -2186,11 +2188,18 @@ class LibContentView extends React.Component {
cookie.save('seafile-repo-dir-sort-order', sortOrder); cookie.save('seafile-repo-dir-sort-order', sortOrder);
const sortedDirentList = Utils.sortDirents(this.state.direntList, sortBy, sortOrder); const sortedDirentList = Utils.sortDirents(this.state.direntList, sortBy, sortOrder);
const sortedTreeData = treeHelper.sortTreeNodes(this.state.treeData, sortBy, sortOrder);
this.setState({ this.setState({
sortBy: sortBy, sortBy: sortBy,
sortOrder: sortOrder, sortOrder: sortOrder,
direntList: sortedDirentList, direntList: sortedDirentList,
});
};
sortTreeNode = (sortBy, sortOrder) => {
cookie.save(SF_DIRECTORY_TREE_SORT_BY_KEY, sortBy);
cookie.save(SF_DIRECTORY_TREE_SORT_ORDER_KEY, sortOrder);
const sortedTreeData = treeHelper.sortTreeNodes(this.state.treeData, sortBy, sortOrder);
this.setState({
treeData: sortedTreeData, treeData: sortedTreeData,
}); });
}; };
@@ -2551,6 +2560,7 @@ class LibContentView extends React.Component {
updateCurrentPath={this.updatePath} updateCurrentPath={this.updatePath}
toggleShowDirentToolbar={this.toggleShowDirentToolbar} toggleShowDirentToolbar={this.toggleShowDirentToolbar}
updateTreeNode={this.updateTreeNode} updateTreeNode={this.updateTreeNode}
sortTreeNode={this.sortTreeNode}
/> />
: :
<div className="message err-tip">{gettext('Folder does not exist.')}</div> <div className="message err-tip">{gettext('Folder does not exist.')}</div>

View File

@@ -233,3 +233,6 @@ export const MimetypesKind = {
export const LARGE_DIALOG_STYLE = { maxWidth: '980px' }; export const LARGE_DIALOG_STYLE = { maxWidth: '980px' };
export const SF_COLOR_MODE = 'sf_color_mode'; export const SF_COLOR_MODE = 'sf_color_mode';
export const SF_DIRECTORY_TREE_SORT_BY_KEY = 'sf_directory_tree_sort_by';
export const SF_DIRECTORY_TREE_SORT_ORDER_KEY = 'sf_directory_tree_sort_order';

View File

@@ -278,6 +278,16 @@ const TextTranslation = {
key: 'New child tag', key: 'New child tag',
value: gettext('New child tag'), value: gettext('New child tag'),
}, },
// directory op
ASCENDING_BY_NAME: {
key: 'Ascending by name',
value: gettext('Ascending by name'),
},
DESCENDING_BY_NAME: {
key: 'Descending by name',
value: gettext('Descending by name'),
},
}; };
export default TextTranslation; export default TextTranslation;