enable viewing results as listview
This commit is contained in:
@@ -4,11 +4,13 @@ import {
|
||||
Dashboard,
|
||||
// Download,
|
||||
Menu, Settings as SettingsIcon,
|
||||
FormatListBulleted,
|
||||
SettingsEthernet,
|
||||
Storage
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
createTheme, CssBaseline,
|
||||
Divider,
|
||||
IconButton, List,
|
||||
@@ -17,16 +19,16 @@ import {
|
||||
} from "@mui/material";
|
||||
import { grey } from "@mui/material/colors";
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useMemo, useState } from "react";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { lazy, Suspense, useMemo, useState } from "react";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
BrowserRouter as Router, Link, Route,
|
||||
Routes
|
||||
} from 'react-router-dom';
|
||||
import { AppBar } from "./components/AppBar";
|
||||
import { Drawer } from "./components/Drawer";
|
||||
import { toggleListView } from "./features/settings/settingsSlice";
|
||||
import Home from "./Home";
|
||||
import Settings from "./Settings";
|
||||
import { RootState, store } from './stores/store';
|
||||
import { formatGiB, getWebSocketEndpoint } from "./utils";
|
||||
|
||||
@@ -35,6 +37,7 @@ function AppContent() {
|
||||
|
||||
const settings = useSelector((state: RootState) => state.settings)
|
||||
const status = useSelector((state: RootState) => state.status)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const socket = useMemo(() => new WebSocket(getWebSocketEndpoint()), [])
|
||||
|
||||
@@ -54,6 +57,8 @@ function AppContent() {
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
const Settings = lazy(() => import('./Settings'))
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Router>
|
||||
@@ -132,6 +137,12 @@ function AppContent() {
|
||||
<ListItemText primary="Home" />
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
<ListItemButton onClick={() => dispatch(toggleListView())}>
|
||||
<ListItemIcon>
|
||||
<FormatListBulleted />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="List view" />
|
||||
</ListItemButton>
|
||||
<Link to={'/settings'} style={
|
||||
{
|
||||
textDecoration: 'none',
|
||||
@@ -158,7 +169,11 @@ function AppContent() {
|
||||
<Toolbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home socket={socket} />} />
|
||||
<Route path="/settings" element={<Settings socket={socket} />} />
|
||||
<Route path="/settings" element={
|
||||
<Suspense fallback={<CircularProgress />}>
|
||||
<Settings socket={socket} />
|
||||
</Suspense>
|
||||
} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -19,9 +19,10 @@ import {
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Buffer } from 'buffer';
|
||||
import { Fragment, useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { StackableResult } from "./components/StackableResult";
|
||||
import { DownloadsCardView } from "./components/DownloadsCardView";
|
||||
import { DownloadsListView } from "./components/DownloadsListView";
|
||||
import { CliArguments } from "./features/core/argsParser";
|
||||
import I18nBuilder from "./features/core/intl";
|
||||
import { RPCClient } from "./features/core/rpcClient";
|
||||
@@ -41,7 +42,7 @@ export default function Home({ socket }: Props) {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ephemeral state
|
||||
const [activeDownloads, setActiveDownloads] = useState(new Array<RPCResult>());
|
||||
const [activeDownloads, setActiveDownloads] = useState<Array<RPCResult>>();
|
||||
const [downloadFormats, setDownloadFormats] = useState<IDLMetadata>();
|
||||
const [pickedVideoFormat, setPickedVideoFormat] = useState('');
|
||||
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
||||
@@ -56,7 +57,7 @@ export default function Home({ socket }: Props) {
|
||||
const [url, setUrl] = useState('');
|
||||
const [workingUrl, setWorkingUrl] = useState('');
|
||||
|
||||
const [showBackdrop, setShowBackdrop] = useState(false);
|
||||
const [showBackdrop, setShowBackdrop] = useState(true);
|
||||
const [showToast, setShowToast] = useState(true);
|
||||
|
||||
// memos
|
||||
@@ -105,10 +106,10 @@ export default function Home({ socket }: Props) {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (activeDownloads.length > 0 && showBackdrop) {
|
||||
if (activeDownloads && activeDownloads.length >= 0) {
|
||||
setShowBackdrop(false)
|
||||
}
|
||||
}, [activeDownloads, showBackdrop])
|
||||
}, [activeDownloads?.length])
|
||||
|
||||
useEffect(() => {
|
||||
client.directoryTree()
|
||||
@@ -137,6 +138,7 @@ export default function Home({ socket }: Props) {
|
||||
|
||||
setUrl('')
|
||||
setWorkingUrl('')
|
||||
setShowBackdrop(true)
|
||||
|
||||
setTimeout(() => {
|
||||
resetInput()
|
||||
@@ -460,25 +462,11 @@ export default function Home({ socket }: Props) {
|
||||
</Grid>
|
||||
</Grid> : null
|
||||
}
|
||||
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
||||
{
|
||||
activeDownloads.map(download => (
|
||||
<Grid item xs={4} sm={8} md={6} key={download.id}>
|
||||
<Fragment>
|
||||
<StackableResult
|
||||
title={download.info.title}
|
||||
thumbnail={download.info.thumbnail}
|
||||
percentage={download.progress.percentage}
|
||||
stopCallback={() => abort(download.id)}
|
||||
resolution={download.info.resolution ?? ''}
|
||||
speed={download.progress.speed}
|
||||
size={download.info.filesize_approx ?? 0}
|
||||
/>
|
||||
</Fragment>
|
||||
</Grid>
|
||||
))
|
||||
settings.listView ?
|
||||
<DownloadsListView downloads={activeDownloads ?? []} abortFunction={abort} /> :
|
||||
<DownloadsCardView downloads={activeDownloads ?? []} abortFunction={abort} />
|
||||
}
|
||||
</Grid>
|
||||
<Snackbar
|
||||
open={showToast === status.connected}
|
||||
autoHideDuration={1500}
|
||||
|
||||
33
frontend/src/components/DownloadsCardView.tsx
Normal file
33
frontend/src/components/DownloadsCardView.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Grid } from "@mui/material"
|
||||
import { Fragment } from "react"
|
||||
import type { RPCResult } from "../types"
|
||||
import { StackableResult } from "./StackableResult"
|
||||
|
||||
type Props = {
|
||||
downloads: RPCResult[]
|
||||
abortFunction: Function
|
||||
}
|
||||
|
||||
export function DownloadsCardView({ downloads, abortFunction }: Props) {
|
||||
return (
|
||||
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
||||
{
|
||||
downloads.map(download => (
|
||||
<Grid item xs={4} sm={8} md={6} key={download.id}>
|
||||
<Fragment>
|
||||
<StackableResult
|
||||
title={download.info.title}
|
||||
thumbnail={download.info.thumbnail}
|
||||
percentage={download.progress.percentage}
|
||||
stopCallback={() => abortFunction(download.id)}
|
||||
resolution={download.info.resolution ?? ''}
|
||||
speed={download.progress.speed}
|
||||
size={download.info.filesize_approx ?? 0}
|
||||
/>
|
||||
</Fragment>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
70
frontend/src/components/DownloadsListView.tsx
Normal file
70
frontend/src/components/DownloadsListView.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Button, CircularProgress, Grid, LinearProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"
|
||||
import { RPCResult } from "../types"
|
||||
import { ellipsis, formatSpeedMiB, roundMiB } from "../utils"
|
||||
|
||||
type Props = {
|
||||
downloads: RPCResult[]
|
||||
abortFunction: Function
|
||||
}
|
||||
|
||||
export function DownloadsListView({ downloads, abortFunction }: Props) {
|
||||
return (
|
||||
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
||||
<Grid item xs={12}>
|
||||
<TableContainer component={Paper} sx={{ minHeight: '65vh' }} elevation={2}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography fontWeight={500} fontSize={15}>Title</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography fontWeight={500} fontSize={15}>Progress</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography fontWeight={500} fontSize={15}>Speed</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography fontWeight={500} fontSize={15}>Size</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography fontWeight={500} fontSize={15}>Actions</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{
|
||||
downloads.map(download => (
|
||||
<TableRow key={download.id}>
|
||||
<TableCell>{ellipsis(download.info.title, 80)}</TableCell>
|
||||
<TableCell>
|
||||
<LinearProgress
|
||||
value={
|
||||
download.progress.percentage === '-1' ? 100 :
|
||||
Number(download.progress.percentage.replace('%', ''))
|
||||
}
|
||||
variant="determinate"
|
||||
color={download.progress.percentage === '-1' ? 'success' : 'primary'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{formatSpeedMiB(download.progress.speed)}</TableCell>
|
||||
<TableCell>{roundMiB(download.info.filesize_approx ?? 0)}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={() => abortFunction(download.id)}
|
||||
>
|
||||
{download.progress.percentage === '-1' ? 'Remove' : 'Stop'}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ellipsis } from "../utils";
|
||||
import { ellipsis, formatSpeedMiB, roundMiB } from "../utils";
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
@@ -42,6 +42,8 @@ export function StackableResult({
|
||||
}
|
||||
}, [percentage])
|
||||
|
||||
const percentageToNumber = () => isCompleted ? 100 : Number(percentage.replace('%', ''))
|
||||
|
||||
const guessResolution = (xByY: string): any => {
|
||||
if (!xByY) return null;
|
||||
if (xByY.includes('4320')) return (<EightK color="primary" />);
|
||||
@@ -51,11 +53,6 @@ export function StackableResult({
|
||||
return null;
|
||||
}
|
||||
|
||||
const percentageToNumber = () => isCompleted ? 100 : Number(percentage.replace('%', ''))
|
||||
|
||||
const roundMiB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)} MiB`
|
||||
const formatSpeedMiB = (val: number) => `${roundMiB(val)}/s`
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardActionArea>
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface SettingsState {
|
||||
fileRenaming: boolean
|
||||
pathOverriding: boolean
|
||||
enableCustomArgs: boolean
|
||||
listView: boolean
|
||||
}
|
||||
|
||||
const initialState: SettingsState = {
|
||||
@@ -27,6 +28,7 @@ const initialState: SettingsState = {
|
||||
fileRenaming: localStorage.getItem("file-renaming") === "true",
|
||||
pathOverriding: localStorage.getItem("path-overriding") === "true",
|
||||
enableCustomArgs: localStorage.getItem("enable-custom-args") === "true",
|
||||
listView: localStorage.getItem("listview") === "true",
|
||||
}
|
||||
|
||||
export const settingsSlice = createSlice({
|
||||
@@ -73,6 +75,10 @@ export const settingsSlice = createSlice({
|
||||
state.enableCustomArgs = action.payload
|
||||
localStorage.setItem("enable-custom-args", action.payload.toString())
|
||||
},
|
||||
toggleListView: (state) => {
|
||||
state.listView = !state.listView
|
||||
localStorage.setItem("listview", state.listView.toString())
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -87,6 +93,7 @@ export const {
|
||||
setFileRenaming,
|
||||
setPathOverriding,
|
||||
setEnableCustomArgs,
|
||||
toggleListView
|
||||
} = settingsSlice.actions
|
||||
|
||||
export default settingsSlice.reducer
|
||||
@@ -84,3 +84,6 @@ export function getHttpRPCEndpoint() {
|
||||
export function formatGiB(bytes: number) {
|
||||
return `${(bytes / 1_000_000_000).toFixed(0)}GiB`
|
||||
}
|
||||
|
||||
export const roundMiB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)} MiB`
|
||||
export const formatSpeedMiB = (val: number) => `${roundMiB(val)}/s`
|
||||
Reference in New Issue
Block a user