frontend/src/component/FileManager/Explorer.js
2022-05-08 10:53:05 +08:00

478 lines
17 KiB
JavaScript

import {
CircularProgress,
Grid,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from "@material-ui/core";
import TableSortLabel from "@material-ui/core/TableSortLabel";
import classNames from "classnames";
import React, { useCallback, useEffect, useMemo } from "react";
import { configure, GlobalHotKeys } from "react-hotkeys";
import explorer, {
changeContextMenu,
openRemoveDialog,
setSelectedTarget,
} from "../../redux/explorer";
import { isMac } from "../../utils";
import pathHelper from "../../utils/page";
import ContextMenu from "./ContextMenu";
import ImgPreivew from "./ImgPreview";
import ObjectIcon from "./ObjectIcon";
import Nothing from "../Placeholder/Nothing";
import { useDispatch, useSelector } from "react-redux";
import { useLocation } from "react-router";
import { usePagination } from "../../hooks/pagination";
import { makeStyles } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
paper: {
padding: theme.spacing(2),
textAlign: "center",
color: theme.palette.text.secondary,
margin: "10px",
},
root: {
padding: "10px",
[theme.breakpoints.up("sm")]: {
height: "calc(100vh - 113px)",
},
},
rootTable: {
padding: "0px",
backgroundColor: theme.palette.background.paper.white,
[theme.breakpoints.up("sm")]: {
height: "calc(100vh - 113px)",
},
},
typeHeader: {
margin: "10px 25px",
color: "#6b6b6b",
fontWeight: "500",
},
loading: {
justifyContent: "center",
display: "flex",
marginTop: "40px",
},
errorBox: {
padding: theme.spacing(4),
},
errorMsg: {
marginTop: "10px",
},
hideAuto: {
[theme.breakpoints.down("sm")]: {
display: "none",
},
},
flexFix: {
minWidth: 0,
},
upButton: {
marginLeft: "20px",
marginTop: "10px",
marginBottom: "10px",
},
clickAway: {
height: "100%",
width: "100%",
},
rootShare: {
height: "100%",
minHeight: 500,
},
visuallyHidden: {
border: 0,
clip: "rect(0 0 0 0)",
height: 1,
margin: -1,
overflow: "hidden",
padding: 0,
position: "absolute",
top: 20,
width: 1,
},
gridContainer: {
[theme.breakpoints.down("sm")]: {
gridTemplateColumns:
"repeat(auto-fill,minmax(180px,1fr))!important",
},
[theme.breakpoints.up("md")]: {
gridTemplateColumns:
"repeat(auto-fill,minmax(220px,1fr))!important",
},
display: "grid!important",
},
gridItem: {
flex: "1 1 220px!important",
},
}));
const keyMap = {
DELETE_FILE: "del",
SELECT_ALL_SHOWED: `${isMac() ? "command" : "ctrl"}+a`,
SELECT_ALL: `${isMac() ? "command" : "ctrl"}+shift+a`,
DESELECT_ALL: "esc",
};
export default function Explorer({ share }) {
const { t } = useTranslation("application", { keyPrefix: "fileManager" });
const location = useLocation();
const dispatch = useDispatch();
const selected = useSelector((state) => state.explorer.selected);
const search = useSelector((state) => state.explorer.search);
const loading = useSelector((state) => state.viewUpdate.navigatorLoading);
const path = useSelector((state) => state.navigator.path);
const sortMethod = useSelector((state) => state.viewUpdate.sortMethod);
const navigatorErrorMsg = useSelector(
(state) => state.viewUpdate.navigatorErrorMsg
);
const navigatorError = useSelector(
(state) => state.viewUpdate.navigatorError
);
const viewMethod = useSelector(
(state) => state.viewUpdate.explorerViewMethod
);
const OpenRemoveDialog = useCallback(() => dispatch(openRemoveDialog()), [
dispatch,
]);
const SetSelectedTarget = useCallback(
(targets) => dispatch(setSelectedTarget(targets)),
[dispatch]
);
const ChangeContextMenu = useCallback(
(type, open) => dispatch(changeContextMenu(type, open)),
[dispatch]
);
const ChangeSortMethod = useCallback(
(method) => dispatch(explorer.actions.changeSortMethod(method)),
[dispatch]
);
const SelectAll = useCallback(
() => dispatch(explorer.actions.selectAll()),
[dispatch]
);
const { dirList, fileList, startIndex } = usePagination();
const handlers = {
DELETE_FILE: () => {
if (selected.length > 0 && !share) {
OpenRemoveDialog();
}
},
SELECT_ALL_SHOWED: (e) => {
e.preventDefault();
if (selected.length >= dirList.length + fileList.length) {
SetSelectedTarget([]);
} else {
SetSelectedTarget([...dirList, ...fileList]);
}
},
SELECT_ALL: (e) => {
e.preventDefault();
SelectAll();
},
DESELECT_ALL: (e) => {
e.preventDefault();
SetSelectedTarget([]);
},
};
useEffect(
() =>
configure({
ignoreTags: ["input", "select", "textarea"],
}),
[]
);
const contextMenu = (e) => {
e.preventDefault();
if (!search && !pathHelper.isSharePage(location.pathname)) {
if (!loading) {
ChangeContextMenu("empty", true);
}
}
};
const ClickAway = (e) => {
const element = e.target;
if (element.dataset.clickaway) {
SetSelectedTarget([]);
}
};
const classes = useStyles();
const isHomePage = pathHelper.isHomePage(location.pathname);
const showView =
!loading && (dirList.length !== 0 || fileList.length !== 0);
const listView = useMemo(
() => (
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell>
<TableSortLabel
active={
sortMethod === "namePos" ||
sortMethod === "nameRev"
}
direction={
sortMethod === "namePos" ? "asc" : "des"
}
onClick={() => {
ChangeSortMethod(
sortMethod === "namePos"
? "nameRev"
: "namePos"
);
}}
>
{t("name")}
{sortMethod === "namePos" ||
sortMethod === "nameRev" ? (
<span className={classes.visuallyHidden}>
{sortMethod === "nameRev"
? "sorted descending"
: "sorted ascending"}
</span>
) : null}
</TableSortLabel>
</TableCell>
<TableCell className={classes.hideAuto}>
<TableSortLabel
active={
sortMethod === "sizePos" ||
sortMethod === "sizeRes"
}
direction={
sortMethod === "sizePos" ? "asc" : "des"
}
onClick={() => {
ChangeSortMethod(
sortMethod === "sizePos"
? "sizeRes"
: "sizePos"
);
}}
>
{t("size")}
{sortMethod === "sizePos" ||
sortMethod === "sizeRes" ? (
<span className={classes.visuallyHidden}>
{sortMethod === "sizeRes"
? "sorted descending"
: "sorted ascending"}
</span>
) : null}
</TableSortLabel>
</TableCell>
<TableCell className={classes.hideAuto}>
<TableSortLabel
active={
sortMethod === "modifyTimePos" ||
sortMethod === "modifyTimeRev"
}
direction={
sortMethod === "modifyTimePos"
? "asc"
: "des"
}
onClick={() => {
ChangeSortMethod(
sortMethod === "modifyTimePos"
? "modifyTimeRev"
: "modifyTimePos"
);
}}
>
{t("lastModified")}
{sortMethod === "modifyTimePos" ||
sortMethod === "modifyTimeRev" ? (
<span className={classes.visuallyHidden}>
{sortMethod === "sizeRes"
? "sorted descending"
: "sorted ascending"}
</span>
) : null}
</TableSortLabel>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{pathHelper.isMobile() && path !== "/" && (
<ObjectIcon
file={{
type: "up",
name: t("backToParentFolder"),
}}
/>
)}
{dirList.map((value, index) => (
<ObjectIcon
key={value.id}
file={value}
index={startIndex + index}
/>
))}
{fileList.map((value, index) => (
<ObjectIcon
key={value.id}
file={value}
index={startIndex + dirList.length + index}
/>
))}
</TableBody>
</Table>
),
[dirList, fileList, path, sortMethod, ChangeSortMethod, classes]
);
const normalView = useMemo(
() => (
<div className={classes.flexFix}>
{dirList.length !== 0 && (
<>
<Typography
data-clickAway={"true"}
variant="body2"
className={classes.typeHeader}
>
{t("folders")}
</Typography>
<Grid
data-clickAway={"true"}
container
spacing={0}
alignItems="flex-start"
className={classes.gridContainer}
>
{dirList.map((value, index) => (
<Grid
key={value.id}
item
className={classes.gridItem}
>
<ObjectIcon
key={value.id}
file={value}
index={startIndex + index}
/>
</Grid>
))}
</Grid>
</>
)}
{fileList.length !== 0 && (
<>
<Typography
data-clickAway={"true"}
variant="body2"
className={classes.typeHeader}
>
{t("files")}
</Typography>
<Grid
data-clickAway={"true"}
container
spacing={0}
alignItems="flex-start"
className={classes.gridContainer}
>
{fileList.map((value, index) => (
<Grid
className={classes.gridItem}
key={value.id}
item
>
<ObjectIcon
key={value.id}
index={
startIndex + dirList.length + index
}
file={value}
/>
</Grid>
))}
</Grid>
</>
)}
</div>
),
[dirList, fileList, classes]
);
const view = viewMethod === "list" ? listView : normalView;
return (
<div
onContextMenu={contextMenu}
onClick={ClickAway}
className={classNames(
{
[classes.root]: viewMethod !== "list",
[classes.rootTable]: viewMethod === "list",
[classes.rootShare]: share,
},
classes.button
)}
>
<GlobalHotKeys handlers={handlers} allowChanges keyMap={keyMap} />
<ContextMenu share={share} />
<ImgPreivew />
{navigatorError && (
<Paper elevation={1} className={classes.errorBox}>
<Typography variant="h5" component="h3">
{t("listError")}
</Typography>
<Typography
color={"textSecondary"}
className={classes.errorMsg}
>
{navigatorErrorMsg.message}
</Typography>
</Paper>
)}
{loading && !navigatorError && (
<div className={classes.loading}>
<CircularProgress />
</div>
)}
{!search &&
isHomePage &&
dirList.length === 0 &&
fileList.length === 0 &&
!loading &&
!navigatorError && (
<Nothing
primary={t("dropFileHere")}
secondary={t("orClickUploadButton")}
/>
)}
{((search &&
dirList.length === 0 &&
fileList.length === 0 &&
!loading &&
!navigatorError) ||
(dirList.length === 0 &&
fileList.length === 0 &&
!loading &&
!navigatorError &&
!isHomePage)) && <Nothing primary={t("nothingFound")} />}
{showView && view}
</div>
);
}