mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
feat: filter download file list (https://github.com/cloudreve/Cloudreve/issues/2295)
This commit is contained in:
parent
0680171f46
commit
57dded5b34
|
|
@ -622,7 +622,12 @@
|
|||
"copyLinkToClipboard": "Copy link to clipboard"
|
||||
},
|
||||
"download": {
|
||||
"noFilesFound": "No files found",
|
||||
"filterByName": "Filter by name",
|
||||
"selectAll": "Select all",
|
||||
"reverseSelect": "Reverse select",
|
||||
"cancelTaskConfirm": "Are you sure to cancel this download task?",
|
||||
"saveChanges": "Save changes",
|
||||
"failedToLoad": "Failed to load.",
|
||||
"active": "Active",
|
||||
"finished": "Finished",
|
||||
|
|
|
|||
|
|
@ -622,6 +622,10 @@
|
|||
"copyLinkToClipboard": "复制链接到剪切板"
|
||||
},
|
||||
"download": {
|
||||
"noFilesFound": "没有找到任何文件",
|
||||
"filterByName": "按名称过滤",
|
||||
"selectAll": "全选",
|
||||
"reverseSelect": "反选",
|
||||
"cancelTaskConfirm": "确定要取消此任务吗?",
|
||||
"saveChanges": "保存更改",
|
||||
"failedToLoad": "加载失败",
|
||||
|
|
|
|||
|
|
@ -622,6 +622,10 @@
|
|||
"copyLinkToClipboard": "復制連結到剪下板"
|
||||
},
|
||||
"download": {
|
||||
"noFilesFound": "沒有找到任何檔案",
|
||||
"filterByName": "按名稱過濾",
|
||||
"selectAll": "全選",
|
||||
"reverseSelect": "反選",
|
||||
"cancelTaskConfirm": "確定要取消此任務嗎?",
|
||||
"saveChanges": "儲存更改",
|
||||
"failedToLoad": "載入失敗",
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import {
|
|||
Box,
|
||||
Button,
|
||||
Grow,
|
||||
Stack,
|
||||
Table,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
|
@ -19,10 +21,7 @@ import { DownloadTaskFile, TaskSummary } from "../../../api/workflow.ts";
|
|||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import { confirmOperation } from "../../../redux/thunks/dialog.ts";
|
||||
import { fileBase, sizeToString } from "../../../util";
|
||||
import {
|
||||
StyledCheckbox,
|
||||
StyledTableContainerPaper,
|
||||
} from "../../Common/StyledComponents.tsx";
|
||||
import { StyledCheckbox, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx";
|
||||
import FileIcon from "../../FileManager/Explorer/FileIcon.tsx";
|
||||
import Dismiss from "../../Icons/Dismiss.tsx";
|
||||
import { getProgressColor } from "./TaskCard.tsx";
|
||||
|
|
@ -41,6 +40,7 @@ const DownloadFileList = ({ taskId, summary, downloading, readonly }: DownloadFi
|
|||
const [changeApplied, setChangeApplied] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [height, setHeight] = useState(33);
|
||||
const [filterText, setFilterText] = useState("");
|
||||
const files = summary?.props?.download?.files;
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
|
@ -51,6 +51,16 @@ const DownloadFileList = ({ taskId, summary, downloading, readonly }: DownloadFi
|
|||
return getProgressColor(theme);
|
||||
}, [theme]);
|
||||
|
||||
const filteredFiles = useMemo(() => {
|
||||
if (!files) {
|
||||
return [];
|
||||
}
|
||||
if (!filterText) {
|
||||
return files;
|
||||
}
|
||||
return files.filter((file) => file.name.toLowerCase().includes(filterText.toLowerCase()));
|
||||
}, [files, filterText]);
|
||||
|
||||
const setFileSelected = (value: DownloadTaskFile, selected: boolean) => {
|
||||
setSelectedMask((prev) => {
|
||||
return {
|
||||
|
|
@ -83,6 +93,25 @@ const DownloadFileList = ({ taskId, summary, downloading, readonly }: DownloadFi
|
|||
});
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
const newMask = { ...selectedMask };
|
||||
filteredFiles.forEach((file) => {
|
||||
newMask[file.index] = true;
|
||||
});
|
||||
setSelectedMask(newMask);
|
||||
setChangeApplied(false);
|
||||
};
|
||||
|
||||
const reverseSelect = () => {
|
||||
const newMask = { ...selectedMask };
|
||||
filteredFiles.forEach((file) => {
|
||||
const currentSelection = newMask[file.index] ?? file.selected;
|
||||
newMask[file.index] = !currentSelection;
|
||||
});
|
||||
setSelectedMask(newMask);
|
||||
setChangeApplied(false);
|
||||
};
|
||||
|
||||
const cancelTask = () => {
|
||||
dispatch(confirmOperation(t("download.cancelTaskConfirm"))).then(() => {
|
||||
setLoading(true);
|
||||
|
|
@ -102,79 +131,104 @@ const DownloadFileList = ({ taskId, summary, downloading, readonly }: DownloadFi
|
|||
return (
|
||||
<Box sx={{ py: readonly ? 0 : 2 }}>
|
||||
{files && (
|
||||
<TableContainer component={StyledTableContainerPaper}>
|
||||
<TableVirtuoso
|
||||
style={{
|
||||
height: height,
|
||||
overflow: "auto",
|
||||
maxHeight: 300,
|
||||
}}
|
||||
totalListHeightChanged={(h) => {
|
||||
setHeight(h + 0.5);
|
||||
}}
|
||||
components={{
|
||||
// eslint-disable-next-line react/display-name
|
||||
Table: (props) => <Table {...props} size={"small"} />,
|
||||
// eslint-disable-next-line react/display-name
|
||||
TableRow: (props) => {
|
||||
const index = props["data-index"];
|
||||
const percentage = (files[index]?.progress ?? 0) * 100;
|
||||
const progressBgColor = theme.palette.background.default;
|
||||
return (
|
||||
<TableRow
|
||||
{...props}
|
||||
key={index}
|
||||
sx={{
|
||||
background: `linear-gradient(to right, ${progressColor} 0%,${progressColor} ${percentage}%,${progressBgColor} ${percentage}%,${progressBgColor} 100%)`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
data={files}
|
||||
itemContent={(_index, value) => (
|
||||
<>
|
||||
<TableCell component="th" scope="row" sx={{ height: 33, minWidth: 50 }}>
|
||||
<StyledCheckbox
|
||||
onChange={(e) => {
|
||||
setFileSelected(value, e.target.checked);
|
||||
}}
|
||||
disabled={!downloading || readonly}
|
||||
disableRipple
|
||||
checked={selectedMask[value.index] ?? value.selected}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 300, width: "100%" }}>
|
||||
<Typography variant={"body2"} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<FileIcon
|
||||
sx={{ px: 0, py: 0, mr: 1, height: "20px" }}
|
||||
variant={"small"}
|
||||
iconProps={{
|
||||
fontSize: "small",
|
||||
}}
|
||||
file={{
|
||||
type: FileType.file,
|
||||
name: value.name,
|
||||
<>
|
||||
{!readonly && files.length > 20 && (
|
||||
<Stack direction="row" spacing={1} sx={{ mb: 1 }}>
|
||||
<TextField
|
||||
label={t("download.filterByName")}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
<Button variant="outlined" onClick={selectAll} disabled={loading || !downloading}>
|
||||
{t("download.selectAll")}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={reverseSelect} disabled={loading || !downloading}>
|
||||
{t("download.reverseSelect")}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
<TableContainer component={StyledTableContainerPaper}>
|
||||
<TableVirtuoso
|
||||
style={{
|
||||
height: height,
|
||||
overflow: "auto",
|
||||
maxHeight: 300,
|
||||
}}
|
||||
totalListHeightChanged={(h) => {
|
||||
setHeight(h + 0.5);
|
||||
}}
|
||||
components={{
|
||||
// eslint-disable-next-line react/display-name
|
||||
Table: (props) => <Table {...props} size={"small"} />,
|
||||
// eslint-disable-next-line react/display-name
|
||||
TableRow: (props) => {
|
||||
const index = props["data-index"];
|
||||
const percentage = (files[index]?.progress ?? 0) * 100;
|
||||
const progressBgColor = theme.palette.background.default;
|
||||
return (
|
||||
<TableRow
|
||||
{...props}
|
||||
key={index}
|
||||
sx={{
|
||||
background: `linear-gradient(to right, ${progressColor} 0%,${progressColor} ${percentage}%,${progressBgColor} ${percentage}%,${progressBgColor} 100%)`,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="inherit">{fileBase(value.name)}</Typography>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 120 }}>
|
||||
<Typography noWrap variant={"body2"}>
|
||||
{sizeToString(value.size)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 105 }}>
|
||||
<Typography noWrap variant={"body2"}>
|
||||
{((value.progress ?? 0) * 100).toFixed(2)} %
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
},
|
||||
}}
|
||||
data={filteredFiles}
|
||||
itemContent={(_index, value) => (
|
||||
<>
|
||||
<TableCell component="th" scope="row" sx={{ height: 33, minWidth: 50 }}>
|
||||
<StyledCheckbox
|
||||
onChange={(e) => {
|
||||
setFileSelected(value, e.target.checked);
|
||||
}}
|
||||
disabled={!downloading || readonly}
|
||||
disableRipple
|
||||
checked={selectedMask[value.index] ?? value.selected}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 300, width: "100%" }}>
|
||||
<Typography variant={"body2"} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<FileIcon
|
||||
sx={{ px: 0, py: 0, mr: 1, height: "20px" }}
|
||||
variant={"small"}
|
||||
iconProps={{
|
||||
fontSize: "small",
|
||||
}}
|
||||
file={{
|
||||
type: FileType.file,
|
||||
name: value.name,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="inherit">{fileBase(value.name)}</Typography>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 120 }}>
|
||||
<Typography noWrap variant={"body2"}>
|
||||
{sizeToString(value.size)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{ minWidth: 105 }}>
|
||||
<Typography noWrap variant={"body2"}>
|
||||
{((value.progress ?? 0) * 100).toFixed(2)} %
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
{filteredFiles.length === 0 && (
|
||||
<Typography variant="body2" color="text.secondary" align="center" sx={{ my: 1 }}>
|
||||
{t("download.noFilesFound")}
|
||||
</Typography>
|
||||
)}
|
||||
/>
|
||||
</TableContainer>
|
||||
</TableContainer>
|
||||
</>
|
||||
)}
|
||||
{downloading && !readonly && summary?.phase == "monitor" && (
|
||||
<Box
|
||||
|
|
|
|||
Loading…
Reference in New Issue