diff --git a/src/pages/library.tsx b/src/pages/library.tsx
index 50a3457..cc75348 100644
--- a/src/pages/library.tsx
+++ b/src/pages/library.tsx
@@ -6,9 +6,9 @@ import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";
import { useToast } from "@/hooks/use-toast";
import { useAppContext } from "@/providers/appContextProvider";
-import { useDownloadActionStatesStore, useDownloadStatesStore } from "@/services/store";
+import { useDownloadActionStatesStore, useDownloadStatesStore, useLibraryPageStatesStore } from "@/services/store";
import { formatBitrate, formatCodec, formatDurationString, formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
-import { AudioLines, Clock, CloudDownload, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, PackageCheck, Pause, Play, Trash2, Video, X } from "lucide-react";
+import { AudioLines, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, Pause, Play, Square, Trash2, Video, X } from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import * as fs from "@tauri-apps/plugin-fs";
import { DownloadState } from "@/types/download";
@@ -18,9 +18,14 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import Heading from "@/components/heading";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Badge } from "@/components/ui/badge";
export default function LibraryPage() {
+ const activeTab = useLibraryPageStatesStore(state => state.activeTab);
+ const setActiveTab = useLibraryPageStatesStore(state => state.setActiveTab);
+
const downloadStates = useDownloadStatesStore(state => state.downloadStates);
const downloadActions = useDownloadActionStatesStore(state => state.downloadActions);
const setIsResumingDownload = useDownloadActionStatesStore(state => state.setIsResumingDownload);
@@ -36,6 +41,9 @@ export default function LibraryPage() {
const incompleteDownloads = downloadStates.filter(state => state.download_status !== 'completed');
const completedDownloads = downloadStates.filter(state => state.download_status === 'completed');
+ const ongoingDownloads = downloadStates.filter(state =>
+ ['starting', 'downloading', 'queued'].includes(state.download_status)
+ );
const openFile = async (filePath: string | null, app: string | null) => {
if (filePath && await fs.exists(filePath)) {
@@ -96,329 +104,389 @@ export default function LibraryPage() {
})
}
+ const stopOngoingDownloads = async () => {
+ if (ongoingDownloads.length > 0) {
+ try {
+ await invoke('pause_ongoing_downloads').then(() => {
+ queryClient.invalidateQueries({ queryKey: ['download-states'] });
+ toast({
+ title: 'Stopped downloads',
+ description: 'All ongoing downloads are being stopped.',
+ });
+ });
+ } catch (e) {
+ console.error(e);
+ toast({
+ title: 'Failed to stop downloads',
+ description: 'An error occurred while trying to stop the downloads.',
+ variant: "destructive"
+ });
+ }
+ } else {
+ toast({
+ title: 'No ongoing downloads',
+ description: 'There are no ongoing downloads to stop.',
+ variant: "destructive"
+ });
+ }
+ }
+
return (
-
-
-
-
Incomplete Downloads
+
+
+
+ Completed {completedDownloads.length > 0 && (`(${completedDownloads.length})`)}
+ Incomplete {(incompleteDownloads.length > 0 && ongoingDownloads.length <= 0) && (`(${incompleteDownloads.length})`)} {ongoingDownloads.length > 0 && ({ongoingDownloads.length} )}
+
+
+
+
+
+ Stop
+
+
+
+
+ Stop all ongoing downloads?
+
+ Are you sure you want to stop all ongoing downloads? This will pause all downloads including the download queue.
+
+
+
+ Cancel
+ stopOngoingDownloads()}
+ >Stop
+
+
+
-
-
-
- {incompleteDownloads.length > 0 ? (
- incompleteDownloads.map((state) => {
- const itemActionStates = downloadActions[state.download_id] || {
- isResuming: false,
- isPausing: false,
- isCanceling: false,
- isDeleteFileChecked: false,
- };
- return (
-
-
-
-
-
- {state.ext && (
-
- {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
-
- )}
- {state.filetype && state.filetype === 'audio' && (
-
- )}
- {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
-
- )}
- {state.ext.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
-
- )}
-
-
-
-
{state.title}
- {((state.download_status === 'starting') || (state.download_status === 'downloading' && state.status === 'finished')) && (
-
- )}
- {(state.download_status === 'downloading' || state.download_status === 'paused') && state.progress && state.status !== 'finished' && (
-
-
{state.progress}%
-
-
{
- state.downloaded && state.total
- ? `(${formatFileSize(state.downloaded)} / ${formatFileSize(state.total)})`
- : null
- }
+
+
+ {completedDownloads.length > 0 ? (
+ completedDownloads.map((state) => {
+ const itemActionStates = downloadActions[state.download_id] || {
+ isResuming: false,
+ isPausing: false,
+ isCanceling: false,
+ isDeleteFileChecked: false,
+ };
+ return (
+
+
+
+
+
+
+ {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
+
+ )}
+ {state.filetype && state.filetype === 'audio' && (
+
+ )}
+ {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
+
+ )}
+ {state.ext?.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
+
+
+
+
+
{state.title}
+
{state.channel ? state.channel : 'unknown'} {state.host ? `• ${state.host}` : 'unknown'}
+
+
{state.duration_string ? formatDurationString(state.duration_string) : 'unknown'}
+
+
+ {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
+
+ )}
+ {state.filetype && state.filetype === 'audio' && (
+
+ )}
+ {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
+
+ )}
+ {state.filesize ? formatFileSize(state.filesize) : 'unknown'}
+
+
+
+ {state.vbr && state.abr ? (
+ formatBitrate(state.vbr + state.abr)
+ ) : state.vbr ? (
+ formatBitrate(state.vbr)
+ ) : state.abr ? (
+ formatBitrate(state.abr)
+ ) : (
+ 'unknown'
+ )}
+
+
+
+ {state.playlist_id && state.playlist_index && (
+
+ Playlist ({state.playlist_index} of {state.playlist_n_entries})
+
+ )}
+ {state.vcodec && (
+ {formatCodec(state.vcodec)}
+ )}
+ {state.acodec && (
+ {formatCodec(state.acodec)}
+ )}
+ {state.dynamic_range && state.dynamic_range !== 'SDR' && (
+ {state.dynamic_range}
+ )}
+ {state.subtitle_id && (
+
+ ESUB
+
+ )}
+
- )}
-
{ state.download_status && (
- `${state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} ${state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? `• Speed: ${formatSpeed(state.speed)}` : ""} ${state.download_status === 'downloading' && state.eta ? `• ETA: ${formatSecToTimeString(state.eta)}` : ""}`
- )}
-
-
- {state.download_status === 'paused' ? (
-
{
- setIsResumingDownload(state.download_id, true);
- try {
- await resumeDownload(state)
- // toast({
- // title: 'Resumed Download',
- // description: 'Download resumed, it will re-start shortly.',
- // })
- } catch (e) {
- console.error(e);
- toast({
- title: 'Failed to Resume Download',
- description: 'An error occurred while trying to resume the download.',
- variant: "destructive"
- })
- } finally {
- setIsResumingDownload(state.download_id, false);
- }
- }}
- disabled={itemActionStates.isResuming || itemActionStates.isCanceling}
- >
- {itemActionStates.isResuming ? (
- <>
-
- Resuming
- >
- ) : (
- <>
-
- Resume
- >
- )}
-
- ) : (
-
{
- setIsPausingDownload(state.download_id, true);
- try {
- await pauseDownload(state)
- // toast({
- // title: 'Paused Download',
- // description: 'Download paused successfully.',
- // })
- } catch (e) {
- console.error(e);
- toast({
- title: 'Failed to Pause Download',
- description: 'An error occurred while trying to pause the download.',
- variant: "destructive"
- })
- } finally {
- setIsPausingDownload(state.download_id, false);
- }
- }}
- disabled={itemActionStates.isPausing || itemActionStates.isCanceling || state.download_status !== 'downloading' || (state.download_status === 'downloading' && state.status === 'finished')}
- >
- {itemActionStates.isPausing ? (
- <>
-
- Pausing
- >
- ) : (
- <>
-
- Pause
- >
- )}
-
- )}
-
{
- setIsCancelingDownload(state.download_id, true);
- try {
- await cancelDownload(state)
- toast({
- title: 'Canceled Download',
- description: 'Download canceled successfully.',
- })
- } catch (e) {
- console.error(e);
- toast({
- title: 'Failed to Cancel Download',
- description: 'An error occurred while trying to cancel the download.',
- variant: "destructive"
- })
- } finally {
- setIsCancelingDownload(state.download_id, false);
- }
- }}
- disabled={itemActionStates.isCanceling || itemActionStates.isResuming || itemActionStates.isPausing || state.download_status === 'starting' || (state.download_status === 'downloading' && state.status === 'finished')}
- >
- {itemActionStates.isCanceling ? (
- <>
-
- Canceling
- >
- ) : (
- <>
-
- Cancel
- >
- )}
-
-
-
-
- )
- })
- ) : (
- No Incomplete downloads!
- )}
-
-
-
-
-
Completed Downloads
-
-
-
-
- {completedDownloads.length > 0 ? (
- completedDownloads.map((state) => {
- const itemActionStates = downloadActions[state.download_id] || {
- isResuming: false,
- isPausing: false,
- isCanceling: false,
- isDeleteFileChecked: false,
- };
- return (
-
-
-
-
-
-
- {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
-
- )}
- {state.filetype && state.filetype === 'audio' && (
-
- )}
- {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
-
- )}
- {state.ext?.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
-
-
-
-
-
{state.title}
-
{state.channel ? state.channel : 'unknown'} {state.host ? `• ${state.host}` : 'unknown'}
-
-
{state.duration_string ? formatDurationString(state.duration_string) : 'unknown'}
-
-
- {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
-
- )}
- {state.filetype && state.filetype === 'audio' && (
-
- )}
- {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
-
- )}
- {state.filesize ? formatFileSize(state.filesize) : 'unknown'}
-
-
-
- {state.vbr && state.abr ? (
- formatBitrate(state.vbr + state.abr)
- ) : state.vbr ? (
- formatBitrate(state.vbr)
- ) : state.abr ? (
- formatBitrate(state.abr)
- ) : (
- 'unknown'
- )}
-
-
-
- {state.playlist_id && state.playlist_index && (
-
- Playlist ({state.playlist_index} of {state.playlist_n_entries})
-
- )}
- {state.vcodec && (
- {formatCodec(state.vcodec)}
- )}
- {state.acodec && (
- {formatCodec(state.acodec)}
- )}
- {state.dynamic_range && state.dynamic_range !== 'SDR' && (
- {state.dynamic_range}
- )}
- {state.subtitle_id && (
-
- ESUB
-
- )}
-
-
-
-
openFile(state.filepath, null)}>
-
- Open
-
-
openFile(state.filepath, 'explorer')}>
-
- Open in Explorer
-
-
-
-
-
- Remove
+
+
openFile(state.filepath, null)}>
+
+ Open
-
-
-
- Are you absolutely sure?
-
- This action cannot be undone! it will permanently remove this from downloads.
-
-
- {setIsDeleteFileChecked(state.download_id, !itemActionStates.isDeleteFileChecked)}} />
- Delete the downloaded file
-
-
-
- Cancel
- removeFromDownloads(state, itemActionStates.isDeleteFileChecked).then(() => {
- setIsDeleteFileChecked(state.download_id, false);
- })
- }>Remove
-
-
-
+
openFile(state.filepath, 'explorer')}>
+
+ Open in Explorer
+
+
+
+
+
+ Remove
+
+
+
+
+ Are you absolutely sure?
+
+ This action cannot be undone! it will permanently remove this from downloads.
+
+
+ {setIsDeleteFileChecked(state.download_id, !itemActionStates.isDeleteFileChecked)}} />
+ Delete the downloaded file
+
+
+
+ Cancel
+ removeFromDownloads(state, itemActionStates.isDeleteFileChecked).then(() => {
+ setIsDeleteFileChecked(state.download_id, false);
+ })
+ }>Remove
+
+
+
+
+
-
+ )
+ })
+ ) : (
+
+
Nothing!
+
No Completed Downloads
+
You have not completed any downloads yet. Complete downloading something to see here :)
- )
- })
- ) : (
-
No Completed downloads!
- )}
-
+ )}
+
+
+
+
+ {incompleteDownloads.length > 0 ? (
+ incompleteDownloads.map((state) => {
+ const itemActionStates = downloadActions[state.download_id] || {
+ isResuming: false,
+ isPausing: false,
+ isCanceling: false,
+ isDeleteFileChecked: false,
+ };
+ return (
+
+
+
+
+
+ {state.ext && (
+
+ {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
+
+ )}
+ {state.filetype && state.filetype === 'audio' && (
+
+ )}
+ {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
+
+ )}
+ {state.ext.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
+
+ )}
+
+
+
+
{state.title}
+ {((state.download_status === 'starting') || (state.download_status === 'downloading' && state.status === 'finished')) && (
+
+ )}
+ {(state.download_status === 'downloading' || state.download_status === 'paused') && state.progress && state.status !== 'finished' && (
+
+
{state.progress}%
+
+
{
+ state.downloaded && state.total
+ ? `(${formatFileSize(state.downloaded)} / ${formatFileSize(state.total)})`
+ : null
+ }
+
+ )}
+
{ state.download_status && (
+ `${state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} ${state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? `• Speed: ${formatSpeed(state.speed)}` : ""} ${state.download_status === 'downloading' && state.eta ? `• ETA: ${formatSecToTimeString(state.eta)}` : ""}`
+ )}
+
+
+ {state.download_status === 'paused' ? (
+
{
+ setIsResumingDownload(state.download_id, true);
+ try {
+ await resumeDownload(state)
+ // toast({
+ // title: 'Resumed Download',
+ // description: 'Download resumed, it will re-start shortly.',
+ // })
+ } catch (e) {
+ console.error(e);
+ toast({
+ title: 'Failed to Resume Download',
+ description: 'An error occurred while trying to resume the download.',
+ variant: "destructive"
+ })
+ } finally {
+ setIsResumingDownload(state.download_id, false);
+ }
+ }}
+ disabled={itemActionStates.isResuming || itemActionStates.isCanceling}
+ >
+ {itemActionStates.isResuming ? (
+ <>
+
+ Resuming
+ >
+ ) : (
+ <>
+
+ Resume
+ >
+ )}
+
+ ) : (
+
{
+ setIsPausingDownload(state.download_id, true);
+ try {
+ await pauseDownload(state)
+ // toast({
+ // title: 'Paused Download',
+ // description: 'Download paused successfully.',
+ // })
+ } catch (e) {
+ console.error(e);
+ toast({
+ title: 'Failed to Pause Download',
+ description: 'An error occurred while trying to pause the download.',
+ variant: "destructive"
+ })
+ } finally {
+ setIsPausingDownload(state.download_id, false);
+ }
+ }}
+ disabled={itemActionStates.isPausing || itemActionStates.isCanceling || state.download_status !== 'downloading' || (state.download_status === 'downloading' && state.status === 'finished')}
+ >
+ {itemActionStates.isPausing ? (
+ <>
+
+ Pausing
+ >
+ ) : (
+ <>
+
+ Pause
+ >
+ )}
+
+ )}
+
{
+ setIsCancelingDownload(state.download_id, true);
+ try {
+ await cancelDownload(state)
+ toast({
+ title: 'Canceled Download',
+ description: 'Download canceled successfully.',
+ })
+ } catch (e) {
+ console.error(e);
+ toast({
+ title: 'Failed to Cancel Download',
+ description: 'An error occurred while trying to cancel the download.',
+ variant: "destructive"
+ })
+ } finally {
+ setIsCancelingDownload(state.download_id, false);
+ }
+ }}
+ disabled={itemActionStates.isCanceling || itemActionStates.isResuming || itemActionStates.isPausing || state.download_status === 'starting' || (state.download_status === 'downloading' && state.status === 'finished')}
+ >
+ {itemActionStates.isCanceling ? (
+ <>
+
+ Canceling
+ >
+ ) : (
+ <>
+
+ Cancel
+ >
+ )}
+
+
+
+
+ )
+ })
+ ) : (
+
+
Nothing!
+
No Incomplete Downloads
+
You have all caught up! Sit back and relax or just spin up a new download to see here :)
+
+ )}
+
+
+
);
}
\ No newline at end of file
diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx
index 603b211..31e946f 100644
--- a/src/pages/settings.tsx
+++ b/src/pages/settings.tsx
@@ -560,7 +560,13 @@ export default function SettingsPage() {
Extension Websocket Server
-
{isChangingWebSocketPort || isRestartingWebSocketServer ? 'Restarting...' : 'Running' }
+
+ {isChangingWebSocketPort || isRestartingWebSocketServer ? (
+ <>
Restarting... >
+ ) : (
+ <>
Running >
+ )}
+
diff --git a/src/services/store.ts b/src/services/store.ts
index 2f53720..d1e09e4 100644
--- a/src/services/store.ts
+++ b/src/services/store.ts
@@ -1,4 +1,4 @@
-import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, KvPairsStatesStore, SettingsPageStatesStore } from '@/types/store';
+import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, KvPairsStatesStore, LibraryPageStatesStore, SettingsPageStatesStore } from '@/types/store';
import { create } from 'zustand';
export const useBasePathsStore = create((set) => ({
@@ -58,6 +58,11 @@ export const useDownloaderPageStatesStore = create((s
setSelectedPlaylistVideoIndex: (index) => set(() => ({ selectedPlaylistVideoIndex: index }))
}));
+export const useLibraryPageStatesStore = create((set) => ({
+ activeTab: 'completed',
+ setActiveTab: (tab) => set(() => ({ activeTab: tab }))
+}));
+
export const useDownloadActionStatesStore = create((set) => ({
downloadActions: {},
setIsResumingDownload: (download_id, isResuming) => set((state) => ({
diff --git a/src/types/store.ts b/src/types/store.ts
index 4ff0e01..55c504e 100644
--- a/src/types/store.ts
+++ b/src/types/store.ts
@@ -47,6 +47,11 @@ export interface DownloaderPageStatesStore {
setSelectedPlaylistVideoIndex: (index: string) => void;
}
+export interface LibraryPageStatesStore {
+ activeTab: string;
+ setActiveTab: (tab: string) => void;
+}
+
export interface DownloadActionStatesStore {
downloadActions: {
[download_id: string]: {