feat: add transport controls and keyboard shortcuts

Centralize seek/play/frame/shuttle in playback helpers, wire the transport bar, and handle global shortcuts from App.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 11:08:16 -04:00
parent c13755e5d1
commit 2f25db3cc5
3 changed files with 318 additions and 5 deletions

View File

@@ -8,15 +8,101 @@
import SetupWizard from '$lib/components/SetupWizard.svelte';
import PreferencesPanel from '$lib/components/PreferencesPanel.svelte';
import { loadPreferences } from '$lib/stores/preferences.svelte';
import { getSelectedClipId, removeClip } from '$lib/stores/clips.svelte';
import { adjustShuttle } from '$lib/transport/playback';
import type { TransportKeyAction } from '$lib/transport/playback';
let showSetupWizard = $state(true);
let showPreferences = $state(false);
let showExportDialog = $state(false);
let shuttleRate = $state(1);
let transportControls = $state<TransportControls | null>(null);
$effect(() => {
loadPreferences();
});
function dispatchTransport(action: TransportKeyAction) {
transportControls?.handleKeyAction(action);
}
function handleGlobalKeydown(e: KeyboardEvent) {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;
switch (e.key) {
case ' ':
e.preventDefault();
dispatchTransport('play-pause');
break;
case ',':
e.preventDefault();
dispatchTransport(e.shiftKey ? 'keyframe-back' : 'frame-back');
break;
case '.':
e.preventDefault();
dispatchTransport(e.shiftKey ? 'keyframe-forward' : 'frame-forward');
break;
case 'ArrowLeft':
e.preventDefault();
dispatchTransport(e.shiftKey ? 'seek-back-1' : 'seek-back-5');
break;
case 'ArrowRight':
e.preventDefault();
dispatchTransport(e.shiftKey ? 'seek-forward-1' : 'seek-forward-5');
break;
case 'i':
case 'I':
e.preventDefault();
dispatchTransport('mark-in');
break;
case 'o':
case 'O':
e.preventDefault();
dispatchTransport('mark-out');
break;
case 'j':
case 'J':
e.preventDefault();
shuttleRate = adjustShuttle(-1, shuttleRate);
break;
case 'k':
case 'K':
e.preventDefault();
dispatchTransport('play-pause');
break;
case 'l':
case 'L':
e.preventDefault();
shuttleRate = adjustShuttle(1, shuttleRate);
break;
case 'Delete':
case 'Backspace': {
e.preventDefault();
const selectedId = getSelectedClipId();
if (selectedId) removeClip(selectedId);
break;
}
case 'a':
if (e.metaKey) {
e.preventDefault();
}
break;
case 'e':
if (e.metaKey) {
e.preventDefault();
showExportDialog = true;
}
break;
default:
break;
}
}
</script>
<svelte:window onkeydown={handleGlobalKeydown} />
{#if showSetupWizard}
<SetupWizard onComplete={() => (showSetupWizard = false)} />
{/if}
@@ -33,7 +119,7 @@
<main class="content">
<VideoPlayer />
<TransportControls />
<TransportControls bind:this={transportControls} />
<Timeline />
<ClipList />
</main>