diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8bb10e8..86f5df6 100755 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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 ( @@ -132,6 +137,12 @@ function AppContent() { + dispatch(toggleListView())}> + + + + + } /> - } /> + }> + + + } /> diff --git a/frontend/src/Home.tsx b/frontend/src/Home.tsx index 66536ef..d4778d4 100644 --- a/frontend/src/Home.tsx +++ b/frontend/src/Home.tsx @@ -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()); + const [activeDownloads, setActiveDownloads] = useState>(); const [downloadFormats, setDownloadFormats] = useState(); 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) { : null } - - { - activeDownloads.map(download => ( - - - abort(download.id)} - resolution={download.info.resolution ?? ''} - speed={download.progress.speed} - size={download.info.filesize_approx ?? 0} - /> - - - )) - } - + { + settings.listView ? + : + + } + { + downloads.map(download => ( + + + abortFunction(download.id)} + resolution={download.info.resolution ?? ''} + speed={download.progress.speed} + size={download.info.filesize_approx ?? 0} + /> + + + )) + } + + ) +} \ No newline at end of file diff --git a/frontend/src/components/DownloadsListView.tsx b/frontend/src/components/DownloadsListView.tsx new file mode 100644 index 0000000..79a18ee --- /dev/null +++ b/frontend/src/components/DownloadsListView.tsx @@ -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 ( + + + + + + + + Title + + + Progress + + + Speed + + + Size + + + Actions + + + + + { + downloads.map(download => ( + + {ellipsis(download.info.title, 80)} + + + + {formatSpeedMiB(download.progress.speed)} + {roundMiB(download.info.filesize_approx ?? 0)} + + + + + )) + } + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/StackableResult.tsx b/frontend/src/components/StackableResult.tsx index 097be39..24a6211 100644 --- a/frontend/src/components/StackableResult.tsx +++ b/frontend/src/components/StackableResult.tsx @@ -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 (); @@ -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 ( diff --git a/frontend/src/features/settings/settingsSlice.ts b/frontend/src/features/settings/settingsSlice.ts index 2dcfdd0..3b98f72 100644 --- a/frontend/src/features/settings/settingsSlice.ts +++ b/frontend/src/features/settings/settingsSlice.ts @@ -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 \ No newline at end of file diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index fe8f138..c00da74 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -83,4 +83,7 @@ export function getHttpRPCEndpoint() { export function formatGiB(bytes: number) { return `${(bytes / 1_000_000_000).toFixed(0)}GiB` -} \ No newline at end of file +} + +export const roundMiB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)} MiB` +export const formatSpeedMiB = (val: number) => `${roundMiB(val)}/s` \ No newline at end of file