Fix for offline mode
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 4m21s
Traceability Validation / Check Requirement Traces (pull_request) Successful in 20s
🏗️ Build and Test JellyTau / Android Compile Check (pull_request) Successful in 4m54s

This commit is contained in:
2026-07-03 19:37:34 +02:00
parent c58cc0cf46
commit 2d141e5bf4
13 changed files with 790 additions and 143 deletions
@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { page } from "$app/stores";
import { truncateMiddle } from "$lib/utils/truncateMiddle";
import { goto } from "$app/navigation";
import { navigateBack } from "$lib/utils/navigation";
@@ -53,6 +54,16 @@
onMount(async () => {
await loadGenres();
// Auto-select a genre when linked with ?genre=<name> (e.g. from a genre tag)
const requestedGenre = $page.url.searchParams.get("genre");
if (requestedGenre) {
const match = genres.find(
(g) => g.name.toLowerCase() === requestedGenre.toLowerCase(),
);
if (match) {
await loadGenreItems(match);
}
}
markLoaded();
});
+22 -4
View File
@@ -5,14 +5,34 @@
genres: string[];
maxShow?: number; // Default: unlimited
clickable?: boolean; // Default: true
itemType?: string; // Determines which genre browse page to open
}
let {
genres,
maxShow,
clickable = true
clickable = true,
itemType
}: Props = $props();
// Map the item type to its genre-browse route
function genreBasePath(type: string | undefined): string {
switch (type) {
case "MusicAlbum":
case "MusicArtist":
case "Audio":
return "/library/music/genres";
case "Series":
case "Season":
case "Episode":
return "/library/shows/genres";
case "Movie":
return "/library/movies/genres";
default:
return "/library/movies/genres";
}
}
const displayGenres = $derived(
maxShow ? genres.slice(0, maxShow) : genres
);
@@ -23,9 +43,7 @@
function handleGenreClick(genre: string) {
if (clickable) {
// Navigate to genre browse page
// For now, we'll use a simple navigation - could be enhanced with a proper genre browse page
goto(`/search?genre=${encodeURIComponent(genre)}`);
goto(`${genreBasePath(itemType)}?genre=${encodeURIComponent(genre)}`);
}
}
</script>
+23 -1
View File
@@ -19,6 +19,7 @@
import { goto } from "$app/navigation";
import type { MediaItem } from "$lib/api/types";
import { auth } from "$lib/stores/auth";
import { commands } from "$lib/api/bindings";
import {
mergedMedia,
mergedIsPlaying,
@@ -70,6 +71,27 @@
// In remote mode, this automatically uses the remote session's nowPlayingItem
const displayMedia = $derived($mergedMedia || $currentQueueItem);
const displayIsPlaying = $derived($mergedIsPlaying);
// The player's MediaItem doesn't carry favorite state, so read it from the
// local user_data cache (offline-safe — same source the optimistic toggle
// writes to). Re-runs whenever the current track changes.
let isFavorite = $state(false);
let favoriteLoadedFor = "";
$effect(() => {
const id = displayMedia?.id;
if (!id || id === favoriteLoadedFor) return;
favoriteLoadedFor = id;
isFavorite = false;
const userId = auth.getUserId();
if (!userId) return;
commands
.storageGetPlaybackProgress(userId, id)
.then((p) => {
// Guard against a race if the track changed while awaiting.
if (displayMedia?.id === id) isFavorite = p?.isFavorite ?? false;
})
.catch(() => {});
});
const displayPosition = $derived($mergedPosition);
const displayDuration = $derived($mergedDuration);
@@ -358,7 +380,7 @@
{#if displayMedia}
<FavoriteButton
itemId={displayMedia?.id ?? ""}
isFavorite={displayMedia?.userData?.isFavorite ?? false}
bind:isFavorite
size="sm"
/>
{/if}