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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user