mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-20 00:49:33 +05:30
refactor: added errored state and improved error detection
This commit is contained in:
63
src/App.tsx
63
src/App.tsx
@@ -52,14 +52,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
color_scheme: APP_COLOR_SCHEME,
|
||||
} = useSettingsPageStatesStore(state => state.settings);
|
||||
|
||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||
const erroredDownloadId = useDownloaderPageStatesStore((state) => state.erroredDownloadId);
|
||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
||||
const erroredDownloadIds = useDownloaderPageStatesStore((state) => state.erroredDownloadIds);
|
||||
const expectedErrorDownloadIds = useDownloaderPageStatesStore((state) => state.expectedErrorDownloadIds);
|
||||
const removeErroredDownload = useDownloaderPageStatesStore((state) => state.removeErroredDownload);
|
||||
const removeExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.removeExpectedErrorDownload);
|
||||
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
const appWindow = getCurrentWebviewWindow();
|
||||
const navigate = useNavigate();
|
||||
const LOG = useLogger();
|
||||
const currentPlatform = platform();
|
||||
@@ -79,6 +77,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
||||
const hasRunAppUpdateCheckRef = useRef(false);
|
||||
const isRegisteredToMacOsRef = useRef(false);
|
||||
const pendingErrorUpdatesRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const { fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload, processQueuedDownloads } = useDownloader();
|
||||
|
||||
@@ -328,38 +327,56 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// show a toast and pause the download when yt-dlp exits unexpectedly
|
||||
useEffect(() => {
|
||||
if (isErrored && !isErrorExpected) {
|
||||
const unexpectedErrors = Array.from(erroredDownloadIds).filter(id => !expectedErrorDownloadIds.has(id));
|
||||
const processedUnexpectedErrors = unexpectedErrors.filter(id => !pendingErrorUpdatesRef.current.has(id));
|
||||
if (unexpectedErrors.length === 0) return;
|
||||
|
||||
processedUnexpectedErrors.forEach((downloadId) => {
|
||||
const downloadState = globalDownloadStates.find(d => d.download_id === downloadId);
|
||||
toast.error("Download Failed", {
|
||||
description: "yt-dlp exited unexpectedly. Please try again later",
|
||||
description: `The download for "${downloadState?.title}" failed because yt-dlp exited unexpectedly. Please try again later.`,
|
||||
});
|
||||
if (erroredDownloadId) {
|
||||
downloadStatusUpdater.mutate({ download_id: erroredDownloadId, download_status: 'paused' }, {
|
||||
});
|
||||
|
||||
const timeoutIds: NodeJS.Timeout[] = [];
|
||||
unexpectedErrors.forEach((downloadId) => {
|
||||
pendingErrorUpdatesRef.current.add(downloadId);
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'errored' }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download status updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
removeErroredDownload(downloadId);
|
||||
pendingErrorUpdatesRef.current.delete(downloadId);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download status:", error);
|
||||
removeErroredDownload(downloadId);
|
||||
pendingErrorUpdatesRef.current.delete(downloadId);
|
||||
}
|
||||
})
|
||||
setErroredDownloadId(null);
|
||||
}
|
||||
setIsErrored(false);
|
||||
setIsErrorExpected(false);
|
||||
}
|
||||
}, [isErrored, isErrorExpected, erroredDownloadId, setIsErrored, setIsErrorExpected, setErroredDownloadId]);
|
||||
});
|
||||
}, 500);
|
||||
timeoutIds.push(timeoutId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
timeoutIds.forEach(id => clearTimeout(id));
|
||||
};
|
||||
}, [erroredDownloadIds, expectedErrorDownloadIds]);
|
||||
|
||||
// auto reset error states after 3 seconds of expecting an error
|
||||
useEffect(() => {
|
||||
if (isErrorExpected) {
|
||||
if (expectedErrorDownloadIds.size > 0) {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setIsErrored(false);
|
||||
setIsErrorExpected(false);
|
||||
setErroredDownloadId(null);
|
||||
expectedErrorDownloadIds.forEach((downloadId) => {
|
||||
removeErroredDownload(downloadId);
|
||||
removeExpectedErrorDownload(downloadId);
|
||||
});
|
||||
}, 3000);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
}, [isErrorExpected, setIsErrorExpected]);
|
||||
}, [expectedErrorDownloadIds]);
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{ fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload }}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { toast } from "sonner";
|
||||
import { useAppContext } from "@/providers/appContextProvider";
|
||||
import { useDownloadActionStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||
import { formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
|
||||
import { ArrowUpRightIcon, CircleCheck, File, Loader2, Music, Pause, Play, Video, X } from "lucide-react";
|
||||
import { ArrowUpRightIcon, CircleCheck, File, Info, Loader2, Music, Pause, Play, RotateCw, Video, X } from "lucide-react";
|
||||
import { DownloadState } from "@/types/download";
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -72,7 +72,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
{((state.download_status === 'starting') || (state.download_status === 'downloading' && state.status === 'finished')) && (
|
||||
<IndeterminateProgress indeterminate={true} className="w-full" />
|
||||
)}
|
||||
{(state.download_status === 'downloading' || state.download_status === 'paused') && state.progress && state.status !== 'finished' && (
|
||||
{(state.download_status === 'downloading' || state.download_status === 'paused' || state.download_status === 'errored') && state.progress && state.status !== 'finished' && (
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<span className="text-sm text-nowrap">{state.progress}%</span>
|
||||
<Progress value={state.progress} />
|
||||
@@ -84,7 +84,21 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{state.download_status && state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} {debugMode && state.download_id ? <><span className="text-primary">•</span> ID: {state.download_id.toUpperCase()}</> : ""} {state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? <><span className="text-primary">•</span> Speed: {formatSpeed(state.speed)}</> : ""} {state.download_status === 'downloading' && state.eta ? <><span className="text-primary">•</span> ETA: {formatSecToTimeString(state.eta)}</> : ""}
|
||||
{state.download_status && state.download_status === 'downloading' && state.status === 'finished' ? (
|
||||
<span>Processing</span>
|
||||
) : state.download_status && state.download_status === 'errored' ? (
|
||||
<span className="text-destructive"><Info className="inline size-3 mb-1 mr-0.5" /> Errored</span>
|
||||
) : (
|
||||
<span>{state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)}</span>
|
||||
)} {
|
||||
(debugMode && state.download_id) || (state.download_status === 'errored' && state.download_id) && (
|
||||
<><span className="text-primary">•</span> ID: {state.download_id.toUpperCase()}</>
|
||||
)} {
|
||||
state.download_status === 'downloading' && state.status !== 'finished' && state.speed && (
|
||||
<><span className="text-primary">•</span> Speed: {formatSpeed(state.speed)}</>
|
||||
)} {state.download_status === 'downloading' && state.eta && (
|
||||
<><span className="text-primary">•</span> ETA: {formatSecToTimeString(state.eta)}</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex items-center gap-2 mt-2">
|
||||
@@ -102,7 +116,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Failed to Resume Download", {
|
||||
description: "An error occurred while trying to resume the download.",
|
||||
description: `An error occurred while trying to resume the download for "${state.title}".`,
|
||||
})
|
||||
} finally {
|
||||
setIsResumingDownload(state.download_id, false);
|
||||
@@ -122,6 +136,37 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : state.download_status === 'errored' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-fill"
|
||||
onClick={async () => {
|
||||
setIsResumingDownload(state.download_id, true);
|
||||
try {
|
||||
await resumeDownload(state);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Failed to Restart Download", {
|
||||
description: `An error occurred while trying to restart the download for "${state.title}".`,
|
||||
})
|
||||
} finally {
|
||||
setIsResumingDownload(state.download_id, false);
|
||||
}
|
||||
}}
|
||||
disabled={itemActionStates.isResuming || itemActionStates.isCanceling}
|
||||
>
|
||||
{itemActionStates.isResuming ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Retrying
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RotateCw className="w-4 h-4" />
|
||||
Retry
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -136,7 +181,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Failed to Pause Download", {
|
||||
description: "An error occurred while trying to pause the download."
|
||||
description: `An error occurred while trying to pause the download for "${state.title}".`,
|
||||
})
|
||||
} finally {
|
||||
setIsPausingDownload(state.download_id, false);
|
||||
@@ -165,12 +210,12 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
||||
try {
|
||||
await cancelDownload(state)
|
||||
toast.success("Canceled Download", {
|
||||
description: "Download canceled successfully.",
|
||||
description: `The download for "${state.title}" has been canceled.`,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Failed to Cancel Download", {
|
||||
description: "An error occurred while trying to cancel the download.",
|
||||
description: `An error occurred while trying to cancel the download for "${state.title}".`,
|
||||
})
|
||||
} finally {
|
||||
setIsCancelingDownload(state.download_id, false);
|
||||
|
||||
@@ -63,10 +63,11 @@ export default function useDownloader() {
|
||||
download_completion_notification: DOWNLOAD_COMPLETION_NOTIFICATION
|
||||
} = useSettingsPageStatesStore(state => state.settings);
|
||||
|
||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
||||
const expectedErrorDownloadIds = useDownloaderPageStatesStore((state) => state.expectedErrorDownloadIds);
|
||||
const addErroredDownload = useDownloaderPageStatesStore((state) => state.addErroredDownload);
|
||||
const removeErroredDownload = useDownloaderPageStatesStore((state) => state.removeErroredDownload);
|
||||
const addExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.addExpectedErrorDownload);
|
||||
const removeExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.removeExpectedErrorDownload);
|
||||
|
||||
const LOG = useLogger();
|
||||
const currentPlatform = platform();
|
||||
@@ -210,10 +211,6 @@ export default function useDownloader() {
|
||||
const startDownload = async (params: StartDownloadParams) => {
|
||||
const { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems } = params;
|
||||
LOG.info('NEODLP', `Initiating yt-dlp download for URL: ${url}`);
|
||||
// set error states to default
|
||||
setIsErrored(false);
|
||||
setIsErrorExpected(false);
|
||||
setErroredDownloadId(null);
|
||||
|
||||
console.log('Starting download:', { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems });
|
||||
if (!ffmpegPath || !tempDownloadDirPath || !downloadDirPath) {
|
||||
@@ -258,6 +255,11 @@ export default function useDownloader() {
|
||||
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
||||
const downloadId = resumeState?.download_id || ulid() /*generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain)*/;
|
||||
|
||||
// Clear any existing errored/expected error states for this download
|
||||
removeErroredDownload(downloadId);
|
||||
removeExpectedErrorDownload(downloadId);
|
||||
|
||||
// const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
||||
// const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
||||
// let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
||||
@@ -446,10 +448,7 @@ export default function useDownloader() {
|
||||
if (data.code !== 0) {
|
||||
console.error(`Download failed with code ${data.code}`);
|
||||
LOG.error(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code} (ignore if you manually paused or cancelled the download)`);
|
||||
if (!isErrorExpected) {
|
||||
setIsErrored(true);
|
||||
setErroredDownloadId(downloadId);
|
||||
}
|
||||
if (!expectedErrorDownloadIds.has(downloadId)) addErroredDownload(downloadId);
|
||||
} else {
|
||||
LOG.info(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code}`);
|
||||
}
|
||||
@@ -458,8 +457,7 @@ export default function useDownloader() {
|
||||
command.on('error', error => {
|
||||
console.error(`Error: ${error}`);
|
||||
LOG.error(`YT-DLP Download ${downloadId}`, `Error occurred: ${error}`);
|
||||
setIsErrored(true);
|
||||
setErroredDownloadId(downloadId);
|
||||
addErroredDownload(downloadId);
|
||||
});
|
||||
|
||||
command.stdout.on('data', line => {
|
||||
@@ -681,7 +679,7 @@ export default function useDownloader() {
|
||||
try {
|
||||
LOG.info('NEODLP', `Pausing yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
||||
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
||||
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
||||
addExpectedErrorDownload(downloadState.download_id); // Mark as error expected to handle UI state
|
||||
console.log("Killing process with PID:", downloadState.process_id);
|
||||
await invoke('kill_all_process', { pid: downloadState.process_id });
|
||||
}
|
||||
@@ -706,12 +704,13 @@ export default function useDownloader() {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}, 500);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to pause download: ${e}`);
|
||||
LOG.error('NEODLP', `Failed to pause download with id: ${downloadState.download_id} with error: ${e}`);
|
||||
isProcessingQueueRef.current = false;
|
||||
removeExpectedErrorDownload(downloadState.download_id);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -726,6 +725,7 @@ export default function useDownloader() {
|
||||
output_format: null,
|
||||
embed_metadata: null,
|
||||
embed_thumbnail: null,
|
||||
square_crop_thumbnail: null,
|
||||
sponsorblock: null,
|
||||
custom_command: null
|
||||
},
|
||||
@@ -744,31 +744,39 @@ export default function useDownloader() {
|
||||
try {
|
||||
LOG.info('NEODLP', `Cancelling yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
||||
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
||||
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
||||
addExpectedErrorDownload(downloadState.download_id); // Mark as error expected to handle UI state
|
||||
console.log("Killing process with PID:", downloadState.process_id);
|
||||
await invoke('kill_all_process', { pid: downloadState.process_id });
|
||||
}
|
||||
downloadStateDeleter.mutate(downloadState.download_id, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download State deleted successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
// Reset processing flag and trigger queue processing
|
||||
isProcessingQueueRef.current = false;
|
||||
|
||||
// Process the queue after a short delay
|
||||
setTimeout(() => {
|
||||
processQueuedDownloads();
|
||||
}, 1000);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to delete download state:", error);
|
||||
isProcessingQueueRef.current = false;
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
downloadStateDeleter.mutate(downloadState.download_id, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download State deleted successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
// Reset processing flag and trigger queue processing
|
||||
isProcessingQueueRef.current = false;
|
||||
|
||||
// Process the queue after a short delay
|
||||
setTimeout(() => {
|
||||
processQueuedDownloads();
|
||||
}, 1000);
|
||||
resolve();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to delete download state:", error);
|
||||
isProcessingQueueRef.current = false;
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
console.error(`Failed to cancel download: ${e}`);
|
||||
LOG.error('NEODLP', `Failed to cancel download with id: ${downloadState.download_id} with error: ${e}`);
|
||||
isProcessingQueueRef.current = false;
|
||||
removeExpectedErrorDownload(downloadState.download_id);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -826,6 +834,7 @@ export default function useDownloader() {
|
||||
output_format: null,
|
||||
embed_metadata: null,
|
||||
embed_thumbnail: null,
|
||||
square_crop_thumbnail: null,
|
||||
sponsorblock: null,
|
||||
custom_command: null
|
||||
},
|
||||
|
||||
@@ -3,13 +3,22 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
const TanstackProvider = ({children}: {children: React.ReactNode}) => {
|
||||
const queryClient = new QueryClient();
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
networkMode: 'always',
|
||||
},
|
||||
mutations: {
|
||||
networkMode: 'always',
|
||||
}
|
||||
}
|
||||
});
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default TanstackProvider
|
||||
export default TanstackProvider
|
||||
|
||||
@@ -62,9 +62,8 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
||||
sponsorblock: null,
|
||||
custom_command: null
|
||||
},
|
||||
isErrored: false,
|
||||
isErrorExpected: false,
|
||||
erroredDownloadId: null,
|
||||
erroredDownloadIds: new Set(),
|
||||
expectedErrorDownloadIds: new Set(),
|
||||
videoPanelSizes: [35, 65],
|
||||
playlistPanelSizes: [45, 55],
|
||||
setActiveDownloadModeTab: (tab) => set(() => ({ activeDownloadModeTab: tab })),
|
||||
@@ -92,9 +91,23 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
||||
custom_command: null
|
||||
}
|
||||
})),
|
||||
setIsErrored: (isErrored) => set(() => ({ isErrored: isErrored })),
|
||||
setIsErrorExpected: (isErrorExpected) => set(() => ({ isErrorExpected: isErrorExpected })),
|
||||
setErroredDownloadId: (downloadId) => set(() => ({ erroredDownloadId: downloadId })),
|
||||
addErroredDownload: (downloadId) => set((state) => ({
|
||||
erroredDownloadIds: new Set(state.erroredDownloadIds).add(downloadId)
|
||||
})),
|
||||
removeErroredDownload: (downloadId) => set((state) => {
|
||||
const newSet = new Set(state.erroredDownloadIds);
|
||||
newSet.delete(downloadId);
|
||||
return { erroredDownloadIds: newSet };
|
||||
}),
|
||||
addExpectedErrorDownload: (downloadId) => set((state) => ({
|
||||
expectedErrorDownloadIds: new Set(state.expectedErrorDownloadIds).add(downloadId)
|
||||
})),
|
||||
removeExpectedErrorDownload: (downloadId) => set((state) => {
|
||||
const newSet = new Set(state.expectedErrorDownloadIds);
|
||||
newSet.delete(downloadId);
|
||||
return { expectedErrorDownloadIds: newSet };
|
||||
}),
|
||||
clearErrorStates: () => set({ erroredDownloadIds: new Set(), expectedErrorDownloadIds: new Set() }),
|
||||
setVideoPanelSizes: (sizes) => set(() => ({ videoPanelSizes: sizes })),
|
||||
setPlaylistPanelSizes: (sizes) => set(() => ({ playlistPanelSizes: sizes }))
|
||||
}));
|
||||
|
||||
@@ -45,9 +45,8 @@ export interface DownloaderPageStatesStore {
|
||||
selectedSubtitles: string[];
|
||||
selectedPlaylistVideoIndex: string;
|
||||
downloadConfiguration: DownloadConfiguration;
|
||||
isErrored: boolean;
|
||||
isErrorExpected: boolean;
|
||||
erroredDownloadId: string | null;
|
||||
erroredDownloadIds: Set<string>;
|
||||
expectedErrorDownloadIds: Set<string>;
|
||||
videoPanelSizes: number[];
|
||||
playlistPanelSizes: number[];
|
||||
setActiveDownloadModeTab: (tab: string) => void;
|
||||
@@ -61,9 +60,11 @@ export interface DownloaderPageStatesStore {
|
||||
setDownloadConfigurationKey: (key: string, value: unknown) => void;
|
||||
setDownloadConfiguration: (config: DownloadConfiguration) => void;
|
||||
resetDownloadConfiguration: () => void;
|
||||
setIsErrored: (isErrored: boolean) => void;
|
||||
setIsErrorExpected: (isErrorExpected: boolean) => void;
|
||||
setErroredDownloadId: (downloadId: string | null) => void;
|
||||
addErroredDownload: (downloadId: string) => void;
|
||||
removeErroredDownload: (downloadId: string) => void;
|
||||
addExpectedErrorDownload: (downloadId: string) => void;
|
||||
removeExpectedErrorDownload: (downloadId: string) => void;
|
||||
clearErrorStates: () => void;
|
||||
setVideoPanelSizes: (sizes: number[]) => void;
|
||||
setPlaylistPanelSizes: (sizes: number[]) => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user