feature: 为弹窗类型的路径选择器PathSelector添加排序功能 (#164)

* feat: extract component `Sort`

* feat: add sorting feature to `PathSelector`

* feat: replace sort action with compoent `Sort`

* feat: move `sortMethodFuncs` to module `Sort`
This commit is contained in:
Shawn Yuan 2023-10-07 19:41:35 +08:00 committed by GitHub
parent aabb964a8a
commit 9ccf148b31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 116 deletions

View File

@ -3,7 +3,6 @@ import { IconButton, makeStyles, Menu, MenuItem } from "@material-ui/core";
import ViewListIcon from "@material-ui/icons/ViewList";
import ViewSmallIcon from "@material-ui/icons/ViewComfy";
import ViewModuleIcon from "@material-ui/icons/ViewModule";
import TextTotateVerticalIcon from "@material-ui/icons/TextRotateVertical";
import DownloadIcon from "@material-ui/icons/CloudDownload";
import Avatar from "@material-ui/core/Avatar";
import { useDispatch, useSelector } from "react-redux";
@ -14,6 +13,7 @@ import { FormatPageBreak } from "mdi-material-ui";
import pathHelper from "../../../utils/page";
import { changePageSize } from "../../../redux/viewUpdate/action";
import { useTranslation } from "react-i18next";
import Sort from "../Sort";
const useStyles = makeStyles((theme) => ({
sideButton: {
@ -22,17 +22,6 @@ const useStyles = makeStyles((theme) => ({
},
}));
const sortOptions = [
"A-Z",
"Z-A",
"oldestUploaded",
"newestUploaded",
"oldestModified",
"newestModified",
"smallest",
"largest",
];
const paginationOption = ["50", "100", "200", "500", "1000"];
export default function SubActions({ isSmall, inherit }) {
@ -63,29 +52,14 @@ export default function SubActions({ isSmall, inherit }) {
const ChangePageSize = useCallback((e) => dispatch(changePageSize(e)), [
dispatch,
]);
const [anchorSort, setAnchorSort] = useState(null);
const [anchorPagination, setAnchorPagination] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(0);
const showSortOptions = (e) => {
setAnchorSort(e.currentTarget);
};
const showPaginationOptions = (e) => {
setAnchorPagination(e.currentTarget);
};
const handleMenuItemClick = (e, index) => {
setSelectedIndex(index);
const optionsTable = {
0: "namePos",
1: "nameRev",
2: "timePos",
3: "timeRev",
4: "modifyTimePos",
5: "modifyTimeRev",
6: "sizePos",
7: "sizeRes",
};
ChangeSortMethod(optionsTable[index]);
setAnchorSort(null);
/** change sort */
const onChangeSort = (value) => {
ChangeSortMethod(value);
};
const handlePaginationChange = (s) => {
ChangePageSize(s);
@ -181,32 +155,12 @@ export default function SubActions({ isSmall, inherit }) {
</MenuItem>
</Menu>
<IconButton
title={t("sortMethod")}
<Sort
isSmall={isSmall}
inherit={inherit}
className={classes.sideButton}
onClick={showSortOptions}
color={inherit ? "inherit" : "default"}
>
<TextTotateVerticalIcon
fontSize={isSmall ? "small" : "default"}
/>
</IconButton>
<Menu
id="sort-menu"
anchorEl={anchorSort}
open={Boolean(anchorSort)}
onClose={() => setAnchorSort(null)}
>
{sortOptions.map((option, index) => (
<MenuItem
key={option}
selected={index === selectedIndex}
onClick={(event) => handleMenuItemClick(event, index)}
>
{t("sortMethods." + option)}
</MenuItem>
))}
</Menu>
onChange={onChangeSort}
/>
{share && (
<IconButton
title={t("shareCreateBy", { nick: share.creator.nick })}

View File

@ -15,6 +15,7 @@ import {
MenuList,
withStyles,
} from "@material-ui/core";
import Sort, { sortMethodFuncs } from './Sort';
import API from "../../middleware/Api";
import { toggleSnackbar } from "../../redux/explorer";
import { withTranslation } from "react-i18next";
@ -53,14 +54,27 @@ const styles = (theme) => ({
maxHeight: "330px",
overflowY: " auto",
},
sortWrapper: {
textAlign: "right",
paddingRight: "30px",
},
sortButton: {
padding: "0",
},
});
class PathSelectorCompoment extends Component {
state = {
presentPath: "/",
sortBy: '',
dirList: [],
selectedTarget: null,
};
/**
* the source dir list from api `/directory`
*
* `state.dirList` is a sorted copy of it
*/
sourceDirList = []
componentDidMount = () => {
const toBeLoad = this.props.presentPath;
@ -93,31 +107,11 @@ class PathSelectorCompoment extends Component {
dirList.forEach((value) => {
value.displayName = value.name;
});
if (toBeLoad === "/") {
dirList.unshift({ name: "/", path: "", displayName: "/" });
} else {
let path = toBeLoad;
let name = toBeLoad;
const displayNames = ["fileManager.currentFolder", "fileManager.backToParentFolder"];
for (let i = 0; i < 2; i++) {
const paths = path.split("/");
name = paths.pop();
name = name === "" ? "/" : name;
path = paths.join("/");
dirList.unshift({
name: name,
path: path,
displayName: this.props.t(
displayNames[i]
),
});
}
}
this.sourceDirList = dirList
this.setState({
presentPath: toBeLoad,
dirList: dirList,
selectedTarget: null,
});
}, this.updateDirList);
})
.catch((error) => {
this.props.toggleSnackbar(
@ -134,6 +128,51 @@ class PathSelectorCompoment extends Component {
this.props.onSelect(this.state.dirList[index]);
};
/**
* change sort type
* @param {Event} event
*/
onChangeSort = (sortBy) => {
this.setState({ sortBy }, this.updateDirList)
};
/**
* sort dir list, and handle parent dirs
*/
updateDirList = () => {
const { state, sourceDirList } = this
const { sortBy, presentPath } = state
// copy
const dirList = [...sourceDirList]
// sort
const sortMethod = sortMethodFuncs[sortBy]
if (sortMethod) dirList.sort(sortMethod)
// add root/parent dirs to top
if (presentPath === "/") {
dirList.unshift({ name: "/", path: "", displayName: "/" });
} else {
let path = presentPath;
let name = presentPath;
const displayNames = ["fileManager.currentFolder", "fileManager.backToParentFolder"];
for (let i = 0; i < 2; i++) {
const paths = path.split("/");
name = paths.pop();
name = name === "" ? "/" : name;
path = paths.join("/");
dirList.unshift({
name: name,
path: path,
displayName: this.props.t(
displayNames[i]
),
});
}
}
this.setState({ dirList })
}
render() {
const { classes, t } = this.props;
@ -157,6 +196,9 @@ class PathSelectorCompoment extends Component {
return (
<div className={classes.container}>
<div className={classes.sortWrapper}>
<Sort value={this.state.sortBy} isSmall className={classes.sortButton} onChange={this.onChangeSort} />
</div>
<MenuList className={classes.selector}>
{this.state.dirList.map((value, index) => (
<MenuItem

View File

@ -0,0 +1,105 @@
import React, { MouseEventHandler, useState } from "react";
import { IconButton, Menu, MenuItem } from "@material-ui/core";
import TextTotateVerticalIcon from "@material-ui/icons/TextRotateVertical";
import { useTranslation } from "react-i18next";
import { CloudreveFile, SortMethod } from "./../../types/index";
const SORT_OPTIONS: {
value: SortMethod;
label: string;
}[] = [
{ value: "namePos", label: "A-Z" },
{ value: "nameRev", label: "Z-A" },
{ value: "timePos", label: "oldestUploaded" },
{ value: "timeRev", label: "newestUploaded" },
{ value: "modifyTimePos", label: "oldestModified" },
{ value: "modifyTimeRev", label: "newestModified" },
{ value: "sizePos", label: "smallest" },
{ value: "sizeRes", label: "largest" },
]
export default function Sort({ value, onChange, isSmall, inherit, className }) {
const { t } = useTranslation("application", { keyPrefix: "fileManager.sortMethods" });
const [anchorSort, setAnchorSort] = useState<Element | null>(null);
const showSortOptions: MouseEventHandler<HTMLButtonElement> = (e) => {
setAnchorSort(e.currentTarget);
}
const [sortBy, setSortBy] = useState<SortMethod>(value || '')
function onChangeSort(value: SortMethod) {
setSortBy(value)
onChange(value)
setAnchorSort(null);
}
return (
<>
<IconButton
title={t("sortMethod")}
className={className}
onClick={showSortOptions}
color={inherit ? "inherit" : "default"}
>
<TextTotateVerticalIcon
fontSize={isSmall ? "small" : "default"}
/>
</IconButton>
<Menu
id="sort-menu"
anchorEl={anchorSort}
open={Boolean(anchorSort)}
onClose={() => setAnchorSort(null)}
>
{
SORT_OPTIONS.map((option, index) => (
<MenuItem
key={index}
selected={option.value === sortBy}
onClick={() => onChangeSort(option.value)}
>
{t(option.label)}
</MenuItem>
))
}
</Menu>
</>
)
}
type SortFunc = (a: CloudreveFile, b: CloudreveFile) => number;
export const sortMethodFuncs: Record<SortMethod, SortFunc> = {
sizePos: (a: CloudreveFile, b: CloudreveFile) => {
return a.size - b.size;
},
sizeRes: (a: CloudreveFile, b: CloudreveFile) => {
return b.size - a.size;
},
namePos: (a: CloudreveFile, b: CloudreveFile) => {
return a.name.localeCompare(
b.name,
navigator.languages[0] || navigator.language,
{ numeric: true, ignorePunctuation: true }
);
},
nameRev: (a: CloudreveFile, b: CloudreveFile) => {
return b.name.localeCompare(
a.name,
navigator.languages[0] || navigator.language,
{ numeric: true, ignorePunctuation: true }
);
},
timePos: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(a.create_date) - Date.parse(b.create_date);
},
timeRev: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(b.create_date) - Date.parse(a.create_date);
},
modifyTimePos: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(a.date) - Date.parse(b.date);
},
modifyTimeRev: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(b.date) - Date.parse(a.date);
},
};

View File

@ -17,7 +17,7 @@ import { Launch, PlaylistPlay, Subtitles } from "@material-ui/icons";
import TextLoading from "../Placeholder/TextLoading";
import SelectMenu from "./SelectMenu";
import { getDownloadURL } from "../../services/file";
import { sortMethodFuncs } from "../../redux/explorer/action";
import { sortMethodFuncs } from "../FileManager/Sort";
import { useTranslation } from "react-i18next";
const Artplayer = React.lazy(() =>

View File

@ -33,6 +33,7 @@ import {
saveFileToFileSystemDirectory,
verifyFileSystemRWPermission,
} from "../../utils/filesystem";
import { sortMethodFuncs } from "../../component/FileManager/Sort";
export interface ActionSetFileList extends AnyAction {
type: "SET_FILE_LIST";
@ -113,42 +114,6 @@ export const setShiftSelectedIds = (shiftSelectedIds: any) => {
};
};
type SortFunc = (a: CloudreveFile, b: CloudreveFile) => number;
export const sortMethodFuncs: Record<SortMethod, SortFunc> = {
sizePos: (a: CloudreveFile, b: CloudreveFile) => {
return a.size - b.size;
},
sizeRes: (a: CloudreveFile, b: CloudreveFile) => {
return b.size - a.size;
},
namePos: (a: CloudreveFile, b: CloudreveFile) => {
return a.name.localeCompare(
b.name,
navigator.languages[0] || navigator.language,
{ numeric: true, ignorePunctuation: true }
);
},
nameRev: (a: CloudreveFile, b: CloudreveFile) => {
return b.name.localeCompare(
a.name,
navigator.languages[0] || navigator.language,
{ numeric: true, ignorePunctuation: true }
);
},
timePos: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(a.create_date) - Date.parse(b.create_date);
},
timeRev: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(b.create_date) - Date.parse(a.create_date);
},
modifyTimePos: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(a.date) - Date.parse(b.date);
},
modifyTimeRev: (a: CloudreveFile, b: CloudreveFile) => {
return Date.parse(b.date) - Date.parse(a.date);
},
};
export const selectAll = (): ThunkAction<any, any, any, any> => {
return (dispatch, getState): void => {
const state = getState();