Skip to content

Commit 2af3389

Browse files
Merge pull request #174 from FabLrc/feature/undo-redo
feat: implement undo/redo functionality in video editor
2 parents 4f68df1 + 4b79909 commit 2af3389

19 files changed

Lines changed: 657 additions & 484 deletions

electron/electron-env.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ interface Window {
2727
getSources: (opts: Electron.SourcesOptions) => Promise<ProcessedDesktopSource[]>;
2828
switchToEditor: () => Promise<void>;
2929
openSourceSelector: () => Promise<void>;
30-
selectSource: (source: any) => Promise<any>;
31-
getSelectedSource: () => Promise<any>;
30+
selectSource: (source: ProcessedDesktopSource) => Promise<ProcessedDesktopSource | null>;
31+
getSelectedSource: () => Promise<ProcessedDesktopSource | null>;
32+
getAssetBasePath: () => Promise<string | null>;
3233
storeRecordedVideo: (
3334
videoData: ArrayBuffer,
3435
fileName: string,

electron/preload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
2020
openSourceSelector: () => {
2121
return ipcRenderer.invoke("open-source-selector");
2222
},
23-
selectSource: (source: any) => {
23+
selectSource: (source: ProcessedDesktopSource) => {
2424
return ipcRenderer.invoke("select-source", source);
2525
},
2626
getSelectedSource: () => {

src/components/video-editor/CropControl.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
108108
if (isDragging) {
109109
try {
110110
e.currentTarget.releasePointerCapture(e.pointerId);
111-
} catch {}
111+
} catch {
112+
// Pointer may already be released; ignore.
113+
}
112114
}
113115
setIsDragging(null);
114116
};

src/components/video-editor/KeyboardShortcutsHelp.tsx

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
import { HelpCircle, Settings2 } from "lucide-react";
2-
import { useEffect, useState } from "react";
32
import { useShortcuts } from "@/contexts/ShortcutsContext";
4-
import { formatBinding, SHORTCUT_ACTIONS, SHORTCUT_LABELS } from "@/lib/shortcuts";
5-
import { formatShortcut } from "@/utils/platformUtils";
3+
import { FIXED_SHORTCUTS, formatBinding, SHORTCUT_ACTIONS, SHORTCUT_LABELS } from "@/lib/shortcuts";
64

75
export function KeyboardShortcutsHelp() {
86
const { shortcuts, isMac, openConfig } = useShortcuts();
97

10-
const [scrollLabels, setScrollLabels] = useState({
11-
pan: "Shift + Ctrl + Scroll",
12-
zoom: "Ctrl + Scroll",
13-
});
14-
15-
useEffect(() => {
16-
Promise.all([
17-
formatShortcut(["shift", "mod", "Scroll"]),
18-
formatShortcut(["mod", "Scroll"]),
19-
]).then(([pan, zoom]) => setScrollLabels({ pan, zoom }));
20-
}, []);
21-
228
return (
239
<div className="relative group">
2410
<HelpCircle className="w-4 h-4 text-slate-500 hover:text-[#34B27B] transition-colors cursor-help" />
@@ -47,25 +33,20 @@ export function KeyboardShortcutsHelp() {
4733
</div>
4834
))}
4935

50-
<div className="pt-1 border-t border-white/5 mt-1">
51-
<div className="flex items-center justify-between">
52-
<span className="text-slate-400">Pan Timeline</span>
53-
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">
54-
{scrollLabels.pan}
55-
</kbd>
56-
</div>
57-
<div className="flex items-center justify-between mt-1.5">
58-
<span className="text-slate-400">Zoom Timeline</span>
59-
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">
60-
{scrollLabels.zoom}
61-
</kbd>
62-
</div>
63-
<div className="flex items-center justify-between mt-1.5">
64-
<span className="text-slate-400">Cycle Annotations</span>
65-
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">
66-
Tab
67-
</kbd>
68-
</div>
36+
<div className="pt-1 border-t border-white/5 mt-1 space-y-1.5">
37+
{FIXED_SHORTCUTS.map((fixed) => (
38+
<div key={fixed.label} className="flex items-center justify-between">
39+
<span className="text-slate-400">{fixed.label}</span>
40+
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">
41+
{isMac
42+
? fixed.display
43+
.replace(/Ctrl/g, "⌘")
44+
.replace(/Shift/g, "⇧")
45+
.replace(/Alt/g, "⌥")
46+
: fixed.display}
47+
</kbd>
48+
</div>
49+
))}
6950
</div>
7051
</div>
7152
</div>

src/components/video-editor/SettingsPanel.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type {
3838
AnnotationRegion,
3939
AnnotationType,
4040
CropRegion,
41+
FigureData,
4142
PlaybackSpeed,
4243
ZoomDepth,
4344
} from "./types";
@@ -86,14 +87,17 @@ interface SettingsPanelProps {
8687
onTrimDelete?: (id: string) => void;
8788
shadowIntensity?: number;
8889
onShadowChange?: (intensity: number) => void;
90+
onShadowCommit?: () => void;
8991
showBlur?: boolean;
9092
onBlurChange?: (showBlur: boolean) => void;
9193
motionBlurEnabled?: boolean;
9294
onMotionBlurChange?: (enabled: boolean) => void;
9395
borderRadius?: number;
9496
onBorderRadiusChange?: (radius: number) => void;
97+
onBorderRadiusCommit?: () => void;
9598
padding?: number;
9699
onPaddingChange?: (padding: number) => void;
100+
onPaddingCommit?: () => void;
97101
cropRegion?: CropRegion;
98102
onCropChange?: (region: CropRegion) => void;
99103
aspectRatio: AspectRatio;
@@ -118,7 +122,7 @@ interface SettingsPanelProps {
118122
onAnnotationContentChange?: (id: string, content: string) => void;
119123
onAnnotationTypeChange?: (id: string, type: AnnotationType) => void;
120124
onAnnotationStyleChange?: (id: string, style: Partial<AnnotationRegion["style"]>) => void;
121-
onAnnotationFigureDataChange?: (id: string, figureData: any) => void;
125+
onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void;
122126
onAnnotationDelete?: (id: string) => void;
123127
selectedSpeedId?: string | null;
124128
selectedSpeedValue?: PlaybackSpeed | null;
@@ -148,14 +152,17 @@ export function SettingsPanel({
148152
onTrimDelete,
149153
shadowIntensity = 0,
150154
onShadowChange,
155+
onShadowCommit,
151156
showBlur,
152157
onBlurChange,
153158
motionBlurEnabled = false,
154159
onMotionBlurChange,
155160
borderRadius = 0,
156161
onBorderRadiusChange,
162+
onBorderRadiusCommit,
157163
padding = 50,
158164
onPaddingChange,
165+
onPaddingCommit,
159166
cropRegion,
160167
onCropChange,
161168
aspectRatio,
@@ -196,7 +203,7 @@ export function SettingsPanel({
196203
try {
197204
const resolved = await Promise.all(WALLPAPER_RELATIVE.map((p) => getAssetPath(p)));
198205
if (mounted) setWallpaperPaths(resolved);
199-
} catch {
206+
} catch (_err) {
200207
if (mounted) setWallpaperPaths(WALLPAPER_RELATIVE.map((p) => `/${p}`));
201208
}
202209
})();
@@ -480,6 +487,7 @@ export function SettingsPanel({
480487
<Slider
481488
value={[shadowIntensity]}
482489
onValueChange={(values) => onShadowChange?.(values[0])}
490+
onValueCommit={() => onShadowCommit?.()}
483491
min={0}
484492
max={1}
485493
step={0.01}
@@ -494,6 +502,7 @@ export function SettingsPanel({
494502
<Slider
495503
value={[borderRadius]}
496504
onValueChange={(values) => onBorderRadiusChange?.(values[0])}
505+
onValueCommit={() => onBorderRadiusCommit?.()}
497506
min={0}
498507
max={16}
499508
step={0.5}
@@ -508,6 +517,7 @@ export function SettingsPanel({
508517
<Slider
509518
value={[padding]}
510519
onValueChange={(values) => onPaddingChange?.(values[0])}
520+
onValueCommit={() => onPaddingCommit?.()}
511521
min={0}
512522
max={100}
513523
step={1}
@@ -620,7 +630,9 @@ export function SettingsPanel({
620630
s.replace(/^file:\/\//, "").replace(/^\//, "");
621631
if (clean(selected).endsWith(clean(path))) return true;
622632
if (clean(path).endsWith(clean(selected))) return true;
623-
} catch {}
633+
} catch {
634+
// Best-effort comparison; fallback to strict match.
635+
}
624636
return false;
625637
})();
626638
return (

src/components/video-editor/ShortcutsConfigDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function ShortcutsConfigDialog() {
8484

8585
window.addEventListener("keydown", handleCapture, { capture: true });
8686
return () => window.removeEventListener("keydown", handleCapture, { capture: true });
87-
}, [captureFor]);
87+
}, [captureFor, draft]);
8888

8989
const handleSwap = useCallback(() => {
9090
if (!conflict || conflict.conflictWith.type !== "configurable") return;

0 commit comments

Comments
 (0)