From 63ea1a4d6adf14dba66ce14fade74fa4531cac5b Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Sun, 5 Apr 2026 12:57:45 +0300 Subject: [PATCH] feat(frontend): make filter expression tokens draggable for reordering Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/components/file/FilterBar.svelte | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/file/FilterBar.svelte b/frontend/src/lib/components/file/FilterBar.svelte index 77c9adc..778aba4 100644 --- a/frontend/src/lib/components/file/FilterBar.svelte +++ b/frontend/src/lib/components/file/FilterBar.svelte @@ -17,7 +17,13 @@ let tags = $state([]); let search = $state(''); let tokens = $state(parseDslFilter(value)); - let tagNames = $derived(new Map(tags.filter((t) => t.id && t.name).map((t) => [t.id as string, t.name as string]))); + let tagNames = $derived( + new Map( + tags + .filter((t) => t.id && t.name) + .map((t) => [t.id as string, t.name as string]), + ), + ); $effect(() => { tokens = parseDslFilter(value ?? null); @@ -52,6 +58,39 @@ search = ''; onApply(null); } + + // --- Drag-and-drop reordering --- + let dragIndex = $state(null); + let dropIndex = $state(null); + + function onDragStart(i: number, e: DragEvent) { + dragIndex = i; + e.dataTransfer!.effectAllowed = 'move'; + // Set minimal drag image so the token itself acts as the ghost + e.dataTransfer!.setData('text/plain', String(i)); + } + + function onDragOver(i: number, e: DragEvent) { + e.preventDefault(); + e.dataTransfer!.dropEffect = 'move'; + dropIndex = i; + } + + function onDrop(i: number, e: DragEvent) { + e.preventDefault(); + if (dragIndex === null || dragIndex === i) return; + const next = [...tokens]; + const [moved] = next.splice(dragIndex, 1); + next.splice(i, 0, moved); + tokens = next; + dragIndex = null; + dropIndex = null; + } + + function onDragEnd() { + dragIndex = null; + dropIndex = null; + }
@@ -61,9 +100,24 @@ No filter — tap a tag or operator below to build one {:else} {#each tokens as token, i (i)} - +
{/each} {/if} @@ -155,12 +209,26 @@ background-color: var(--color-accent); color: var(--color-bg-primary); font-weight: 600; + cursor: grab; + user-select: none; + transition: opacity 0.15s, outline 0.1s; + outline: 2px solid transparent; } .active-token:hover { background-color: var(--color-accent-hover); } + .active-token.dragging { + opacity: 0.4; + cursor: grabbing; + } + + .active-token.drop-before { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + } + .op-token { background-color: color-mix(in srgb, var(--color-accent) 18%, var(--color-bg-elevated)); color: var(--color-text-primary);