207 lines
4.2 KiB
Svelte
207 lines
4.2 KiB
Svelte
<script lang="ts">
|
|
import { formatTime } from '$lib/utils/time';
|
|
|
|
export type ContextMenuAction =
|
|
| { type: 'mark-in' }
|
|
| { type: 'mark-out' }
|
|
| { type: 'go-to-clip-start' }
|
|
| { type: 'go-to-clip-end' }
|
|
| { type: 'delete-clip' };
|
|
|
|
let {
|
|
x,
|
|
y,
|
|
time,
|
|
selectedClipId,
|
|
onAction,
|
|
onClose,
|
|
}: {
|
|
x: number;
|
|
y: number;
|
|
time: number;
|
|
selectedClipId: string | null;
|
|
onAction: (action: ContextMenuAction) => void;
|
|
onClose: () => void;
|
|
} = $props();
|
|
|
|
let menuEl = $state<HTMLDivElement | null>(null);
|
|
|
|
function handleAction(action: ContextMenuAction) {
|
|
onAction(action);
|
|
onClose();
|
|
}
|
|
|
|
function handleKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
onClose();
|
|
}
|
|
}
|
|
|
|
function handleClickOutside(e: MouseEvent) {
|
|
if (menuEl && !menuEl.contains(e.target as Node)) {
|
|
onClose();
|
|
}
|
|
}
|
|
|
|
let adjustedX = $state(0);
|
|
let adjustedY = $state(0);
|
|
|
|
$effect(() => {
|
|
const posX = x;
|
|
const posY = y;
|
|
if (!menuEl) {
|
|
adjustedX = posX;
|
|
adjustedY = posY;
|
|
return;
|
|
}
|
|
const rect = menuEl.getBoundingClientRect();
|
|
const vw = window.innerWidth;
|
|
const vh = window.innerHeight;
|
|
|
|
let newX = posX;
|
|
let newY = posY;
|
|
|
|
if (posX + rect.width > vw) {
|
|
newX = posX - rect.width;
|
|
}
|
|
if (posY + rect.height > vh) {
|
|
newY = posY - rect.height;
|
|
}
|
|
|
|
adjustedX = Math.max(0, newX);
|
|
adjustedY = Math.max(0, newY);
|
|
});
|
|
</script>
|
|
|
|
<svelte:window
|
|
onmousedown={handleClickOutside}
|
|
onkeydown={handleKeydown}
|
|
/>
|
|
|
|
<div
|
|
class="context-menu"
|
|
bind:this={menuEl}
|
|
style="left: {adjustedX}px; top: {adjustedY}px;"
|
|
role="menu"
|
|
>
|
|
<div class="menu-header">{formatTime(time)}</div>
|
|
<button
|
|
class="menu-item"
|
|
role="menuitem"
|
|
onclick={() => handleAction({ type: 'mark-in' })}
|
|
>
|
|
<span class="label">Mark In</span>
|
|
<span class="shortcut">I</span>
|
|
</button>
|
|
<button
|
|
class="menu-item"
|
|
role="menuitem"
|
|
onclick={() => handleAction({ type: 'mark-out' })}
|
|
>
|
|
<span class="label">Mark Out</span>
|
|
<span class="shortcut">O</span>
|
|
</button>
|
|
|
|
<div class="separator"></div>
|
|
|
|
<button
|
|
class="menu-item"
|
|
class:disabled={!selectedClipId}
|
|
role="menuitem"
|
|
disabled={!selectedClipId}
|
|
onclick={() => handleAction({ type: 'go-to-clip-start' })}
|
|
>
|
|
<span class="label">Go to Clip Start</span>
|
|
</button>
|
|
<button
|
|
class="menu-item"
|
|
class:disabled={!selectedClipId}
|
|
role="menuitem"
|
|
disabled={!selectedClipId}
|
|
onclick={() => handleAction({ type: 'go-to-clip-end' })}
|
|
>
|
|
<span class="label">Go to Clip End</span>
|
|
</button>
|
|
|
|
{#if selectedClipId}
|
|
<div class="separator"></div>
|
|
<button
|
|
class="menu-item danger"
|
|
role="menuitem"
|
|
onclick={() => handleAction({ type: 'delete-clip' })}
|
|
>
|
|
<span class="label">Delete Clip</span>
|
|
<span class="shortcut">⌫</span>
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.context-menu {
|
|
position: fixed;
|
|
z-index: 1000;
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 4px 0;
|
|
min-width: 180px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.menu-header {
|
|
padding: 4px 12px 2px;
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
.menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
padding: 6px 12px;
|
|
background: none;
|
|
border: none;
|
|
border-radius: 0;
|
|
color: var(--text-primary);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.menu-item:hover:not(:disabled) {
|
|
background: var(--accent);
|
|
color: var(--bg-primary);
|
|
}
|
|
|
|
.menu-item:disabled {
|
|
color: var(--text-muted);
|
|
cursor: default;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.menu-item.danger:hover {
|
|
background: var(--danger);
|
|
color: var(--bg-primary);
|
|
}
|
|
|
|
.shortcut {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-left: 16px;
|
|
}
|
|
|
|
.menu-item:hover:not(:disabled) .shortcut {
|
|
color: inherit;
|
|
}
|
|
|
|
.separator {
|
|
height: 1px;
|
|
background: var(--border);
|
|
margin: 4px 0;
|
|
}
|
|
</style>
|