Files
minstrel/web/src/lib/components/SelectionBar.svelte
T
bvandeusenandClaude Opus 5.5 94a9c8cbe3 chore(deps): SvelteKit 3 with adapter-static 4 and TypeScript 6, full-tree npm audit (#5021)
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps,
plus the migration they need. The mechanical part is `sv migrate
sveltekit-3`, run one task at a time and reviewed:

- svelte.config.js is gone. Its options move into sveltekit() in
  vite.config.ts, exported as kitOptions so vitest.config.ts runs the
  same kit setup, including the $test-utils alias the tests import.
- $lib becomes #lib through package.json "imports". There is no
  src/lib/index, so only the "#lib/*" entry is kept.
- tsconfig extends $app/tsconfig.
- Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite
  ^8.0.12, svelte-check ^4.7.5.

By hand, from the codemod's list of non-automated tasks:

- goto's replaceState option is now replace; keepFocus becomes
  reset: false. For the search typeahead, reset: false also stops the
  scroll-to-top, which is wanted while typing.
- The test setup mocks drop pushState/replaceState and $app/paths
  base/assets, which kit 3 removed, and mock refreshAll in place of
  invalidateAll.
- The other flagged files only read page.url or goto internal routes,
  so they needed no change.

TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares
typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to
7 once both accept it.

With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0,
so the web lane now audits every dependency rather than only what
ships to browsers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:50:39 -04:00

130 lines
4.2 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { X, ListPlus, Plus, ListMusic, Heart } from 'lucide-svelte';
import { useQueryClient } from '@tanstack/svelte-query';
import { selection, clearSelection } from '#lib/selection/store.svelte.js';
import { playNextMany, enqueueTracks } from '#lib/player/store.svelte.js';
import { likeEntity, createLikedIdsQuery } from '#lib/api/likes.js';
import AddToPlaylistMenu from './AddToPlaylistMenu.svelte';
const queryClient = useQueryClient();
const likedQ = $derived(createLikedIdsQuery());
const likedIds = $derived($likedQ?.data?.track_ids ?? []);
let addOpen = $state(false);
let busy = $state(false);
function onPlayNext() {
playNextMany(selection.tracks);
clearSelection();
}
function onAddQueue() {
enqueueTracks(selection.tracks);
clearSelection();
}
async function onLikeAll() {
if (busy) return;
busy = true;
try {
const likedSet = new Set(likedIds);
const toLike = selection.tracks.filter((t) => !likedSet.has(t.id));
for (const t of toLike) {
await likeEntity(queryClient, 'track', t.id);
}
clearSelection();
} finally {
busy = false;
}
}
function onClose() {
addOpen = false;
}
</script>
{#if selection.active}
<!-- Sits above PlayerBar (z-30 < 40). Anchors to the viewport bottom
with bottom-24 so PlayerBar's ~80px shell stays visible. The bar
is dismissed by the × button or by Escape (handled in layout). -->
<div
role="region"
aria-label="Selection actions"
class="pointer-events-none fixed inset-x-0 bottom-24 z-30 flex justify-center px-4"
>
<div
class="pointer-events-auto flex items-center gap-2 rounded-full border border-border
bg-surface px-3 py-2 shadow-lg"
>
<span class="px-2 text-sm text-text-primary">
{selection.count} selected
</span>
<span class="h-5 w-px bg-border"></span>
<button
type="button"
onclick={onPlayNext}
aria-label="Play selected next"
title="Play next"
class="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent"
>
<ListPlus size={16} strokeWidth={1.5} />
<span class="hidden sm:inline">Play next</span>
</button>
<button
type="button"
onclick={onAddQueue}
aria-label="Add selected to queue"
title="Add to queue"
class="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent"
>
<Plus size={16} strokeWidth={1.5} />
<span class="hidden sm:inline">Add to queue</span>
</button>
<button
type="button"
onclick={() => (addOpen = !addOpen)}
aria-haspopup="menu"
aria-expanded={addOpen}
aria-label="Add selected to playlist"
title="Add to playlist"
class="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent"
>
<ListMusic size={16} strokeWidth={1.5} />
<span class="hidden sm:inline">Add to playlist</span>
</button>
<button
type="button"
onclick={onLikeAll}
disabled={busy}
aria-label="Like all selected"
title="Like all"
class="flex items-center gap-1.5 rounded px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent disabled:opacity-50"
>
<Heart size={16} strokeWidth={1.5} />
<span class="hidden sm:inline">Like all</span>
</button>
<span class="h-5 w-px bg-border"></span>
<button
type="button"
onclick={clearSelection}
aria-label="Clear selection"
title="Clear selection"
class="rounded p-1.5 text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent"
>
<X size={16} strokeWidth={1.5} />
</button>
</div>
{#if addOpen}
<div class="pointer-events-auto absolute bottom-full mb-2">
<AddToPlaylistMenu tracks={selection.tracks} {onClose} />
</div>
{/if}
</div>
{/if}