enable viewing results as listview

This commit is contained in:
2023-01-20 12:50:45 +01:00
parent 1f192f48f4
commit 0c737b2a3e
7 changed files with 149 additions and 36 deletions

View File

@@ -4,11 +4,13 @@ import {
Dashboard, Dashboard,
// Download, // Download,
Menu, Settings as SettingsIcon, Menu, Settings as SettingsIcon,
FormatListBulleted,
SettingsEthernet, SettingsEthernet,
Storage Storage
} from "@mui/icons-material"; } from "@mui/icons-material";
import { import {
Box, Box,
CircularProgress,
createTheme, CssBaseline, createTheme, CssBaseline,
Divider, Divider,
IconButton, List, IconButton, List,
@@ -17,16 +19,16 @@ import {
} from "@mui/material"; } from "@mui/material";
import { grey } from "@mui/material/colors"; import { grey } from "@mui/material/colors";
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
import { useMemo, useState } from "react"; import { lazy, Suspense, useMemo, useState } from "react";
import { Provider, useSelector } from "react-redux"; import { Provider, useDispatch, useSelector } from "react-redux";
import { import {
BrowserRouter as Router, Link, Route, BrowserRouter as Router, Link, Route,
Routes Routes
} from 'react-router-dom'; } from 'react-router-dom';
import { AppBar } from "./components/AppBar"; import { AppBar } from "./components/AppBar";
import { Drawer } from "./components/Drawer"; import { Drawer } from "./components/Drawer";
import { toggleListView } from "./features/settings/settingsSlice";
import Home from "./Home"; import Home from "./Home";
import Settings from "./Settings";
import { RootState, store } from './stores/store'; import { RootState, store } from './stores/store';
import { formatGiB, getWebSocketEndpoint } from "./utils"; import { formatGiB, getWebSocketEndpoint } from "./utils";
@@ -35,6 +37,7 @@ function AppContent() {
const settings = useSelector((state: RootState) => state.settings) const settings = useSelector((state: RootState) => state.settings)
const status = useSelector((state: RootState) => state.status) const status = useSelector((state: RootState) => state.status)
const dispatch = useDispatch()
const socket = useMemo(() => new WebSocket(getWebSocketEndpoint()), []) const socket = useMemo(() => new WebSocket(getWebSocketEndpoint()), [])
@@ -54,6 +57,8 @@ function AppContent() {
setOpen(!open) setOpen(!open)
} }
const Settings = lazy(() => import('./Settings'))
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Router> <Router>
@@ -132,6 +137,12 @@ function AppContent() {
<ListItemText primary="Home" /> <ListItemText primary="Home" />
</ListItemButton> </ListItemButton>
</Link> </Link>
<ListItemButton onClick={() => dispatch(toggleListView())}>
<ListItemIcon>
<FormatListBulleted />
</ListItemIcon>
<ListItemText primary="List view" />
</ListItemButton>
<Link to={'/settings'} style={ <Link to={'/settings'} style={
{ {
textDecoration: 'none', textDecoration: 'none',
@@ -158,7 +169,11 @@ function AppContent() {
<Toolbar /> <Toolbar />
<Routes> <Routes>
<Route path="/" element={<Home socket={socket} />} /> <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> </Routes>
</Box> </Box>
</Box> </Box>

View File

@@ -19,9 +19,10 @@ import {
Typography Typography
} from "@mui/material"; } from "@mui/material";
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import { Fragment, useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; 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 { CliArguments } from "./features/core/argsParser";
import I18nBuilder from "./features/core/intl"; import I18nBuilder from "./features/core/intl";
import { RPCClient } from "./features/core/rpcClient"; import { RPCClient } from "./features/core/rpcClient";
@@ -41,7 +42,7 @@ export default function Home({ socket }: Props) {
const dispatch = useDispatch() const dispatch = useDispatch()
// ephemeral state // ephemeral state
const [activeDownloads, setActiveDownloads] = useState(new Array<RPCResult>()); const [activeDownloads, setActiveDownloads] = useState<Array<RPCResult>>();
const [downloadFormats, setDownloadFormats] = useState<IDLMetadata>(); const [downloadFormats, setDownloadFormats] = useState<IDLMetadata>();
const [pickedVideoFormat, setPickedVideoFormat] = useState(''); const [pickedVideoFormat, setPickedVideoFormat] = useState('');
const [pickedAudioFormat, setPickedAudioFormat] = useState(''); const [pickedAudioFormat, setPickedAudioFormat] = useState('');
@@ -56,7 +57,7 @@ export default function Home({ socket }: Props) {
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const [workingUrl, setWorkingUrl] = useState(''); const [workingUrl, setWorkingUrl] = useState('');
const [showBackdrop, setShowBackdrop] = useState(false); const [showBackdrop, setShowBackdrop] = useState(true);
const [showToast, setShowToast] = useState(true); const [showToast, setShowToast] = useState(true);
// memos // memos
@@ -105,10 +106,10 @@ export default function Home({ socket }: Props) {
}, []) }, [])
useEffect(() => { useEffect(() => {
if (activeDownloads.length > 0 && showBackdrop) { if (activeDownloads && activeDownloads.length >= 0) {
setShowBackdrop(false) setShowBackdrop(false)
} }
}, [activeDownloads, showBackdrop]) }, [activeDownloads?.length])
useEffect(() => { useEffect(() => {
client.directoryTree() client.directoryTree()
@@ -137,6 +138,7 @@ export default function Home({ socket }: Props) {
setUrl('') setUrl('')
setWorkingUrl('') setWorkingUrl('')
setShowBackdrop(true)
setTimeout(() => { setTimeout(() => {
resetInput() resetInput()
@@ -460,25 +462,11 @@ export default function Home({ socket }: Props) {
</Grid> </Grid>
</Grid> : null </Grid> : null
} }
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}> {
{ settings.listView ?
activeDownloads.map(download => ( <DownloadsListView downloads={activeDownloads ?? []} abortFunction={abort} /> :
<Grid item xs={4} sm={8} md={6} key={download.id}> <DownloadsCardView downloads={activeDownloads ?? []} abortFunction={abort} />
<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>
))
}
</Grid>
<Snackbar <Snackbar
open={showToast === status.connected} open={showToast === status.connected}
autoHideDuration={1500} autoHideDuration={1500}

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

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

View File

@@ -13,7 +13,7 @@ import {
Typography Typography
} from "@mui/material"; } from "@mui/material";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ellipsis } from "../utils"; import { ellipsis, formatSpeedMiB, roundMiB } from "../utils";
type Props = { type Props = {
title: string, title: string,
@@ -42,6 +42,8 @@ export function StackableResult({
} }
}, [percentage]) }, [percentage])
const percentageToNumber = () => isCompleted ? 100 : Number(percentage.replace('%', ''))
const guessResolution = (xByY: string): any => { const guessResolution = (xByY: string): any => {
if (!xByY) return null; if (!xByY) return null;
if (xByY.includes('4320')) return (<EightK color="primary" />); if (xByY.includes('4320')) return (<EightK color="primary" />);
@@ -51,11 +53,6 @@ export function StackableResult({
return null; 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 ( return (
<Card> <Card>
<CardActionArea> <CardActionArea>

View File

@@ -14,6 +14,7 @@ export interface SettingsState {
fileRenaming: boolean fileRenaming: boolean
pathOverriding: boolean pathOverriding: boolean
enableCustomArgs: boolean enableCustomArgs: boolean
listView: boolean
} }
const initialState: SettingsState = { const initialState: SettingsState = {
@@ -27,6 +28,7 @@ const initialState: SettingsState = {
fileRenaming: localStorage.getItem("file-renaming") === "true", fileRenaming: localStorage.getItem("file-renaming") === "true",
pathOverriding: localStorage.getItem("path-overriding") === "true", pathOverriding: localStorage.getItem("path-overriding") === "true",
enableCustomArgs: localStorage.getItem("enable-custom-args") === "true", enableCustomArgs: localStorage.getItem("enable-custom-args") === "true",
listView: localStorage.getItem("listview") === "true",
} }
export const settingsSlice = createSlice({ export const settingsSlice = createSlice({
@@ -73,6 +75,10 @@ export const settingsSlice = createSlice({
state.enableCustomArgs = action.payload state.enableCustomArgs = action.payload
localStorage.setItem("enable-custom-args", action.payload.toString()) 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, setFileRenaming,
setPathOverriding, setPathOverriding,
setEnableCustomArgs, setEnableCustomArgs,
toggleListView
} = settingsSlice.actions } = settingsSlice.actions
export default settingsSlice.reducer export default settingsSlice.reducer

View File

@@ -84,3 +84,6 @@ export function getHttpRPCEndpoint() {
export function formatGiB(bytes: number) { export function formatGiB(bytes: number) {
return `${(bytes / 1_000_000_000).toFixed(0)}GiB` 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`