fix: Fixing speed undoable and add undo/redo to the list of shortucts configuration

This commit is contained in:
FabLrc
2026-03-02 16:26:42 +01:00
parent e6e3abb88c
commit 6d44dafd96
4 changed files with 33 additions and 64 deletions
@@ -1,28 +1,10 @@
import { HelpCircle, Settings2 } from "lucide-react";
import { useState, useEffect } from "react";
import { formatShortcut } from "@/utils/platformUtils";
import { useShortcuts } from "@/contexts/ShortcutsContext";
import { formatBinding, SHORTCUT_LABELS, SHORTCUT_ACTIONS } from "@/lib/shortcuts";
import { formatBinding, SHORTCUT_LABELS, SHORTCUT_ACTIONS, FIXED_SHORTCUTS } from "@/lib/shortcuts";
export function KeyboardShortcutsHelp() {
const { shortcuts, isMac, openConfig } = useShortcuts();
const [scrollLabels, setScrollLabels] = useState({ pan: 'Shift + Ctrl + Scroll', zoom: 'Ctrl + Scroll' });
const [undoRedoLabels, setUndoRedoLabels] = useState({ undo: 'Ctrl + Z', redo: 'Ctrl + Shift + Z', redoAlt: 'Ctrl + Y' });
useEffect(() => {
Promise.all([
formatShortcut(['shift', 'mod', 'Scroll']),
formatShortcut(['mod', 'Scroll']),
formatShortcut(['mod', 'Z']),
formatShortcut(['shift', 'mod', 'Z']),
formatShortcut(['mod', 'Y']),
]).then(([pan, zoom, undo, redo, redoAlt]) => {
setScrollLabels({ pan, zoom });
setUndoRedoLabels({ undo, redo, redoAlt });
});
}, []);
return (
<div className="relative group">
<HelpCircle className="w-4 h-4 text-slate-500 hover:text-[#34B27B] transition-colors cursor-help" />
@@ -51,31 +33,15 @@ export function KeyboardShortcutsHelp() {
</div>
))}
<div className="pt-1 border-t border-white/5 mt-1">
<div className="flex items-center justify-between">
<span className="text-slate-400">Pan Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{scrollLabels.pan}</kbd>
</div>
<div className="flex items-center justify-between mt-1.5">
<span className="text-slate-400">Zoom Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{scrollLabels.zoom}</kbd>
</div>
<div className="flex items-center justify-between mt-1.5">
<span className="text-slate-400">Cycle Annotations</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">Tab</kbd>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Undo</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{undoRedoLabels.undo}</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Redo</span>
<div className="flex items-center gap-1">
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{undoRedoLabels.redo}</kbd>
<span className="text-slate-600 text-[9px]">or</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{undoRedoLabels.redoAlt}</kbd>
</div>
<div className="pt-1 border-t border-white/5 mt-1 space-y-1.5">
{FIXED_SHORTCUTS.map((fixed) => (
<div key={fixed.label} className="flex items-center justify-between">
<span className="text-slate-400">{fixed.label}</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">
{isMac ? fixed.display.replace(/Ctrl/g, '⌘').replace(/Shift/g, '⇧').replace(/Alt/g, '⌥') : fixed.display}
</kbd>
</div>
))}
</div>
</div>
</div>
+13 -14
View File
@@ -45,7 +45,7 @@ export default function VideoEditor() {
useEditorHistory(INITIAL_EDITOR_STATE);
const {
zoomRegions, trimRegions, annotationRegions,
zoomRegions, trimRegions, speedRegions, annotationRegions,
cropRegion, wallpaper, shadowIntensity, showBlur,
motionBlurEnabled, borderRadius, padding, aspectRatio,
} = editorState;
@@ -60,7 +60,6 @@ export default function VideoEditor() {
const [cursorTelemetry, setCursorTelemetry] = useState<CursorTelemetryPoint[]>([]);
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null);
const [speedRegions, setSpeedRegions] = useState<SpeedRegion[]>([]);
const [selectedSpeedId, setSelectedSpeedId] = useState<string | null>(null);
const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null);
const [isExporting, setIsExporting] = useState(false);
@@ -316,16 +315,16 @@ export default function VideoEditor() {
endMs: Math.round(span.end),
speed: DEFAULT_PLAYBACK_SPEED,
};
setSpeedRegions((prev) => [...prev, newRegion]);
pushState((prev) => ({ speedRegions: [...prev.speedRegions, newRegion] }));
setSelectedSpeedId(id);
setSelectedZoomId(null);
setSelectedTrimId(null);
setSelectedAnnotationId(null);
}, []);
}, [pushState]);
const handleSpeedSpanChange = useCallback((id: string, span: Span) => {
setSpeedRegions((prev) =>
prev.map((region) =>
pushState((prev) => ({
speedRegions: prev.speedRegions.map((region) =>
region.id === id
? {
...region,
@@ -334,24 +333,24 @@ export default function VideoEditor() {
}
: region,
),
);
}, []);
}));
}, [pushState]);
const handleSpeedDelete = useCallback((id: string) => {
setSpeedRegions((prev) => prev.filter((region) => region.id !== id));
pushState((prev) => ({ speedRegions: prev.speedRegions.filter((region) => region.id !== id) }));
if (selectedSpeedId === id) {
setSelectedSpeedId(null);
}
}, [selectedSpeedId]);
}, [selectedSpeedId, pushState]);
const handleSpeedChange = useCallback((speed: PlaybackSpeed) => {
if (!selectedSpeedId) return;
setSpeedRegions((prev) =>
prev.map((region) =>
pushState((prev) => ({
speedRegions: prev.speedRegions.map((region) =>
region.id === selectedSpeedId ? { ...region, speed } : region,
),
);
}, [selectedSpeedId]);
}));
}, [selectedSpeedId, pushState]);
const handleAnnotationAdded = useCallback((span: Span) => {
const id = `annotation-${nextAnnotationIdRef.current++}`;
+3 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useRef, useState } from "react";
import type { ZoomRegion, TrimRegion, AnnotationRegion, CropRegion } from "@/components/video-editor/types";
import type { ZoomRegion, TrimRegion, AnnotationRegion, SpeedRegion, CropRegion } from "@/components/video-editor/types";
import { DEFAULT_CROP_REGION } from "@/components/video-editor/types";
import type { AspectRatio } from "@/utils/aspectRatioUtils";
@@ -8,6 +8,7 @@ import type { AspectRatio } from "@/utils/aspectRatioUtils";
export interface EditorState {
zoomRegions: ZoomRegion[];
trimRegions: TrimRegion[];
speedRegions: SpeedRegion[];
annotationRegions: AnnotationRegion[];
cropRegion: CropRegion;
wallpaper: string;
@@ -22,6 +23,7 @@ export interface EditorState {
export const INITIAL_EDITOR_STATE: EditorState = {
zoomRegions: [],
trimRegions: [],
speedRegions: [],
annotationRegions: [],
cropRegion: DEFAULT_CROP_REGION,
wallpaper: "/wallpapers/wallpaper1.jpg",
+7 -5
View File
@@ -27,11 +27,13 @@ export interface FixedShortcut {
}
export const FIXED_SHORTCUTS: FixedShortcut[] = [
{ label: 'Cycle Annotations Forward', display: 'Tab', bindings: [{ key: 'tab' }] },
{ label: 'Cycle Annotations Backward', display: 'Shift + Tab', bindings: [{ key: 'tab', shift: true }] },
{ label: 'Delete Selected (alt)', display: 'Del / ⌫', bindings: [{ key: 'delete' }, { key: 'backspace' }] },
{ label: 'Pan Timeline', display: 'Shift + Ctrl + Scroll', bindings: [] },
{ label: 'Zoom Timeline', display: 'Ctrl + Scroll', bindings: [] },
{ label: 'Undo', display: 'Ctrl + Z', bindings: [{ key: 'z', ctrl: true }] },
{ label: 'Redo', display: 'Ctrl + Shift + Z / Ctrl + Y', bindings: [{ key: 'z', ctrl: true, shift: true }, { key: 'y', ctrl: true }] },
{ label: 'Cycle Annotations Forward', display: 'Tab', bindings: [{ key: 'tab' }] },
{ label: 'Cycle Annotations Backward', display: 'Shift + Tab', bindings: [{ key: 'tab', shift: true }] },
{ label: 'Delete Selected (alt)', display: 'Del / ⌫', bindings: [{ key: 'delete' }, { key: 'backspace' }] },
{ label: 'Pan Timeline', display: 'Shift + Ctrl + Scroll', bindings: [] },
{ label: 'Zoom Timeline', display: 'Ctrl + Scroll', bindings: [] },
];
export type ShortcutConflict =