virtualized downloads table
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.23.0",
|
"react-router-dom": "^6.23.0",
|
||||||
|
"react-virtuoso": "^4.7.11",
|
||||||
"recoil": "^0.7.7",
|
"recoil": "^0.7.7",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
@@ -31,8 +32,8 @@
|
|||||||
"@types/react-helmet": "^6.1.11",
|
"@types/react-helmet": "^6.1.11",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||||
|
"million": "^3.0.6",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.4.3",
|
||||||
"vite": "^5.2.11",
|
"vite": "^5.2.11"
|
||||||
"million": "^3.0.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
frontend/pnpm-lock.yaml
generated
15
frontend/pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.23.0
|
specifier: ^6.23.0
|
||||||
version: 6.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 6.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
react-virtuoso:
|
||||||
|
specifier: ^4.7.11
|
||||||
|
version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
recoil:
|
recoil:
|
||||||
specifier: ^0.7.7
|
specifier: ^0.7.7
|
||||||
version: 0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -1013,6 +1016,13 @@ packages:
|
|||||||
react: '>=16.6.0'
|
react: '>=16.6.0'
|
||||||
react-dom: '>=16.6.0'
|
react-dom: '>=16.6.0'
|
||||||
|
|
||||||
|
react-virtuoso@4.7.11:
|
||||||
|
resolution: {integrity: sha512-Kdn9qEtQI2ulEuBMzW2BTkDsfijB05QUd6lpZ1K36oyA3k65Cz4lG4EKrh2pCfUafX4C2uMSZOwzMOhbrMOTFA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16 || >=17 || >= 18'
|
||||||
|
react-dom: '>=16 || >=17 || >= 18'
|
||||||
|
|
||||||
react@18.3.1:
|
react@18.3.1:
|
||||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2036,6 +2046,11 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
|
react-virtuoso@4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
react@18.3.1:
|
react@18.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|||||||
@@ -18,12 +18,61 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material"
|
} from "@mui/material"
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
import { TableComponents, TableVirtuoso } from 'react-virtuoso'
|
||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { activeDownloadsState } from '../atoms/downloads'
|
import { activeDownloadsState } from '../atoms/downloads'
|
||||||
import { serverURL } from '../atoms/settings'
|
import { serverURL } from '../atoms/settings'
|
||||||
import { useRPC } from '../hooks/useRPC'
|
import { useRPC } from '../hooks/useRPC'
|
||||||
|
import { RPCResult } from '../types'
|
||||||
import { base64URLEncode, formatSize, formatSpeedMiB } from "../utils"
|
import { base64URLEncode, formatSize, formatSpeedMiB } from "../utils"
|
||||||
|
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
width: 8,
|
||||||
|
label: 'Status',
|
||||||
|
dataKey: 'status',
|
||||||
|
numeric: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 500,
|
||||||
|
label: 'Title',
|
||||||
|
dataKey: 'title',
|
||||||
|
numeric: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 50,
|
||||||
|
label: 'Speed',
|
||||||
|
dataKey: 'speed',
|
||||||
|
numeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 150,
|
||||||
|
label: 'Progress',
|
||||||
|
dataKey: 'progress',
|
||||||
|
numeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 80,
|
||||||
|
label: 'Size',
|
||||||
|
dataKey: 'size',
|
||||||
|
numeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 100,
|
||||||
|
label: 'Added on',
|
||||||
|
dataKey: 'addedon',
|
||||||
|
numeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 80,
|
||||||
|
label: 'Actions',
|
||||||
|
dataKey: 'actions',
|
||||||
|
numeric: true,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
|
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
@@ -39,10 +88,40 @@ function LinearProgressWithLabel(props: LinearProgressProps & { value: number })
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DownloadsTableView: React.FC = () => {
|
const VirtuosoTableComponents: TableComponents<RPCResult> = {
|
||||||
const serverAddr = useRecoilValue(serverURL)
|
Scroller: forwardRef<HTMLDivElement>((props, ref) => (
|
||||||
const downloads = useRecoilValue(activeDownloadsState)
|
<TableContainer {...props} ref={ref} />
|
||||||
|
)),
|
||||||
|
Table: (props) => (
|
||||||
|
<Table {...props} sx={{ borderCollapse: 'separate', tableLayout: 'fixed', mt: 2 }} size='small' />
|
||||||
|
),
|
||||||
|
TableHead,
|
||||||
|
TableRow: ({ item: _item, ...props }) => <TableRow {...props} />,
|
||||||
|
TableBody: forwardRef<HTMLTableSectionElement>((props, ref) => (
|
||||||
|
<TableBody {...props} ref={ref} />
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixedHeaderContent() {
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell
|
||||||
|
key={column.dataKey}
|
||||||
|
variant="head"
|
||||||
|
align={column.numeric || false ? 'right' : 'left'}
|
||||||
|
style={{ width: column.width }}
|
||||||
|
>
|
||||||
|
{column.label}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DownloadsTableView: React.FC = () => {
|
||||||
|
const downloads = useRecoilValue(activeDownloadsState)
|
||||||
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
const { client } = useRPC()
|
const { client } = useRPC()
|
||||||
|
|
||||||
const abort = (id: string) => client.kill(id)
|
const abort = (id: string) => client.kill(id)
|
||||||
@@ -57,41 +136,9 @@ const DownloadsTableView: React.FC = () => {
|
|||||||
window.open(`${serverAddr}/archive/d/${encoded}?token=${localStorage.getItem('token')}`)
|
window.open(`${serverAddr}/archive/d/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rowContent(_index: number, download: RPCResult) {
|
||||||
return (
|
return (
|
||||||
<TableContainer
|
<>
|
||||||
sx={{ minHeight: '80vh', mt: 4 }}
|
|
||||||
hidden={downloads.length === 0}
|
|
||||||
>
|
|
||||||
<Table size="small">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell width={8}>
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Status</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Title</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Speed</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center" width={200}>
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Progress</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Size</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right" width={180}>
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Added on</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right" width={8}>
|
|
||||||
<Typography fontWeight={500} fontSize={13}>Actions</Typography>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
downloads.map(download => (
|
|
||||||
<TableRow key={download.id}>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{download.progress.percentage === '-1'
|
{download.progress.percentage === '-1'
|
||||||
? <DownloadDoneIcon color="primary" />
|
? <DownloadDoneIcon color="primary" />
|
||||||
@@ -147,12 +194,20 @@ const DownloadsTableView: React.FC = () => {
|
|||||||
}
|
}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</>
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
</TableBody>
|
|
||||||
</Table>
|
return (
|
||||||
</TableContainer>
|
<Box style={{ height: '80vh', width: '100%' }}>
|
||||||
|
<TableVirtuoso
|
||||||
|
hidden={downloads.length === 0}
|
||||||
|
data={downloads}
|
||||||
|
components={VirtuosoTableComponents}
|
||||||
|
fixedHeaderContent={fixedHeaderContent}
|
||||||
|
itemContent={rowContent}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user