Reconcile the ~50 frontend files that consumed the old hand-written types against
the stricter generated bindings (141 -> 0 svelte-check errors):
- Nullable strictness: null-coalesce/guard at consumer sites; widen shared helpers
(CachedImage.tag, ticksToSeconds, formatDuration, getSessionIcon, session store
send*/transferToRemote) to accept null.
- Field-name fixes exposed by codegen: userData.played -> isPlayed; remote
NowPlayingItem uses album (not albumName); ArtistItem id/name.
- player.ts: normalize remote NowPlayingItem -> MediaItem in mergedMedia.
- ArtistItem now serializes camelCase (PascalCase alias retained for Jellyfin
deserialize); update its serialize test.
- Update downloads.test.ts to the bundled { request } shape; complete the
sessions.test.ts NowPlayingItem mock.
Frontend: svelte-check 0 errors, vitest 448 passing. Backend: 387 passing.
94 lines
2.2 KiB
Svelte
94 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { auth } from "$lib/stores/auth";
|
|
import { get } from "svelte/store";
|
|
|
|
interface Props {
|
|
itemId: string;
|
|
imageType?: string;
|
|
tag?: string | null;
|
|
maxWidth?: number;
|
|
maxHeight?: number;
|
|
class?: string;
|
|
alt?: string;
|
|
}
|
|
|
|
let {
|
|
itemId,
|
|
imageType = "Primary",
|
|
tag,
|
|
maxWidth,
|
|
maxHeight,
|
|
class: className = "",
|
|
alt = "",
|
|
}: Props = $props();
|
|
|
|
let imageUrl = $state<string | null>(null);
|
|
let loading = $state(true);
|
|
let error = $state(false);
|
|
let lastLoadKey = "";
|
|
|
|
async function loadImage() {
|
|
if (!itemId) {
|
|
loading = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
loading = true;
|
|
error = false;
|
|
|
|
// Get repository handle from auth store
|
|
const authState = get(auth);
|
|
if (!authState.isAuthenticated) {
|
|
throw new Error("Not authenticated");
|
|
}
|
|
const repository = auth.getRepository();
|
|
const repositoryHandle = repository.getHandle();
|
|
|
|
// Call Rust to get image as base64 data URL
|
|
const dataUrl = await invoke<string>("image_get_url", {
|
|
repositoryHandle,
|
|
request: {
|
|
itemId,
|
|
imageType,
|
|
maxWidth,
|
|
maxHeight,
|
|
tag,
|
|
},
|
|
});
|
|
|
|
// Use data URL directly
|
|
imageUrl = dataUrl;
|
|
error = false;
|
|
} catch (e) {
|
|
error = true;
|
|
imageUrl = null;
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
// Only reload when the image identity changes (not on parent re-renders)
|
|
$effect(() => {
|
|
const loadKey = `${itemId}|${imageType}|${tag || ""}`;
|
|
if (loadKey !== lastLoadKey) {
|
|
lastLoadKey = loadKey;
|
|
imageUrl = null;
|
|
loadImage();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{#if loading}
|
|
<div class="{className} bg-gray-700 animate-pulse" aria-busy="true" aria-label="Loading image"></div>
|
|
{:else if error || !imageUrl}
|
|
<div class="{className} bg-gray-800 flex items-center justify-center">
|
|
<svg class="w-8 h-8 text-gray-600" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
|
|
</svg>
|
|
</div>
|
|
{:else}
|
|
<img src={imageUrl} {alt} class={className} />
|
|
{/if}
|