diff --git a/src/components/ExportSettings.tsx b/src/components/ExportSettings.tsx index 026f0760..84cad51d 100644 --- a/src/components/ExportSettings.tsx +++ b/src/components/ExportSettings.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState } from "react"; import { EditRecipe } from "@/lib/types"; import { cn } from "@/lib/utils"; import { @@ -40,6 +41,18 @@ export default function ExportSettings({ ) ); + // NEW: Copy link logic + const handleCopyLink = () => { + // window.location.href automatically includes the #hash we set in the hook! + navigator.clipboard.writeText(window.location.href); + setCopied(true); + + // Reset the button back to normal after 2 seconds + setTimeout(() => { + setCopied(false); + }, 2000); + }; + return ( <>
diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index d43f5d18..a921018a 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -74,6 +74,7 @@ export default function VideoEditor() { } }, [status]); + const isProcessing = status === "loading-engine" || status === "exporting"; const videoSrc = useMemo( diff --git a/src/hooks/useVideoEditor.ts b/src/hooks/useVideoEditor.ts index a2283128..e2a2752b 100644 --- a/src/hooks/useVideoEditor.ts +++ b/src/hooks/useVideoEditor.ts @@ -147,6 +147,70 @@ export function useVideoEditor() { const [overlaySize, setOverlaySize] = useState(150); const [overlayOpacity, setOverlayOpacity] = useState(100); + // --- Initialization (Hash & LocalStorage) --- + useEffect(() => { + const savedToggle = localStorage.getItem('rememberSettings') === 'true'; + setRememberSettings(savedToggle); + + // 1. Check URL Hash First (Highest Priority) + if (window.location.hash && window.location.hash.length > 1) { + try { + const hashParams = new URLSearchParams(window.location.hash.slice(1)); + const parsedRecipe: Partial = {}; + + // Carefully cast strings back to proper types + if (hashParams.has("preset")) parsedRecipe.preset = hashParams.get("preset")!; + if (hashParams.has("customWidth")) parsedRecipe.customWidth = Number(hashParams.get("customWidth")); + if (hashParams.has("customHeight")) parsedRecipe.customHeight = Number(hashParams.get("customHeight")); + if (hashParams.has("framing")) parsedRecipe.framing = hashParams.get("framing") as "fit" | "fill"; + if (hashParams.has("trimStart")) parsedRecipe.trimStart = Number(hashParams.get("trimStart")); + if (hashParams.has("trimEnd")) parsedRecipe.trimEnd = hashParams.get("trimEnd") === "null" ? null : Number(hashParams.get("trimEnd")); + if (hashParams.has("rotate")) parsedRecipe.rotate = Number(hashParams.get("rotate")) as 0 | 90 | 180 | 270; + if (hashParams.has("keepAudio")) parsedRecipe.keepAudio = hashParams.get("keepAudio") === "true"; + if (hashParams.has("speed")) parsedRecipe.speed = Number(hashParams.get("speed")); + if (hashParams.has("quality")) parsedRecipe.quality = Number(hashParams.get("quality")); + + setRecipe((prev) => ({ ...prev, ...parsedRecipe })); + return; // Skip localStorage if hash exists + } catch (e) { + console.error("Failed to parse URL hash settings", e); + } + } + + // 2. Fallback to LocalStorage + if (savedToggle) { + const savedRecipe = localStorage.getItem('videoEditorRecipe'); + if (savedRecipe) { + try { + setRecipe(JSON.parse(savedRecipe)); + } catch (error) { + console.error("Failed to parse saved video recipe", error); + } + } + } + }, []); + + // --- Save to LocalStorage --- + useEffect(() => { + localStorage.setItem('rememberSettings', String(rememberSettings)); + if (rememberSettings) { + localStorage.setItem('videoEditorRecipe', JSON.stringify(recipe)); + } else { + localStorage.removeItem('videoEditorRecipe'); + } + }, [rememberSettings, recipe]); + + // --- NEW: Sync state to URL Hash --- + useEffect(() => { + const params = new URLSearchParams(); + Object.entries(recipe).forEach(([key, value]) => { + params.set(key, String(value)); + }); + + // replaceState prevents creating a massive browser history + window.history.replaceState(null, '', `#${params.toString()}`); + }, [recipe]); + const updateRecipe = useCallback((patch: Partial) => { setRecipe((prev) => ({ ...prev, ...patch })); }, []); @@ -304,6 +368,24 @@ export function useVideoEditor() { if (exportCancelledRef.current) return; setResult(exportResult); + + setExportHistory((prevHistory) => { + const newItem: ExportHistoryItem = { + id: Date.now().toString(), + timestamp: Date.now(), + result: exportResult, + recipe: recipe, + }; + + const updatedHistory = [newItem, ...prevHistory]; + + if (updatedHistory.length > 5) { + URL.revokeObjectURL(updatedHistory[5].result.blobUrl); + } + + return updatedHistory.slice(0, 5); + }); + setStatus("done"); } catch (err) { if (exportCancelledRef.current) return;