import Heading from "@/components/heading"; import { Card } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useBasePathsStore, useDownloadStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { ExternalLink, FolderOpen, Loader2, LucideIcon, Monitor, Moon, Radio, RotateCcw, RotateCw, Sun, Terminal } from "lucide-react"; import { cn } from "@/lib/utils"; import { useEffect } from "react"; import { useTheme } from "@/providers/themeProvider"; import { Slider } from "@/components/ui/slider"; import { Input } from "@/components/ui/input"; import { open } from '@tauri-apps/plugin-dialog'; import { useSettings } from "@/helpers/use-settings"; import { useYtDlpUpdater } from "@/helpers/use-ytdlp-updater"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod" import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { invoke } from "@tauri-apps/api/core"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; const websocketPortSchema = z.object({ port: z.string().min(1, { message: "Websocket port is required" }) .regex(/^\d+$/, { message: "Websocket port must be a number" }) .transform((val) => parseInt(val, 10)) .refine((port) => port >= 50000 && port <= 60000, { message: "Websocket port must be between 50000 and 60000", }) }); const proxyUrlSchema = z.object({ url: z.string().min(1, { message: "Proxy URL is required" }).url({ message: "Invalid URL format" }) }); export default function SettingsPage() { const { toast } = useToast(); const { setTheme } = useTheme(); const activeTab = useSettingsPageStatesStore(state => state.activeTab); const setActiveTab = useSettingsPageStatesStore(state => state.setActiveTab); const isUsingDefaultSettings = useSettingsPageStatesStore(state => state.isUsingDefaultSettings); const ytDlpVersion = useSettingsPageStatesStore(state => state.ytDlpVersion); const isFetchingYtDlpVersion = useSettingsPageStatesStore(state => state.isFetchingYtDlpVersion); const isUpdatingYtDlp = useSettingsPageStatesStore(state => state.isUpdatingYtDlp); const ytDlpUpdateChannel = useSettingsPageStatesStore(state => state.settings.ytdlp_update_channel); const ytDlpAutoUpdate = useSettingsPageStatesStore(state => state.settings.ytdlp_auto_update); const appTheme = useSettingsPageStatesStore(state => state.settings.theme); const maxParallelDownloads = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads); const preferVideoOverPlaylist = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist); const useProxy = useSettingsPageStatesStore(state => state.settings.use_proxy); const proxyUrl = useSettingsPageStatesStore(state => state.settings.proxy_url); const websocketPort = useSettingsPageStatesStore(state => state.settings.websocket_port); const isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort); const setIsChangingWebSocketPort = useSettingsPageStatesStore(state => state.setIsChangingWebSocketPort); const isRestartingWebSocketServer = useSettingsPageStatesStore(state => state.isRestartingWebSocketServer); const setIsRestartingWebSocketServer = useSettingsPageStatesStore(state => state.setIsRestartingWebSocketServer); const downloadStates = useDownloadStatesStore(state => state.downloadStates); const ongoingDownloads = downloadStates.filter(state => ['starting', 'downloading', 'queued'].includes(state.download_status) ); const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath); const setPath = useBasePathsStore((state) => state.setPath); const { saveSettingsKey, resetSettings } = useSettings(); const { updateYtDlp } = useYtDlpUpdater(); const themeOptions: { value: string; icon: LucideIcon; label: string }[] = [ { value: 'light', icon: Sun, label: 'Light' }, { value: 'dark', icon: Moon, label: 'Dark' }, { value: 'system', icon: Monitor, label: 'System' }, ]; const proxyUrlForm = useForm>({ resolver: zodResolver(proxyUrlSchema), defaultValues: { url: proxyUrl, }, mode: "onChange", }); const watchedProxyUrl = proxyUrlForm.watch("url"); const { errors: proxyUrlFormErrors } = proxyUrlForm.formState; function handleProxyUrlSubmit(values: z.infer) { try { saveSettingsKey('proxy_url', values.url); toast({ title: "Proxy URL updated", description: `Proxy URL changed to ${values.url}`, }); } catch (error) { console.error("Error changing proxy URL:", error); toast({ title: "Failed to change proxy URL", description: "Please try again.", variant: "destructive", }); } } interface Config { port: number; } const websocketPortForm = useForm>({ resolver: zodResolver(websocketPortSchema), defaultValues: { port: websocketPort, }, mode: "onChange", }); const watchedPort = websocketPortForm.watch("port"); const { errors: websocketPortFormErrors } = websocketPortForm.formState; async function handleWebsocketPortSubmit(values: z.infer) { setIsChangingWebSocketPort(true); try { // const port = parseInt(values.port, 10); const updatedConfig: Config = await invoke("update_config", { newConfig: { port: values.port, } }); saveSettingsKey('websocket_port', updatedConfig.port); toast({ title: "Websocket port updated", description: `Websocket port changed to ${values.port}`, }); } catch (error) { console.error("Error changing websocket port:", error); toast({ title: "Failed to change websocket port", description: "Please try again.", variant: "destructive", }); } finally { setIsChangingWebSocketPort(false); } } useEffect(() => { const updateTheme = async () => { setTheme(appTheme); } updateTheme().catch(console.error); }, [appTheme]); return (
General Extension

YT-DLP

Version: {isFetchingYtDlpVersion ? 'Loading...' : ytDlpVersion ?? 'unknown'}

saveSettingsKey('ytdlp_auto_update', checked)} />

Theme

Choose app interface theme

{themeOptions.map(({ value, icon: Icon, label }) => ( ))}

Download Directory

Set default download directory

Max Parallel Downloads

Set maximum number of allowed parallel downloads

saveSettingsKey('max_parallel_downloads', value[0])} />

Prefer Video Over Playlist

Prefer only the video, if the URL refers to a video and a playlist

saveSettingsKey('prefer_video_over_playlist', checked)} />

Proxy

Use proxy for downloads, Unblocks blocked sites in your region (Download speed may affect, Some sites may not work)

saveSettingsKey('use_proxy', checked)} />
( )} />

Extension Websocket Server

{isChangingWebSocketPort || isRestartingWebSocketServer ? 'Restarting...' : 'Running' }

Websocket Port

Change extension websocket server port

( )} />

Reset Settings

Reset all setting to default

Are you absolutely sure? This action cannot be undone! it will permanently reset all settings to default. Cancel resetSettings() }>Reset
) }