Files
jellytau/src/lib/components/common/CachedImage.svelte
T
dtourolle 3363ff7f08 Merge branch 'master' into worktree-mosaic-library
# Conflicts:
#	scripts/extract-traces.test.ts
2026-08-16 00:51:46 +02:00

109 lines
2.7 KiB
Svelte

<script lang="ts">
import { commands } from "$lib/api/bindings";
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;
/**
* Called once the bitmap is decoded, with its intrinsic pixel size. Lets a
* layout that sizes boxes from artwork (the mosaic) use the shape the image
* actually has rather than the one its item type suggests.
* TRACES: UR-075 | DR-174
*/
onNaturalSize?: (width: number, height: number) => void;
}
let {
itemId,
imageType = "Primary",
tag,
maxWidth,
maxHeight,
class: className = "",
alt = "",
onNaturalSize,
}: 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 commands.imageGetUrl(repositoryHandle, {
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}
onload={(e) => {
const img = e.currentTarget as HTMLImageElement;
if (img.naturalWidth > 0 && img.naturalHeight > 0) {
onNaturalSize?.(img.naturalWidth, img.naturalHeight);
}
}}
/>
{/if}