diff --git a/src/components/DownloadResult.tsx b/src/components/DownloadResult.tsx index 35bb9460..209f706b 100644 --- a/src/components/DownloadResult.tsx +++ b/src/components/DownloadResult.tsx @@ -13,6 +13,7 @@ const SHARE_TWEET_TEXT = interface Props { result: ExportResult; + exportHistory: ExportHistoryItem[]; // NEW: Added history array prop onReset: () => void; soundOnCompletion: boolean; } @@ -41,6 +42,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P return (
+ {/* Current Export Success Header */}
@@ -51,6 +53,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P
+ {/* Current Export Details */}

Resolution

@@ -143,6 +146,39 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P Share on X
+ + {/* NEW: Export History Panel */} + {exportHistory && exportHistory.length > 0 && ( +
+ + + + Recent Exports ({exportHistory.length}) + + Click to view + +
+ {exportHistory.map((item) => ( +
+
+ + {new Date(item.timestamp).toLocaleTimeString()} + + {item.result.width}x{item.result.height} • {formatBytes(item.result.size)} +
+ + + Save + +
+ ))} +
+
+ )}
); -} +} \ No newline at end of file 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..981ed859 100644 --- a/src/hooks/useVideoEditor.ts +++ b/src/hooks/useVideoEditor.ts @@ -147,6 +147,39 @@ export function useVideoEditor() { const [overlaySize, setOverlaySize] = useState(150); const [overlayOpacity, setOverlayOpacity] = useState(100); + // --- LocalStorage Persistence Logic --- + + // 1. Load saved settings on mount (Client-side only) + useEffect(() => { + const savedToggle = localStorage.getItem('rememberSettings') === 'true'; + setRememberSettings(savedToggle); + + if (savedToggle) { + const savedRecipe = localStorage.getItem('videoEditorRecipe'); + if (savedRecipe) { + try { + const parsedRecipe = JSON.parse(savedRecipe); + setRecipe(parsedRecipe); + } catch (error) { + console.error("Failed to parse saved video recipe", error); + } + } + } + }, []); + + // 2. Save settings when recipe or toggle changes + useEffect(() => { + localStorage.setItem('rememberSettings', String(rememberSettings)); + + if (rememberSettings) { + localStorage.setItem('videoEditorRecipe', JSON.stringify(recipe)); + } else { + localStorage.removeItem('videoEditorRecipe'); + } + }, [rememberSettings, recipe]); + + // --- End LocalStorage Logic --- + const updateRecipe = useCallback((patch: Partial) => { setRecipe((prev) => ({ ...prev, ...patch })); }, []); @@ -304,6 +337,27 @@ export function useVideoEditor() { if (exportCancelledRef.current) return; setResult(exportResult); + + // NEW: Update export history and safely manage memory limits + setExportHistory((prevHistory) => { + const newItem: ExportHistoryItem = { + id: Date.now().toString(), + timestamp: Date.now(), + result: exportResult, + recipe: recipe, // Captures the exact settings used + }; + + const updatedHistory = [newItem, ...prevHistory]; + + // MEMORY LEAK MANAGER: Revoke URL of the 6th item before throwing it away + if (updatedHistory.length > 5) { + URL.revokeObjectURL(updatedHistory[5].result.blobUrl); + console.log("Memory Released: Oldest history blob revoked."); + } + + return updatedHistory.slice(0, 5); // Ensure only 5 are kept in state + }); + setStatus("done"); } catch (err) { if (exportCancelledRef.current) return; diff --git a/src/lib/types.ts b/src/lib/types.ts index bf167094..8df7b392 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -86,4 +86,4 @@ export const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024; export const WARNING_FILE_SIZE = - 500 * 1024 * 1024; // 500MB \ No newline at end of file + 500 * 1024 * 1024; // 500MB