import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { AlertCircle, Download, Loader2, LucideIcon, Monitor, Moon, Sun } from "lucide-react"; import { type Browser } from 'wxt/browser'; import { Textarea } from "@/components/ui/textarea"; 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 { Settings } from "@/types/settings"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { cn } from "@/lib/utils"; import { useTheme } from "@/components/theme-provider"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; const downloadFormSchema = z.object({ url: z.string().min(1, { message: "URL is required" }).url({message: "Invalid URL format." }), }); export default function HomePage() { const { setTheme } = useTheme(); const [isDownloading, setIsDownloading] = useState(false); const [isLoadingSettings, setIsLoadingSettings] = useState(true); const [isUpdatingSettings, setIsUpdatingSettings] = useState(false); const [showNotRunningAlert, setShowNotRunningAlert] = useState(false); const [settings, setSettings] = useState({ theme: "system", autofill_url: true, }); const themeOptions: { value: 'system' | 'dark' | 'light'; icon: LucideIcon; label: string }[] = [ { value: 'light', icon: Sun, label: 'Light' }, { value: 'dark', icon: Moon, label: 'Dark' }, { value: 'system', icon: Monitor, label: 'System' }, ]; const downloadForm = useForm>({ resolver: zodResolver(downloadFormSchema), defaultValues: { url: '', }, mode: "onChange", }); const watchedUrl = downloadForm.watch("url"); const handleDownload = async (url?: string) => { setIsDownloading(true); setShowNotRunningAlert(false); // Reset alert status at the beginning // Create a timeout reference with undefined type let timeoutId: NodeJS.Timeout | undefined; try { const tabs = await new Promise(resolve => { browser.tabs.query({active: true, currentWindow: true}, resolve); }); const activeTab = tabs[0]; // Create a race between the actual message and a timeout const response = await Promise.race([ browser.runtime.sendMessage({ action: 'download', url: url ?? activeTab.url }), new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error('TIMEOUT')); }, 5000); // 5 second timeout }) ]); // If we reach here, the request completed successfully if (timeoutId) clearTimeout(timeoutId); if (response) { console.log('Response from background script:', response); } } catch (error) { console.error("Download failed", error); // Check if this was a timeout error if (error instanceof Error && error.message === 'TIMEOUT') { setShowNotRunningAlert(true); } // Clear the timeout if it was some other error if (timeoutId) clearTimeout(timeoutId); } finally { setIsDownloading(false); } }; const handleDownloadSubmit = async (values: z.infer) => { await handleDownload(values.url); } const saveSettings = async (key: K, value: Settings[K]) => { setIsUpdatingSettings(true); try { // First, get current settings from storage const result = await browser.storage.local.get('settings'); const currentSettings = result.settings || {}; // Update with new value const updatedSettings = { ...currentSettings, [key]: value }; // Save to storage await browser.storage.local.set({ settings: updatedSettings }); // Update state if save was successful setSettings(prevSettings => ({ ...prevSettings, [key]: value })); console.log(`Settings ${key} updated to:`, value); } catch (error) { console.error(`Failed to save settings ${key}:`, error); } finally { setIsUpdatingSettings(false); } }; // loading the settings from storage if available, overwriting the default values when the component mounts useEffect(() => { const loadSettings = async () => { try { const result = await browser.storage.local.get('settings'); if (result.settings) { // Merge saved settings with default settings // Only override keys that exist in saved settings, keeping defaults otherwise setSettings(prevSettings => ({ ...prevSettings, ...result.settings })); } } catch (error) { console.error("Failed to load settings:", error); } finally { setIsLoadingSettings(false); } }; loadSettings(); }, []); // Auto-fill the URL field with the active tab's URL when the component mounts (if autofill is enabled) useEffect(() => { console.log({isLoadingSettings, settings}); const autoFillUrl = async () => { const tabs = await new Promise(resolve => { browser.tabs.query({active: true, currentWindow: true}, resolve); }); const activeTab = tabs[0]; if (activeTab && activeTab.url) { downloadForm.setValue("url", activeTab.url); await downloadForm.trigger("url"); } } if (!isLoadingSettings && settings.autofill_url) { autoFillUrl(); } }, [isLoadingSettings, settings.autofill_url]); // Listen for tab URL changes and update the form value accordingly (if autofill is enabled) useEffect(() => { if (isLoadingSettings || !settings.autofill_url) return; const handleTabUrlChange = async (tabId: number, changeInfo: Browser.tabs.TabChangeInfo) => { if (changeInfo.status === "complete") { browser.tabs.get(tabId).then(async (tab) => { if (tab.active && tab.url) { downloadForm.setValue("url", tab.url); await downloadForm.trigger("url"); } }); } } browser.tabs.onUpdated.addListener(handleTabUrlChange); return () => { browser.tabs.onUpdated.removeListener(handleTabUrlChange); } }, [isLoadingSettings, settings.autofill_url]); // Update the theme when settings change useEffect(() => { const updateTheme = async () => { setTheme(settings.theme); } updateTheme().catch(console.error); }, [settings.theme]); return (
{themeOptions.map(({ value, icon: Icon, label }) => ( ))}
saveSettings("autofill_url", checked)} />
(