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;