feat: added cancel search button and improved search error handling

This commit is contained in:
2025-07-21 19:27:39 +05:30
parent 161f7ad13c
commit 495b6f2129
4 changed files with 65 additions and 15 deletions

View File

@@ -43,6 +43,8 @@ export default function App({ children }: { children: React.ReactNode }) {
const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath);
const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath);
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
@@ -121,14 +123,19 @@ export default function App({ children }: { children: React.ReactNode }) {
jsonOutput += line;
});
command.on('close', async () => {
try {
const data: RawVideoInfo = JSON.parse(jsonOutput);
resolve(data);
}
catch (e) {
console.error(`Failed to parse JSON: ${e}`);
command.on('close', async (data) => {
if (data.code !== 0) {
console.error(`yt-dlp failed to fetch metadata with code ${data.code}`);
resolve(null);
} else {
try {
const parsedData: RawVideoInfo = JSON.parse(jsonOutput);
resolve(parsedData);
}
catch (e) {
console.error(`Failed to parse JSON: ${e}`);
resolve(null);
}
}
});
@@ -137,7 +144,9 @@ export default function App({ children }: { children: React.ReactNode }) {
resolve(null);
});
command.spawn().catch(e => {
command.spawn().then(child => {
setSearchPid(child.pid);
}).catch(e => {
console.error(`Failed to spawn command: ${e}`);
resolve(null);
});
@@ -265,7 +274,7 @@ export default function App({ children }: { children: React.ReactNode }) {
console.log('Starting download with args:', args);
const command = Command.sidecar('binaries/yt-dlp', args);
command.on('close', async data => {
command.on('close', async (data) => {
if (data.code !== 0) {
console.error(`Download failed with code ${data.code}`);
if (!isErrorExpected) {
@@ -303,6 +312,8 @@ export default function App({ children }: { children: React.ReactNode }) {
command.on('error', error => {
console.error(`Error: ${error}`);
setIsErrored(true);
setErroredDownloadId(downloadId);
});
command.stdout.on('data', line => {

View File

@@ -9,7 +9,7 @@ import { useToast } from "@/hooks/use-toast";
import { useAppContext } from "@/providers/appContextProvider";
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon } from "lucide-react";
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon, X } from "lucide-react";
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
import { useEffect, useRef } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
@@ -23,6 +23,7 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from "@/component
import { config } from "@/config";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { invoke } from "@tauri-apps/api/core";
const searchFormSchema = z.object({
url: z.string().min(1, { message: "URL is required" })
@@ -38,11 +39,14 @@ export default function DownloaderPage() {
const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading);
const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl);
const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch);
const searchPid = useCurrentVideoMetadataStore((state) => state.searchPid);
const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl);
const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata);
const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading);
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch);
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
const setShowSearchError = useCurrentVideoMetadataStore((state) => state.setShowSearchError);
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
@@ -205,19 +209,25 @@ export default function DownloaderPage() {
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
setVideoMetadata(null);
setSearchPid(null);
setShowSearchError(true);
setIsMetadataLoading(true);
setSelectedDownloadFormat('best');
setSelectedCombinableVideoFormat('');
setSelectedCombinableAudioFormat('');
setSelectedSubtitles([]);
setSelectedPlaylistVideoIndex('1');
fetchVideoMetadata(values.url).then((metadata) => {
if (!metadata || (metadata._type !== 'video' && metadata._type !== 'playlist') || (metadata && metadata._type === 'video' && metadata.formats.length <= 0) || (metadata && metadata._type === 'playlist' && metadata.entries.length <= 0)) {
toast({
title: 'Opps! No results found',
description: 'The provided URL does not contain any downloadable content. Please check the URL and try again.',
variant: "destructive"
});
const showSearchError = useCurrentVideoMetadataStore.getState().showSearchError;
if (showSearchError) {
toast({
title: 'Oops! No results found',
description: 'The provided URL does not contain any downloadable content or you are not connected to the internet. Please check the URL, your network connection and try again.',
variant: "destructive"
});
}
}
if (metadata && (metadata._type === 'video' || metadata._type === 'playlist') && ((metadata._type === 'video' && metadata.formats.length > 0) || (metadata._type === 'playlist' && metadata.entries.length > 0))) setVideoMetadata(metadata);
if (metadata) console.log(metadata);
@@ -225,6 +235,16 @@ export default function DownloaderPage() {
});
}
const cancelSearch = async (pid: number | null) => {
setShowSearchError(false);
if (pid) {
console.log("Killing process with PID:", pid);
await invoke('kill_all_process', { pid: pid });
}
setVideoMetadata(null);
setIsMetadataLoading(false);
};
useEffect(() => {
const updateBottomBarWidth = (): void => {
if (containerRef.current && bottomBarRef.current) {
@@ -338,6 +358,17 @@ export default function DownloaderPage() {
</FormItem>
)}
/>
{isMetadataLoading && (
<Button
type="button"
variant="destructive"
size="icon"
disabled={!isMetadataLoading}
onClick={() => cancelSearch(searchPid)}
>
<X className="size-4" />
</Button>
)}
<Button
type="submit"
disabled={!videoUrl || isMetadataLoading}

View File

@@ -34,11 +34,15 @@ export const useCurrentVideoMetadataStore = create<CurrentVideoMetadataStore>((s
isMetadataLoading: false,
requestedUrl: '',
autoSubmitSearch: false,
searchPid: null,
showSearchError: true,
setVideoUrl: (url) => set(() => ({ videoUrl: url })),
setVideoMetadata: (metadata) => set(() => ({ videoMetadata: metadata })),
setIsMetadataLoading: (isLoading) => set(() => ({ isMetadataLoading: isLoading })),
setRequestedUrl: (url) => set(() => ({ requestedUrl: url })),
setAutoSubmitSearch: (autoSubmit) => set(() => ({ autoSubmitSearch: autoSubmit })),
setSearchPid: (pid) => set(() => ({ searchPid: pid })),
setShowSearchError: (showError) => set(() => ({ showSearchError: showError }))
}));
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({

View File

@@ -23,11 +23,15 @@ export interface CurrentVideoMetadataStore {
isMetadataLoading: boolean;
requestedUrl: string;
autoSubmitSearch: boolean;
searchPid: number | null;
showSearchError: boolean;
setVideoUrl: (url: string) => void;
setVideoMetadata: (metadata: RawVideoInfo | null) => void;
setIsMetadataLoading: (isLoading: boolean) => void;
setRequestedUrl: (url: string) => void;
setAutoSubmitSearch: (autoSubmit: boolean) => void;
setSearchPid: (pid: number | null) => void;
setShowSearchError: (showError: boolean) => void;
}
export interface DownloaderPageStatesStore {