Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/components/DownloadResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const SHARE_TWEET_TEXT =

interface Props {
result: ExportResult;
exportHistory: ExportHistoryItem[]; // NEW: Added history array prop
onReset: () => void;
soundOnCompletion: boolean;
}
Expand Down Expand Up @@ -41,6 +42,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P

return (
<div className="p-5 bg-[var(--surface)] border border-[var(--border)] rounded-xl space-y-4">
{/* Current Export Success Header */}
<div className="flex items-center gap-4">
<div className="w-12 h-12 shrink-0">
<LottiePlayer animationData={successAnim} loop={false} autoplay />
Expand All @@ -51,6 +53,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P
</div>
</div>

{/* Current Export Details */}
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="bg-[var(--bg)] rounded-lg p-3 border border-[var(--border)]">
<p className="text-[10px] font-heading font-semibold uppercase tracking-wider text-[var(--muted)] mb-1">Resolution</p>
Expand Down Expand Up @@ -143,6 +146,39 @@ export default function DownloadResult({ result, onReset, soundOnCompletion }: P
Share on X
</a>
</div>

{/* NEW: Export History Panel */}
{exportHistory && exportHistory.length > 0 && (
<details className="group border border-[var(--border)] rounded-lg bg-[var(--bg)] mt-4 transition-all">
<summary className="flex cursor-pointer items-center justify-between p-3 font-heading text-sm font-semibold text-[var(--text)] hover:text-film-500 transition-colors">
<span className="flex items-center gap-2">
<History size={16} />
Recent Exports ({exportHistory.length})
</span>
<span className="text-xs text-[var(--muted)] group-open:hidden">Click to view</span>
</summary>
<div className="p-3 border-t border-[var(--border)] space-y-3">
{exportHistory.map((item) => (
<div key={item.id} className="flex justify-between items-center text-xs text-[var(--muted)] bg-[var(--surface)] p-2 rounded border border-[var(--border)]">
<div className="flex flex-col gap-1">
<span className="font-medium text-[var(--text)]">
{new Date(item.timestamp).toLocaleTimeString()}
</span>
<span>{item.result.width}x{item.result.height} • {formatBytes(item.result.size)}</span>
</div>
<a
href={item.result.blobUrl}
download={`reframe_history_${item.result.width}x${item.result.height}.${item.result.format}`}
className="flex items-center gap-1 px-3 py-1.5 bg-[var(--bg)] border border-[var(--border)] text-[var(--text)] rounded hover:bg-film-600 hover:text-white hover:border-film-600 transition-all font-medium"
>
<Download size={12} />
Save
</a>
</div>
))}
</div>
</details>
)}
</div>
);
}
}
1 change: 1 addition & 0 deletions src/components/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default function VideoEditor() {
}
}, [status]);


const isProcessing = status === "loading-engine" || status === "exporting";

const videoSrc = useMemo(
Expand Down
54 changes: 54 additions & 0 deletions src/hooks/useVideoEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EditRecipe>) => {
setRecipe((prev) => ({ ...prev, ...patch }));
}, []);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ export const MAX_FILE_SIZE =
2 * 1024 * 1024 * 1024;

export const WARNING_FILE_SIZE =
500 * 1024 * 1024; // 500MB
500 * 1024 * 1024; // 500MB
Loading