diff --git a/src/App.svelte b/src/App.svelte index c814953..ff685f6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -10,7 +10,7 @@ import ExportDialog from '$lib/components/ExportDialog.svelte'; import AboutDialog from '$lib/components/AboutDialog.svelte'; import ProcessingModal from '$lib/components/ProcessingModal.svelte'; - import { loadPreferences } from '$lib/stores/preferences.svelte'; + import { loadPreferences, preferences, setClipListPosition, setClipListWidth } 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'; @@ -32,6 +32,11 @@ let resizeStartHeight = $state(0); let splitAreaEl = $state(null); + let isSidebarResizing = $state(false); + let sidebarResizeStartX = $state(0); + let sidebarResizeStartWidth = $state(0); + let windowWidth = $state(window.innerWidth); + $effect(() => { loadPreferences(); }); @@ -74,6 +79,33 @@ isResizing = false; } + function toggleClipListPosition() { + const next = preferences.clipListPosition === 'bottom' ? 'left' : 'bottom'; + setClipListPosition(next); + } + + function handleSidebarResizeStart(e: MouseEvent) { + e.preventDefault(); + isSidebarResizing = true; + sidebarResizeStartX = e.clientX; + sidebarResizeStartWidth = preferences.clipListWidth; + } + + function handleSidebarResizeMove(e: MouseEvent) { + if (!isSidebarResizing) return; + const delta = e.clientX - sidebarResizeStartX; + const maxWidth = Math.round(windowWidth * 0.4); + const newWidth = Math.max(150, Math.min(maxWidth, sidebarResizeStartWidth + delta)); + preferences.clipListWidth = newWidth; + } + + function handleSidebarResizeEnd() { + if (isSidebarResizing) { + isSidebarResizing = false; + setClipListWidth(preferences.clipListWidth); + } + } + function handleGlobalKeydown(e: KeyboardEvent) { const target = e.target as HTMLElement; if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return; @@ -156,8 +188,17 @@ { + handleResizeMove(e); + handleSidebarResizeMove(e); + }} + onmouseup={() => { + handleResizeEnd(); + handleSidebarResizeEnd(); + }} + onresize={() => { + windowWidth = window.innerWidth; + }} /> {#if showSetupWizard} @@ -183,43 +224,115 @@
+
-
- - -
-
- + {#if preferences.clipListPosition === 'bottom'} +
+ + +
+
+ +
+
{ + if (e.key === 'ArrowDown') { + e.preventDefault(); + timelineHeight = Math.min(500, timelineHeight + 10); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + timelineHeight = Math.max(40, timelineHeight - 10); + } + }} + >
+
+ (showExportDialog = true)} /> +
-
{ - if (e.key === 'ArrowDown') { - e.preventDefault(); - timelineHeight = Math.min(500, timelineHeight + 10); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - timelineHeight = Math.max(40, timelineHeight - 10); - } - }} - >
-
- (showExportDialog = true)} /> +
+ {:else} +
+
+
+ (showExportDialog = true)} position="left" /> +
+ +
+ + +
-
-
+
+
{ + if (e.key === 'ArrowDown') { + e.preventDefault(); + timelineHeight = Math.min(500, timelineHeight + 10); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + timelineHeight = Math.max(40, timelineHeight - 10); + } + }} + >
+
+ +
+
+ + {/if} (showAboutDialog = true)} />
@@ -279,6 +392,54 @@ overflow: hidden; } + .content-left { + display: flex; + flex-direction: column; + overflow: hidden; + } + + .upper-section { + flex: 1; + display: flex; + flex-direction: row; + min-height: 0; + overflow: hidden; + } + + .cliplist-sidebar { + flex-shrink: 0; + overflow-y: auto; + overflow-x: hidden; + border-right: 1px solid var(--border); + background: var(--bg-secondary); + } + + .sidebar-resize-handle { + width: 5px; + background: var(--border); + cursor: ew-resize; + flex-shrink: 0; + transition: background 0.1s; + } + + .sidebar-resize-handle:hover, + .sidebar-resize-handle.active { + background: var(--accent); + } + + .right-column { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + overflow: hidden; + } + + .layout-btn { + font-size: 16px; + padding: 4px 8px; + } + .about-btn { font-size: 16px; padding: 4px 8px; diff --git a/src/lib/components/ClipList.svelte b/src/lib/components/ClipList.svelte index 9fc0bfe..c2c0a40 100644 --- a/src/lib/components/ClipList.svelte +++ b/src/lib/components/ClipList.svelte @@ -8,7 +8,10 @@ import { formatTime, parseTime } from '$lib/utils/time'; import { session } from '$lib/stores/videoSession.svelte'; - let { onExport }: { onExport?: () => void } = $props(); + let { + onExport, + position = 'bottom', + }: { onExport?: () => void; position?: 'bottom' | 'left' } = $props(); let clips = $derived(clipStore.clips); let selectedId = $derived(clipStore.selectedClipId); @@ -45,6 +48,7 @@
{ if (savedDir) preferences.outputDirectory = savedDir; if (savedCookie) preferences.cookieSource = savedCookie; if (savedCaptions) preferences.captionSettings = { ...DEFAULT_CAPTION_SETTINGS, ...savedCaptions }; + const savedClipListPosition = await store.get<'bottom' | 'left'>('clipListPosition'); + const savedClipListWidth = await store.get('clipListWidth'); + if (savedClipListPosition) preferences.clipListPosition = savedClipListPosition; + if (savedClipListWidth) preferences.clipListWidth = savedClipListWidth; })(); await loadPromise; } @@ -85,6 +93,8 @@ export async function savePreferences(): Promise { await store.set('outputDirectory', preferences.outputDirectory); await store.set('cookieSource', preferences.cookieSource); await store.set('captionSettings', preferences.captionSettings); + await store.set('clipListPosition', preferences.clipListPosition); + await store.set('clipListWidth', preferences.clipListWidth); await store.save(); } @@ -102,3 +112,13 @@ export async function setCaptionSettings(settings: Partial): Pr Object.assign(preferences.captionSettings, settings); await savePreferences(); } + +export async function setClipListPosition(position: 'bottom' | 'left'): Promise { + preferences.clipListPosition = position; + await savePreferences(); +} + +export async function setClipListWidth(width: number): Promise { + preferences.clipListWidth = width; + await savePreferences(); +}