mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
enhance remote download dashboard (#119)
* fix: aria2 task page navigation * feat: improve performance with virtual list * feat: hide remote download when disabled * Update aria2 file list style
This commit is contained in:
parent
99b6ee40bb
commit
22744e2f2f
|
|
@ -19,13 +19,13 @@ import Grid from "@material-ui/core/Grid";
|
|||
import withStyles from "@material-ui/core/styles/withStyles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { ExpandMore, HighlightOff } from "@material-ui/icons";
|
||||
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TimeAgo from "timeago-react";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
|
|
@ -34,7 +34,9 @@ import { hex2bin, sizeToString } from "../../utils";
|
|||
import TypeIcon from "../FileManager/TypeIcon";
|
||||
import SelectFileDialog from "../Modals/SelectFile";
|
||||
import { useHistory } from "react-router";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ExpansionPanel = withStyles({
|
||||
root: {
|
||||
maxWidth: "100%",
|
||||
|
|
@ -123,14 +125,26 @@ const useStyles = makeStyles((theme) => ({
|
|||
expanded: {
|
||||
transform: "rotate(180deg)",
|
||||
},
|
||||
subFile: {
|
||||
width: "100%",
|
||||
minWidth: 300,
|
||||
wordBreak: "break-all",
|
||||
},
|
||||
subFileName: {
|
||||
display: "flex",
|
||||
},
|
||||
subFileIcon: {
|
||||
marginRight: "20px",
|
||||
},
|
||||
subFileSize: {
|
||||
minWidth: 120,
|
||||
},
|
||||
subFilePercent: {
|
||||
minWidth: 105,
|
||||
},
|
||||
scroll: {
|
||||
overflowY: "auto",
|
||||
overflow: "auto",
|
||||
maxHeight: "300px",
|
||||
},
|
||||
action: {
|
||||
padding: theme.spacing(2),
|
||||
|
|
@ -325,6 +339,177 @@ export default function DownloadingCard(props) {
|
|||
});
|
||||
};
|
||||
|
||||
const subFileList = useMemo(() => {
|
||||
const processStyle = (value) => ({
|
||||
background:
|
||||
"linear-gradient(to right, " +
|
||||
(theme.palette.type ===
|
||||
"dark"
|
||||
? darken(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.4
|
||||
)
|
||||
: lighten(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.85
|
||||
)) +
|
||||
" 0%," +
|
||||
(theme.palette.type ===
|
||||
"dark"
|
||||
? darken(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.4
|
||||
)
|
||||
: lighten(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.85
|
||||
)) +
|
||||
" " +
|
||||
getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(0) +
|
||||
"%," +
|
||||
theme.palette.background
|
||||
.paper +
|
||||
" " +
|
||||
getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(0) +
|
||||
"%," +
|
||||
theme.palette.background
|
||||
.paper +
|
||||
" 100%)",
|
||||
});
|
||||
|
||||
const subFileCell = (value) => (
|
||||
<>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFile}
|
||||
>
|
||||
<Typography
|
||||
className={
|
||||
classes.subFileName
|
||||
}
|
||||
>
|
||||
<TypeIcon
|
||||
className={
|
||||
classes.subFileIcon
|
||||
}
|
||||
fileName={
|
||||
value.path
|
||||
}
|
||||
/>
|
||||
{value.path}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFileSize}
|
||||
>
|
||||
<Typography noWrap>
|
||||
{" "}
|
||||
{sizeToString(
|
||||
value.length
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFilePercent}
|
||||
>
|
||||
<Typography noWrap>
|
||||
{getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(2)}
|
||||
%
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip
|
||||
title={t(
|
||||
"deleteThisFile"
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
deleteFile(
|
||||
value.index
|
||||
)
|
||||
}
|
||||
disabled={loading}
|
||||
size={"small"}
|
||||
>
|
||||
<HighlightOff />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
|
||||
return activeFiles().length > 5 ? (
|
||||
<TableVirtuoso
|
||||
style={{ height: 43 * activeFiles().length }}
|
||||
className={classes.scroll}
|
||||
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 value = activeFiles()[index];
|
||||
return (
|
||||
<TableRow
|
||||
{...props}
|
||||
key={index}
|
||||
style={processStyle(value)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
data={activeFiles()}
|
||||
itemContent={(index, value) => (
|
||||
subFileCell(value)
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className={classes.scroll}>
|
||||
<Table size="small">
|
||||
<TableBody>
|
||||
{activeFiles().map((value) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={value.index}
|
||||
style={processStyle(value)}
|
||||
>
|
||||
{subFileCell(value)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
classes,
|
||||
theme,
|
||||
activeFiles,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<SelectFileDialog
|
||||
|
|
@ -400,135 +585,7 @@ export default function DownloadingCard(props) {
|
|||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Divider />
|
||||
{task.info.bittorrent.mode === "multi" && (
|
||||
<div className={classes.scroll}>
|
||||
<Table size="small">
|
||||
<TableBody>
|
||||
{activeFiles().map((value) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={value.index}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(to right, " +
|
||||
(theme.palette.type ===
|
||||
"dark"
|
||||
? darken(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.4
|
||||
)
|
||||
: lighten(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.85
|
||||
)) +
|
||||
" 0%," +
|
||||
(theme.palette.type ===
|
||||
"dark"
|
||||
? darken(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.4
|
||||
)
|
||||
: lighten(
|
||||
theme.palette
|
||||
.primary
|
||||
.main,
|
||||
0.85
|
||||
)) +
|
||||
" " +
|
||||
getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(0) +
|
||||
"%," +
|
||||
theme.palette.background
|
||||
.paper +
|
||||
" " +
|
||||
getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(0) +
|
||||
"%," +
|
||||
theme.palette.background
|
||||
.paper +
|
||||
" 100%)",
|
||||
}}
|
||||
>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography
|
||||
className={
|
||||
classes.subFileName
|
||||
}
|
||||
>
|
||||
<TypeIcon
|
||||
className={
|
||||
classes.subFileIcon
|
||||
}
|
||||
fileName={
|
||||
value.path
|
||||
}
|
||||
/>
|
||||
{value.path}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography noWrap>
|
||||
{" "}
|
||||
{sizeToString(
|
||||
value.length
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography noWrap>
|
||||
{getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(2)}
|
||||
%
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip
|
||||
title={t(
|
||||
"deleteThisFile"
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
deleteFile(
|
||||
value.index
|
||||
)
|
||||
}
|
||||
disabled={loading}
|
||||
size={"small"}
|
||||
>
|
||||
<HighlightOff />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{task.info.bittorrent.mode === "multi" && subFileList}
|
||||
<div className={classes.action}>
|
||||
<Button
|
||||
className={classes.actionButton}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -17,10 +17,10 @@ import withStyles from "@material-ui/core/styles/withStyles";
|
|||
import Divider from "@material-ui/core/Divider";
|
||||
import { ExpandMore } from "@material-ui/icons";
|
||||
import classNames from "classnames";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
|
@ -30,6 +30,7 @@ import { useDispatch } from "react-redux";
|
|||
import { useHistory } from "react-router";
|
||||
import { formatLocalTime } from "../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ExpansionPanel = withStyles({
|
||||
|
|
@ -121,14 +122,26 @@ const useStyles = makeStyles((theme) => ({
|
|||
expanded: {
|
||||
transform: "rotate(180deg)",
|
||||
},
|
||||
subFile: {
|
||||
width: "100%",
|
||||
minWidth: 300,
|
||||
wordBreak: "break-all",
|
||||
},
|
||||
subFileName: {
|
||||
display: "flex",
|
||||
},
|
||||
subFileIcon: {
|
||||
marginRight: "20px",
|
||||
},
|
||||
subFileSize: {
|
||||
minWidth: 115,
|
||||
},
|
||||
subFilePercent: {
|
||||
minWidth: 100,
|
||||
},
|
||||
scroll: {
|
||||
overflowY: "auto",
|
||||
overflow: "auto",
|
||||
maxHeight: "300px",
|
||||
},
|
||||
action: {
|
||||
padding: theme.spacing(2),
|
||||
|
|
@ -223,6 +236,91 @@ export default function FinishedCard(props) {
|
|||
}
|
||||
};
|
||||
|
||||
const subFileList = useMemo(() => {
|
||||
const subFileCell = (value) => (
|
||||
<>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFile}
|
||||
>
|
||||
<Typography
|
||||
className={
|
||||
classes.subFileName
|
||||
}
|
||||
>
|
||||
<TypeIcon
|
||||
className={
|
||||
classes.subFileIcon
|
||||
}
|
||||
fileName={
|
||||
value.path
|
||||
}
|
||||
/>
|
||||
{value.path}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFileSize}
|
||||
>
|
||||
<Typography noWrap>
|
||||
{" "}
|
||||
{sizeToString(
|
||||
value.length
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.subFilePercent}
|
||||
>
|
||||
<Typography noWrap>
|
||||
{getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(2)}
|
||||
%
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
|
||||
return activeFiles().length > 5 ? (
|
||||
<TableVirtuoso
|
||||
style={{ height: 57 * activeFiles().length }}
|
||||
className={classes.scroll}
|
||||
components={{
|
||||
// eslint-disable-next-line react/display-name
|
||||
Table: (props) => <Table {...props} />,
|
||||
}}
|
||||
data={activeFiles()}
|
||||
itemContent={(index, value) => (
|
||||
subFileCell(value)
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className={classes.scroll}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{activeFiles().map((value) => {
|
||||
return (
|
||||
<TableRow key={value.index}>
|
||||
{subFileCell(value)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
classes,
|
||||
activeFiles,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<ExpansionPanel
|
||||
|
|
@ -328,64 +426,7 @@ export default function FinishedCard(props) {
|
|||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Divider />
|
||||
{props.task.files.length > 1 && (
|
||||
<div className={classes.scroll}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{activeFiles().map((value) => {
|
||||
return (
|
||||
<TableRow key={value.index}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography
|
||||
className={
|
||||
classes.subFileName
|
||||
}
|
||||
>
|
||||
<TypeIcon
|
||||
className={
|
||||
classes.subFileIcon
|
||||
}
|
||||
fileName={
|
||||
value.path
|
||||
}
|
||||
/>
|
||||
{value.path}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography noWrap>
|
||||
{" "}
|
||||
{sizeToString(
|
||||
value.length
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
<Typography noWrap>
|
||||
{getPercent(
|
||||
value.completedLength,
|
||||
value.length
|
||||
).toFixed(2)}
|
||||
%
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.task.files.length > 1 && subFileList}
|
||||
<div className={classes.action}>
|
||||
<Button
|
||||
className={classes.actionButton}
|
||||
|
|
@ -393,7 +434,7 @@ export default function FinishedCard(props) {
|
|||
color="secondary"
|
||||
onClick={() =>
|
||||
history.push(
|
||||
"/#/home?path=" +
|
||||
"/home?path=" +
|
||||
encodeURIComponent(props.task.dst)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import FormGroup from "@material-ui/core/FormGroup";
|
|||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -33,6 +34,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
content: {
|
||||
padding: 0,
|
||||
},
|
||||
scroll: {
|
||||
maxHeight: "calc(100vh - 200px)",
|
||||
},
|
||||
}));
|
||||
|
||||
export default function SelectFileDialog(props) {
|
||||
|
|
@ -75,14 +79,18 @@ export default function SelectFileDialog(props) {
|
|||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("download.selectDownloadingFile")}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers={"paper"} className={classes.content}>
|
||||
{files.map((v, k) => {
|
||||
return (
|
||||
<MenuItem key={k}>
|
||||
<Virtuoso
|
||||
style={{ height: 54 * files.length }}
|
||||
className={classes.scroll}
|
||||
data={files}
|
||||
itemContent={(index, v) => (
|
||||
<MenuItem key={index}>
|
||||
<FormGroup row>
|
||||
<FormControlLabel
|
||||
control={
|
||||
|
|
@ -96,8 +104,8 @@ export default function SelectFileDialog(props) {
|
|||
/>
|
||||
</FormGroup>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>
|
||||
|
|
|
|||
|
|
@ -397,22 +397,24 @@ class NavbarCompoment extends Component {
|
|||
primary={t("navbar.myShare")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
key="离线下载"
|
||||
onClick={() =>
|
||||
this.props.history.push("/aria2?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DownloadIcon
|
||||
className={classes.iconFix}
|
||||
{user.group.allowRemoteDownload && (
|
||||
<ListItem
|
||||
button
|
||||
key="离线下载"
|
||||
onClick={() =>
|
||||
this.props.history.push("/aria2?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DownloadIcon
|
||||
className={classes.iconFix}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t("navbar.remoteDownload")}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t("navbar.remoteDownload")}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.group.webdav && (
|
||||
<ListItem
|
||||
button
|
||||
|
|
|
|||
Loading…
Reference in New Issue