Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
c12daf4
Add undo/redo support for export settings
siddjs19 May 14, 2026
633b333
Merge remote-tracking branch 'upstream/main' into feat/undo-redo-export
siddjs19 May 15, 2026
f8090fe
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 16, 2026
e9a40c8
getting prev merge
siddjs19 May 16, 2026
23d87ad
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 16, 2026
58d2928
Add undo/redo support for export settings
siddjs19 May 14, 2026
6bdc15b
getting prev merge
siddjs19 May 16, 2026
e104115
staged changes
siddjs19 May 16, 2026
dbbea19
Merge branch 'feat/undo-redo-export' of https://github.com/siddjs19/r…
siddjs19 May 16, 2026
7e8bad7
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 16, 2026
36a75ca
Add undo/redo support for export settings
siddjs19 May 14, 2026
d99238f
getting prev merge
siddjs19 May 16, 2026
b3b2b2b
staged changes
siddjs19 May 16, 2026
92e03ce
Add undo/redo support for export settings
siddjs19 May 14, 2026
20f637b
resolving errors
siddjs19 May 16, 2026
2736f0f
Add undo/redo support for export settings
siddjs19 May 14, 2026
abf355f
getting prev merge
siddjs19 May 16, 2026
37aec1d
staged changes
siddjs19 May 16, 2026
c9e9e9b
Add undo/redo support for export settings
siddjs19 May 14, 2026
0e24944
readding cancleExport and restSetting
siddjs19 May 16, 2026
26aa6dd
staged changes
siddjs19 May 16, 2026
911e540
Add undo/redo support for export settings
siddjs19 May 14, 2026
08fa53f
Merge branch 'feat/undo-redo-export' of https://github.com/siddjs19/r…
siddjs19 May 16, 2026
8fc9ece
Merge remote-tracking branch 'upstream/main' into feat/undo-redo-export
siddjs19 May 16, 2026
032313e
Add undo/redo support for export settings
siddjs19 May 14, 2026
c530756
getting prev merge
siddjs19 May 16, 2026
c2d455e
fix merge
siddjs19 May 16, 2026
879d356
Add undo/redo support for export settings
siddjs19 May 14, 2026
a5d1fab
readding cancleExport and restSetting
siddjs19 May 16, 2026
255d501
staged changes
siddjs19 May 16, 2026
bc34e51
Add undo/redo support for export settings
siddjs19 May 14, 2026
aa8a9b4
Add undo/redo support for export settings
siddjs19 May 14, 2026
453f067
staged changes
siddjs19 May 16, 2026
9dc88ae
Add undo/redo support for export settings
siddjs19 May 14, 2026
f7aa71c
Add undo/redo support for export settings
siddjs19 May 14, 2026
54c7cef
staged changes
siddjs19 May 16, 2026
57b3e49
Add undo/redo support for export settings
siddjs19 May 14, 2026
23f8e5e
rebasing
siddjs19 May 17, 2026
859ffc0
removing errors
siddjs19 May 17, 2026
114b906
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 17, 2026
e611447
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 17, 2026
1b1f37e
Remove resetSettings function from useVideoEditor
siddjs19 May 17, 2026
30b6e16
merges
siddjs19 May 17, 2026
fba8536
fix merge
siddjs19 May 16, 2026
037747e
Add undo/redo support for export settings
siddjs19 May 14, 2026
9204aa5
readding cancleExport and restSetting
siddjs19 May 16, 2026
94e5b83
Add undo/redo support for export settings
siddjs19 May 14, 2026
451548f
staged changes
siddjs19 May 16, 2026
b1627f3
Add undo/redo support for export settings
siddjs19 May 14, 2026
b559fec
Add undo/redo support for export settings
siddjs19 May 14, 2026
3ae2540
Add undo/redo support for export settings
siddjs19 May 14, 2026
30c2a54
Add undo/redo support for export settings
siddjs19 May 14, 2026
4501445
Add undo/redo support for export settings
siddjs19 May 14, 2026
7d36cf6
readding cancleExport and restSetting
siddjs19 May 16, 2026
39b4a15
Add undo/redo support for export settings
siddjs19 May 14, 2026
1abcbe8
Add undo/redo support for export settings
siddjs19 May 14, 2026
d4c1079
Add undo/redo support for export settings
siddjs19 May 14, 2026
89929b2
Add undo/redo support for export settings
siddjs19 May 14, 2026
441e5ed
Add undo/redo support for export settings
siddjs19 May 14, 2026
4e8faea
Remove resetSettings function from useVideoEditor
siddjs19 May 17, 2026
858382c
Merge branch 'feat/undo-redo-export' of https://github.com/siddjs19/r…
siddjs19 May 17, 2026
373dc97
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 18, 2026
887d7fd
added cancExport
siddjs19 May 18, 2026
a2b8c5b
Merge branch 'feat/undo-redo-export' of https://github.com/siddjs19/r…
siddjs19 May 18, 2026
4087dcc
removed duplicate sethistory
siddjs19 May 18, 2026
ebd82e5
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 18, 2026
d055827
"resolving error"
siddjs19 May 18, 2026
e65a47a
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 19, 2026
09b1ba7
merges
siddjs19 May 17, 2026
92e8b07
Add undo/redo support for export settings
siddjs19 May 14, 2026
72c718a
readding cancleExport and restSetting
siddjs19 May 16, 2026
973f623
staged changes
siddjs19 May 16, 2026
0db8175
Add undo/redo support for export settings
siddjs19 May 14, 2026
06beb2a
Add undo/redo support for export settings
siddjs19 May 14, 2026
9a72646
Add undo/redo support for export settings
siddjs19 May 14, 2026
a11d21a
Add undo/redo support for export settings
siddjs19 May 14, 2026
e15021a
readding cancleExport and restSetting
siddjs19 May 16, 2026
7b03195
Add undo/redo support for export settings
siddjs19 May 14, 2026
3f67c27
Add undo/redo support for export settings
siddjs19 May 14, 2026
0c39c53
Remove resetSettings function from useVideoEditor
siddjs19 May 17, 2026
86121c8
Add undo/redo support for export settings
siddjs19 May 14, 2026
9bc90b5
Add undo/redo support for export settings
siddjs19 May 14, 2026
9349290
readding cancleExport and restSetting
siddjs19 May 16, 2026
e20f418
Add undo/redo support for export settings
siddjs19 May 14, 2026
552506e
Add undo/redo support for export settings
siddjs19 May 14, 2026
e0bf07a
Add undo/redo support for export settings
siddjs19 May 14, 2026
45c724f
Add undo/redo support for export settings
siddjs19 May 14, 2026
086d4ee
Remove resetSettings function from useVideoEditor
siddjs19 May 17, 2026
3477418
added setrecipe again
siddjs19 May 19, 2026
7a81764
Merge branch 'feat/undo-redo-export' of https://github.com/siddjs19/r…
siddjs19 May 19, 2026
2f95a9f
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 19, 2026
10cada5
Merge branch 'main' into feat/undo-redo-export
siddjs19 May 20, 2026
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
37 changes: 34 additions & 3 deletions src/components/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export default function VideoEditor() {
file, duration, recipe, status, progress,
result, error, updateRecipe,
handleFileSelect, fileError, handleExport, cancelExport, reset, resetSettings,
undo,redo,canUndo,canRedo,
videoRef,
seekTo,
overlayFile, setOverlayFile,
Expand Down Expand Up @@ -142,7 +143,7 @@ export default function VideoEditor() {
});
}
}, [status]);

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

