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:
小白-白 2022-07-07 20:21:28 +08:00 committed by GitHub
parent 99b6ee40bb
commit 22744e2f2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 323 additions and 215 deletions

View File

@ -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}

View File

@ -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)
)
}

View File

@ -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}>

View File

@ -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