From 70606553b8712a0e3f175c91884d75174383e1a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Jul 2026 15:37:41 +0200 Subject: [PATCH] refactor: item rendering --- src/lib/components/item/ContainedItem.svelte | 3 +- src/lib/components/item/Item.svelte | 6 ++- .../components/item/ResolvedItemImage.svelte | 32 ++++++++++++ src/lib/components/item/item-content.svelte | 6 ++- src/lib/shared/items/resolve-item-texture.ts | 49 +++++++++++++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 src/lib/components/item/ResolvedItemImage.svelte create mode 100644 src/lib/shared/items/resolve-item-texture.ts diff --git a/src/lib/components/item/ContainedItem.svelte b/src/lib/components/item/ContainedItem.svelte index 04f1c8b1e..ce11cef71 100644 --- a/src/lib/components/item/ContainedItem.svelte +++ b/src/lib/components/item/ContainedItem.svelte @@ -3,6 +3,7 @@ import { cn } from "$lib/shared/utils"; import Image from "@lucide/svelte/icons/image"; import { Avatar } from "bits-ui"; + import ResolvedItemImage from "./ResolvedItemImage.svelte"; let { piece, isInventory }: { piece: ModelsStrippedItem; isInventory?: boolean } = $props(); @@ -10,7 +11,7 @@
- + diff --git a/src/lib/components/item/Item.svelte b/src/lib/components/item/Item.svelte index 60388a0ba..d83a40bd9 100644 --- a/src/lib/components/item/Item.svelte +++ b/src/lib/components/item/Item.svelte @@ -7,6 +7,7 @@ import ImageOff from "@lucide/svelte/icons/image-off"; import { Avatar, Tooltip, type AvatarImageLoadingStatus } from "bits-ui"; import { IsInViewport } from "runed"; + import ResolvedItemImage from "./ResolvedItemImage.svelte"; type Props = { piece: ModelsStrippedItem; @@ -19,11 +20,12 @@ let targetNode = $state(null); let hasBeenInViewport = $state(false); let loadingStatus = $state(null!); + let resolvedTexturePack = $state(); const internalState = getInternalState(); const inViewport = new IsInViewport(() => targetNode, { rootMargin: "200px 0px", threshold: 0 }); - const skyblockItem = $derived(piece); + const skyblockItem = $derived({ ...piece, texture_pack: resolvedTexturePack ?? piece.texture_pack }); const bgColor = $derived(getRarityClass(piece.rarity ?? ("common".toLowerCase() as string), "bg")); const recombobulated = $derived(showRecombobulated && (skyblockItem.recombobulated ?? false)); const enchanted = $derived(skyblockItem?.texture_path?.includes("/api/leather/") ? false : skyblockItem.shiny); @@ -53,7 +55,7 @@
{#if hasBeenInViewport} - + (resolvedTexturePack = resolution.texture_pack)} /> {#if loadingStatus === "loading"} {@render loadingState()} {:else} diff --git a/src/lib/components/item/ResolvedItemImage.svelte b/src/lib/components/item/ResolvedItemImage.svelte new file mode 100644 index 000000000..e3d8521e3 --- /dev/null +++ b/src/lib/components/item/ResolvedItemImage.svelte @@ -0,0 +1,32 @@ + + + diff --git a/src/lib/components/item/item-content.svelte b/src/lib/components/item/item-content.svelte index f0a3a8f01..4789128f4 100644 --- a/src/lib/components/item/item-content.svelte +++ b/src/lib/components/item/item-content.svelte @@ -9,6 +9,7 @@ import TriangleAlert from "@lucide/svelte/icons/triangle-alert"; import { Avatar, Button } from "bits-ui"; import ContainedItem from "./ContainedItem.svelte"; + import ResolvedItemImage from "./ResolvedItemImage.svelte"; type Props = { piece: ModelsStrippedItem; @@ -16,9 +17,10 @@ }; let { piece, isDrawer }: Props = $props(); + let resolvedTexturePack = $state(); const preferences = getPreferences(); - const skyblockItem = $derived(piece); + const skyblockItem = $derived({ ...piece, texture_pack: resolvedTexturePack ?? piece.texture_pack }); const itemName = $derived(piece?.display_name); const itemNameHtml = $derived(itemName ? renderLore(itemName) : ""); const isMulticolor = $derived((itemNameHtml?.match(/<\/span>/g) || [])?.length > 1); @@ -34,7 +36,7 @@
- + (resolvedTexturePack = resolution.texture_pack)} /> diff --git a/src/lib/shared/items/resolve-item-texture.ts b/src/lib/shared/items/resolve-item-texture.ts new file mode 100644 index 000000000..eb85c47ba --- /dev/null +++ b/src/lib/shared/items/resolve-item-texture.ts @@ -0,0 +1,49 @@ +export type ResolvedItemTexture = { + texture: string; + texture_pack?: string; +}; + +const resolutionCache = new Map>(); + +function enabledPacksCookie(): string { + if (typeof document === "undefined") return ""; + const cookie = document.cookie + .split(";") + .map((part) => part.trim()) + .find((part) => part.startsWith("enabledPacks=")); + const value = cookie?.slice("enabledPacks=".length) ?? ""; + try { + return decodeURIComponent(value); + } catch { + return value; + } +} + +function resolverUrl(textureUrl: string, enabledPacks: string): string | null { + const url = new URL(textureUrl, window.location.origin); + const match = url.pathname.match(/^(.*\/api\/item\/)([^/]+)$/); + if (!match) return null; + url.pathname = `${match[1]}${match[2]}/resolve`; + if (enabledPacks) url.searchParams.set("enabledPacks", enabledPacks); + return url.toString(); +} + +export function resolveItemTexture(textureUrl: string): Promise { + const enabledPacks = enabledPacksCookie(); + const cacheKey = `${textureUrl}|${enabledPacks}`; + const cached = resolutionCache.get(cacheKey); + if (cached) return cached; + + const url = resolverUrl(textureUrl, enabledPacks); + if (!url) return Promise.resolve({ texture: textureUrl }); + + const resolution = fetch(url) + .then(async (response) => { + if (!response.ok) throw new Error(`Failed to resolve item texture: ${response.status}`); + return (await response.json()) as ResolvedItemTexture; + }) + .catch(() => ({ texture: textureUrl })); + + resolutionCache.set(cacheKey, resolution); + return resolution; +}