const videoSrc = useMemo(
Expand All @@ -158,7 +159,7 @@ export default function VideoEditor() {

return (
<div className="min-h-screen relative flex flex-col" style={{ background: "var(--bg)" }}>
<ExportOverlay status={status} progress={progress} onCancel={cancelExport} />
<ExportOverlay status={status} progress={progress} />
<OnboardingTour />

<div aria-live="polite" aria-atomic="true" className="sr-only">
Expand Down Expand Up @@ -430,7 +431,37 @@ export default function VideoEditor() {
</button>
</div>
</div>

<div className="flex gap-3">
<button
type="button"
onClick={undo}
disabled={!canUndo || isProcessing}
className={cn(
"flex-1 py-3 rounded-xl border transition-all duration-200",
"font-heading text-xs uppercase tracking-widest",
canUndo && !isProcessing
? "border-[var(--border)] bg-[var(--surface)] hover:border-film-500 hover:text-film-600"
: "border-[var(--border)] text-[var(--muted)] cursor-not-allowed opacity-50"
)}
>
Undo
</button>

<button
type="button"
onClick={redo}
disabled={!canRedo || isProcessing}
className={cn(
"flex-1 py-3 rounded-xl border transition-all duration-200",
"font-heading text-xs uppercase tracking-widest",
canRedo && !isProcessing
? "border-[var(--border)] bg-[var(--surface)] hover:border-film-500 hover:text-film-600"
: "border-[var(--border)] text-[var(--muted)] cursor-not-allowed opacity-50"
)}
>
Redo
</button>
</div>
<KeyboardShortcutsPanel />

<button
Expand Down
99 changes: 82 additions & 17 deletions src/hooks/useVideoEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function extractMetadata(file: File): Promise<{ width: number; height: nu
});
URL.revokeObjectURL(url);
};

video.onerror = () => {
clearTimeout(timeout)
URL.revokeObjectURL(url);
Expand Down Expand Up @@ -117,6 +118,10 @@ function validateRecipe(recipe: EditRecipe, duration: number ): string | null {
export function useVideoEditor() {
const [file, setFile] = useState<File | null>(null);
const [duration, setDuration] = useState<number>(0);

const [history, setHistory] = useState<EditRecipe[]>([DEFAULT_RECIPE]);
const [historyIndex, setHistoryIndex] = useState(0);

const [videoMetadata, setVideoMetadata] = useState<{
width: number;
height: number;
Expand All @@ -128,6 +133,7 @@ export function useVideoEditor() {
typeof window !== "undefined" &&
localStorage.getItem("soundOnCompletion") === "true",
});

const [status, setStatus] = useState<ExportStatus>("idle");
const [progress, setProgress] = useState(0);
const [result, setResult] = useState<ExportResult | null>(null);
Expand All @@ -147,16 +153,27 @@ export function useVideoEditor() {
const [overlaySize, setOverlaySize] = useState(150);
const [overlayOpacity, setOverlayOpacity] = useState(100);

const updateRecipe = useCallback((patch: Partial<EditRecipe>) => {
setRecipe((prev) => {
const next = { ...prev, ...patch };
// GIF has no audio — force keepAudio off
if (next.format === "gif") {
next.keepAudio = false;
}
return next;
});
}, []);
const updateRecipe = useCallback((patch: Partial<EditRecipe>) => {
setHistory((prevHistory) => {
const trimmedHistory = prevHistory.slice(0, historyIndex + 1);

const newRecipe = {
...trimmedHistory[historyIndex],
...patch,
};
if (newRecipe.format === "gif") {
newRecipe.keepAudio = false;
}
const updatedHistory = [...trimmedHistory, newRecipe];

return updatedHistory.length > 20
? updatedHistory.slice(-20)
: updatedHistory;
});

setHistoryIndex((prev) => Math.min(prev + 1, 19));
}, [historyIndex]);

const isValidValue = (key: keyof EditRecipe, val: any): boolean => {
switch (key) {
case "preset":
Expand Down Expand Up @@ -190,6 +207,13 @@ export function useVideoEditor() {
}
};

const undo = useCallback(() => {
setHistoryIndex((prev) => Math.max(0, prev - 1));
}, []);

const redo = useCallback(() => {
setHistoryIndex((prev) => Math.min(history.length - 1, prev + 1));
}, [history.length]);
useEffect(() => {
if (typeof window === "undefined") return;
try {
Expand Down Expand Up @@ -292,6 +316,10 @@ export function useVideoEditor() {
return getPresetById(suggestPreset(videoMetadata.width, videoMetadata.height)) ?? null;
}, [videoMetadata]);

useEffect(() => {
setRecipe(history[historyIndex]);
}, [historyIndex, history]);

const handleFileSelect = useCallback(async (selectedFile: File) => {
setResult(null);
setStatus("idle");
Expand All @@ -303,6 +331,16 @@ export function useVideoEditor() {
return;
}

setHistory([
{
...DEFAULT_RECIPE,
trimStart: 0,
trimEnd: null,
},
]);

setHistoryIndex(0);

setFileError("");

// LAYER 0: Size check
Expand All @@ -312,6 +350,7 @@ export function useVideoEditor() {
return;
}

// LAYER 1: Extension check
const validExtensions = ['.mp4', '.mov', '.avi', '.webm', '.mkv'];
const filename = selectedFile.name.toLowerCase();
const hasValidExtension = validExtensions.some(ext => filename.endsWith(ext));
Expand Down Expand Up @@ -443,6 +482,7 @@ export function useVideoEditor() {
} else {
document.title = DEFAULT_TITLE;
}

return () => {
document.title = DEFAULT_TITLE;
};
Expand All @@ -466,22 +506,43 @@ export function useVideoEditor() {

useEffect(() => {
const handleKeydown = (e: KeyboardEvent) => {
const isModifier = e.ctrlKey || e.metaKey;

if (
(e.ctrlKey || e.metaKey) &&
isModifier &&
e.key === "Enter" &&
file &&
status !== "loading-engine" &&
status !== "exporting"
) {
e.preventDefault();
handleExport();
return;
}

if (isModifier && e.key.toLowerCase() === "z" && !e.shiftKey) {
e.preventDefault();
undo();
return;
}

if (
isModifier &&
(
(e.key.toLowerCase() === "z" && e.shiftKey) ||
e.key.toLowerCase() === "y"
)
) {
e.preventDefault();
redo();
}
};

document.addEventListener("keydown", handleKeydown);
return () => {
document.removeEventListener("keydown", handleKeydown);
};
}, [file, status, handleExport]);
}, [file, status, handleExport,undo, redo]);

// M key: toggle audio mute — only when a file is loaded and focus isn't in a text field
useEffect(() => {
Expand Down Expand Up @@ -517,7 +578,7 @@ export function useVideoEditor() {
},[result?.blobUrl])

const resetSettings = useCallback(() => {
setRecipe(DEFAULT_RECIPE);
updateRecipe(DEFAULT_RECIPE);
}, []);

const cancelExport = useCallback(() => {
Expand All @@ -530,13 +591,13 @@ export function useVideoEditor() {
setError(null);
}, []);


const reset = useCallback(() => {
if (result?.blobUrl) URL.revokeObjectURL(result.blobUrl);
setFile(null);
setVideoMetadata(null);
setDuration(0);
setRecipe(DEFAULT_RECIPE);
setHistory([DEFAULT_RECIPE]);
setHistoryIndex(0);
setStatus("idle");
setProgress(0);
setResult(null);
Expand Down Expand Up @@ -577,11 +638,15 @@ export function useVideoEditor() {
videoRef,
seekTo,
updateRecipe,
undo,
redo,
canUndo: historyIndex > 0,
canRedo: historyIndex < history.length - 1,
handleFileSelect,
fileError,
handleExport,
cancelExport,
reset,
cancelExport,
resetSettings,
musicFile,
setMusicFile,
Expand All @@ -601,4 +666,4 @@ export function useVideoEditor() {
setOverlayOpacity,
recommendedPreset,
};
}
}
Loading