171 lines
4.4 KiB
Svelte
171 lines
4.4 KiB
Svelte
<script lang="ts">
|
|
import UrlInput from '$lib/components/UrlInput.svelte';
|
|
import VideoPlayer from '$lib/components/VideoPlayer.svelte';
|
|
import TransportControls from '$lib/components/TransportControls.svelte';
|
|
import Timeline from '$lib/components/Timeline.svelte';
|
|
import ClipList from '$lib/components/ClipList.svelte';
|
|
import StatusBar from '$lib/components/StatusBar.svelte';
|
|
import SetupWizard from '$lib/components/SetupWizard.svelte';
|
|
import PreferencesPanel from '$lib/components/PreferencesPanel.svelte';
|
|
import ExportDialog from '$lib/components/ExportDialog.svelte';
|
|
import { loadPreferences } from '$lib/stores/preferences.svelte';
|
|
import { getSelectedClipId, removeClip } from '$lib/stores/clips.svelte';
|
|
import { adjustShuttle, resetShuttleRate } from '$lib/transport/playback';
|
|
import { session } from '$lib/stores/videoSession.svelte';
|
|
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();
|
|
});
|
|
|
|
$effect(() => {
|
|
if (session.status === 'resolving') {
|
|
shuttleRate = 1;
|
|
resetShuttleRate();
|
|
}
|
|
});
|
|
|
|
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}
|
|
|
|
{#if showPreferences}
|
|
<PreferencesPanel onClose={() => (showPreferences = false)} />
|
|
{/if}
|
|
|
|
{#if showExportDialog}
|
|
<ExportDialog onClose={() => (showExportDialog = false)} />
|
|
{/if}
|
|
|
|
<div class="app-shell">
|
|
<header class="toolbar">
|
|
<UrlInput />
|
|
<button class="prefs-btn" onclick={() => (showPreferences = true)} title="Preferences">⚙</button>
|
|
</header>
|
|
|
|
<main class="content">
|
|
<VideoPlayer />
|
|
<TransportControls bind:this={transportControls} />
|
|
<Timeline />
|
|
<ClipList onExport={() => (showExportDialog = true)} />
|
|
</main>
|
|
|
|
<StatusBar />
|
|
</div>
|
|
|
|
<style>
|
|
.app-shell {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 8px 12px;
|
|
background: var(--bg-secondary);
|
|
border-bottom: 1px solid var(--border);
|
|
gap: 8px;
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.prefs-btn {
|
|
font-size: 18px;
|
|
padding: 4px 8px;
|
|
}
|
|
</style>
|