feat(web): multi-select + bulk actions on track lists (Tier A3)
test-web / test (push) Successful in 31s
test-web / test (push) Successful in 31s
Add a global selection store backing a checkbox per track row and a floating SelectionBar above PlayerBar with Play next / Add to queue / Add to playlist / Like all / Clear. - selection/store.svelte.ts: id Set + TrackRef Map + anchor index; toggleOne, selectRange (shift+click), clearSelection. Singleton — layout effect clears on pathname change. - TrackRow: checkbox slot replaces the track number on hover; row click toggles selection once any row is picked; shift+click extends the range. Esc clears (takes priority over closing the queue drawer). - SelectionBar: floating pill above PlayerBar, mounted inside the QueryClientProvider so the Like-all action can resolve. - player.playNextMany: bulk variant of playNext for "Play next" on a multi-track selection. Covered by selection-store unit tests and three new TrackRow tests (checkbox toggle, sticky select-mode row click, shift+click range).
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
import { isPublicRoute } from '$lib/auth/publicRoutes';
|
||||
import Shell from '$lib/components/Shell.svelte';
|
||||
import QueueDrawer from '$lib/components/QueueDrawer.svelte';
|
||||
import SelectionBar from '$lib/components/SelectionBar.svelte';
|
||||
import ToastHost from '$lib/components/ToastHost.svelte';
|
||||
import { selection, clearSelection } from '$lib/selection/store.svelte';
|
||||
import {
|
||||
registerAudioEl,
|
||||
reportTimeUpdate,
|
||||
@@ -104,14 +106,24 @@
|
||||
|
||||
$effect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && player.queueDrawerOpen) {
|
||||
closeQueueDrawer();
|
||||
if (e.key === 'Escape') {
|
||||
// Selection bar takes priority over queue drawer: pressing Esc
|
||||
// with both open clears the (less-modal) selection first.
|
||||
if (selection.active) clearSelection();
|
||||
else if (player.queueDrawerOpen) closeQueueDrawer();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
return () => document.removeEventListener('keydown', handler);
|
||||
});
|
||||
|
||||
// Route-change reset: selection state is per-page; navigating away
|
||||
// (or to a different track-list surface) clears whatever was picked.
|
||||
$effect(() => {
|
||||
page.url.pathname; // subscribe
|
||||
clearSelection();
|
||||
});
|
||||
|
||||
useMediaSession();
|
||||
useEventsDispatcher();
|
||||
useGlobalShortcuts();
|
||||
@@ -156,6 +168,9 @@
|
||||
{:else}
|
||||
{@render children()}
|
||||
{/if}
|
||||
<!-- SelectionBar must be inside the QueryClientProvider so its
|
||||
Like-all action can use useQueryClient. -->
|
||||
<SelectionBar />
|
||||
</QueryClientProvider>
|
||||
|
||||
<ToastHost />
|
||||
|
||||
Reference in New Issue
Block a user