feat(library): genre sliders, artist links, and navigation utils
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 3m49s
Traceability Validation / Check Requirement Traces (pull_request) Successful in 19s
🏗️ Build and Test JellyTau / Build Android APK (pull_request) Successful in 18m24s

- music landing: diverse per-genre album sliders (online counts /
  offline wide-probe fallback) and home-screen library shortcuts
- add ArtistLinks component and shared navigation/genreDiversity utils
- player/playback-mode refinements across Rust and frontend
This commit is contained in:
2026-06-25 19:18:06 +02:00
parent 62874564ff
commit 1836615dc0
38 changed files with 1009 additions and 207 deletions
+9 -20
View File
@@ -8,7 +8,7 @@
import type { MediaItem } from "$lib/api/types";
import { auth } from "$lib/stores/auth";
import { library } from "$lib/stores/library";
import { queue, currentQueueItem } from "$lib/stores/queue";
import { queue, currentQueueItem, isShuffle, repeatMode, hasNext as hasNextStore, hasPrevious as hasPreviousStore } from "$lib/stores/queue";
import { downloads, type DownloadInfo } from "$lib/stores/downloads";
import { playbackPosition, playbackDuration, currentMedia as storeCurrentMedia } from "$lib/stores/player";
import { get } from "svelte/store";
@@ -47,10 +47,12 @@
const position = $derived($playbackPosition);
const duration = $derived($playbackDuration);
let isPlaying = $state(false);
let shuffle = $state(false);
let repeat = $state<"off" | "all" | "one">("off");
let hasNext = $state(false);
let hasPrevious = $state(false);
// Shuffle/repeat/next/previous are event-driven via the queue store (instant
// update on queue_changed), not polled.
const shuffle = $derived($isShuffle);
const repeat = $derived($repeatMode);
const hasNext = $derived($hasNextStore);
const hasPrevious = $derived($hasPreviousStore);
let currentMedia = $state<MediaItem | null>(null);
let streamUrl = $state<string | null>(null);
let mediaSourceId = $state<string | null>(null);
@@ -133,14 +135,7 @@
isVideo = item.type === "Movie" || item.type === "Episode";
isPlaying = true;
loading = false;
// Sync queue status
try {
const queueStatus = await commands.playerGetQueue();
hasNext = queueStatus.hasNext;
hasPrevious = queueStatus.hasPrevious;
} catch (e) {
// Ignore - queue status will update via polling
}
// hasNext/hasPrevious come from the event-driven queue store.
// Fetch next episode for video skip button
if (isVideo) {
fetchNextEpisode(item);
@@ -446,13 +441,7 @@
isPlaying = status.state.kind === "playing";
// Note: position/duration are now derived from player store (updated by events)
}
shuffle = status.shuffle;
repeat = status.repeat;
// Update queue status
const queue = await commands.playerGetQueue();
hasNext = queue.hasNext;
hasPrevious = queue.hasPrevious;
// shuffle/repeat/hasNext/hasPrevious come from the queue store (event-driven).
} catch (e) {
// Ignore polling errors
}