Files
jellytau/src/lib/components/settings/SearchGroupOrderList.svelte
T
dtourolle ad48d89dfe chore(format): run prettier over src/ and scripts/
Formatting was configured but never enforced: `bun run format:check`
reported 199 unformatted files and ran in no workflow and in no git hook,
so .prettierrc (printWidth 100, trailing commas) described an intention
rather than the tree.

This is the one-time sweep that makes the check gateable. Whitespace and
token-reflow only -- no behavioural change: `bun run check` reports 0
errors and all 1053 frontend tests pass before and after.

Kept out of every other commit on purpose. A 199-file diff mixed with
real changes is unreviewable, and the next commit turns format:check
into a hard CI gate so this cannot silently accumulate again.
2026-08-21 17:41:44 +02:00

158 lines
4.7 KiB
Svelte

<script lang="ts">
// Reorderable list of search result groups.
//
// Drag-and-drop alone would leave this unusable with a screen reader or any
// pointerless input, so every row also carries labelled move up/down buttons
// which are the primary, always-available mechanism.
//
// TRACES: UR-050 | DR-066
import { searchGroupOrder } from "$lib/stores/searchGroupOrder";
import { GROUP_LABELS, type SearchGroupId } from "$lib/utils/searchScope";
let dragIndex = $state<number | null>(null);
let overIndex = $state<number | null>(null);
// Announced to assistive tech after a move, since the list itself reorders
// silently.
let announcement = $state("");
function announce(id: SearchGroupId) {
const position = $searchGroupOrder.indexOf(id) + 1;
announcement = `${GROUP_LABELS[id]} moved to position ${position} of ${$searchGroupOrder.length}`;
}
function move(id: SearchGroupId, delta: number) {
searchGroupOrder.move(id, delta);
announce(id);
}
function onDragStart(event: DragEvent, index: number) {
dragIndex = index;
event.dataTransfer?.setData("text/plain", String(index));
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
}
function onDragOver(event: DragEvent, index: number) {
if (dragIndex === null) return;
event.preventDefault();
overIndex = index;
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
}
function onDrop(event: DragEvent, index: number) {
event.preventDefault();
if (dragIndex !== null && dragIndex !== index) {
const id = $searchGroupOrder[dragIndex];
searchGroupOrder.reorder(dragIndex, index);
if (id) announce(id);
}
dragIndex = null;
overIndex = null;
}
function onDragEnd() {
dragIndex = null;
overIndex = null;
}
</script>
<ul class="space-y-2">
{#each $searchGroupOrder as id, i (id)}
<li
draggable="true"
ondragstart={(e) => onDragStart(e, i)}
ondragover={(e) => onDragOver(e, i)}
ondrop={(e) => onDrop(e, i)}
ondragend={onDragEnd}
class="flex items-center gap-3 px-3 py-2 rounded-lg bg-gray-800 border transition-colors {overIndex ===
i && dragIndex !== i
? 'border-[var(--color-jellyfin)]'
: 'border-gray-700'} {dragIndex === i ? 'opacity-50' : ''}"
>
<!-- Decorative: dragging is the mouse affordance, the buttons below are
the accessible path. -->
<svg
class="w-4 h-4 text-gray-500 flex-shrink-0 cursor-grab"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M9 4h2v2H9V4zm4 0h2v2h-2V4zM9 9h2v2H9V9zm4 0h2v2h-2V9zm-4 5h2v2H9v-2zm4 0h2v2h-2v-2zm-4 5h2v2H9v-2zm4 0h2v2h-2v-2z"
/>
</svg>
<span class="text-sm text-gray-400 w-5 flex-shrink-0">{i + 1}</span>
<span class="flex-1 text-white">{GROUP_LABELS[id]}</span>
<button
type="button"
onclick={() => move(id, -1)}
disabled={i === 0}
aria-label="Move {GROUP_LABELS[id]} up"
class="p-2 rounded text-gray-300 hover:bg-gray-700 hover:text-white transition-colors disabled:opacity-30 disabled:hover:bg-transparent"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
</svg>
</button>
<button
type="button"
onclick={() => move(id, 1)}
disabled={i === $searchGroupOrder.length - 1}
aria-label="Move {GROUP_LABELS[id]} down"
class="p-2 rounded text-gray-300 hover:bg-gray-700 hover:text-white transition-colors disabled:opacity-30 disabled:hover:bg-transparent"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
</li>
{/each}
</ul>
<div class="sr-only" role="status" aria-live="polite">{announcement}</div>
<div class="flex justify-end pt-3">
<button
type="button"
onclick={() => {
searchGroupOrder.reset();
announcement = "Search group order reset to default";
}}
class="text-sm text-gray-400 hover:text-white transition-colors"
>
Reset to default
</button>
</div>
<style>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>