diff --git a/components/public/material-poster.tsx b/components/public/material-poster.tsx index 4d457a5..a041c1c 100644 --- a/components/public/material-poster.tsx +++ b/components/public/material-poster.tsx @@ -7,16 +7,10 @@ import type { CatalogAscensionGroup, CatalogBaseStat, } from "@/lib/catalog/lunaris"; +import { mergePosterMaterials, type PosterMaterial } from "@/lib/material-poster"; import { accentGradient } from "@/lib/overview-image"; import { cn } from "@/lib/utils"; -type PosterMaterial = { - key: string; - amount: number; - name: string; - imageUrl: string | null; -}; - export type PosterBlendMode = "normal" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity"; type PosterCharacter = { @@ -80,7 +74,9 @@ export function MaterialPoster({ }) { const dragStart = useRef<{ pointerId: number; x: number; y: number; offsetX: number; offsetY: number } | null>(null); const stats = character.baseStats.slice(0, 4); - const materials = [...character.ascension, ...character.leveling].flatMap((group) => group.items); + const materials = mergePosterMaterials( + [...character.ascension, ...character.leveling].flatMap((group) => group.items), + ); const background = `linear-gradient(180deg, ${topColors[0]} 0%, ${topColors[1]} 22%, rgba(0,0,0,0) 42%, rgba(0,0,0,0) 62%, ${bottomColors[0]} 88%, ${bottomColors[1]} 100%)`; function handlePointerDown(event: PointerEvent) { diff --git a/lib/material-poster.test.ts b/lib/material-poster.test.ts new file mode 100644 index 0000000..874e34e --- /dev/null +++ b/lib/material-poster.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; + +import { mergePosterMaterials } from "@/lib/material-poster"; + +describe("mergePosterMaterials", () => { + it("combines amounts when leveling and talent materials use the same image", () => { + expect(mergePosterMaterials([ + { key: "talent", amount: 9, name: "Talent", imageUrl: "/shared.webp" }, + { key: "leveling", amount: 18, name: "Leveling", imageUrl: "/shared.webp" }, + ])).toEqual([ + { key: "talent", amount: 27, name: "Talent", imageUrl: "/shared.webp" }, + ]); + }); + + it("keeps materials without images separate", () => { + const materials = [ + { key: "first", amount: 1, name: "First", imageUrl: null }, + { key: "second", amount: 2, name: "Second", imageUrl: null }, + ]; + + expect(mergePosterMaterials(materials)).toEqual(materials); + }); +}); diff --git a/lib/material-poster.ts b/lib/material-poster.ts new file mode 100644 index 0000000..798a108 --- /dev/null +++ b/lib/material-poster.ts @@ -0,0 +1,33 @@ +export type PosterMaterial = { + key: string; + amount: number; + name: string; + imageUrl: string | null; +}; + +export function mergePosterMaterials(materials: PosterMaterial[]): PosterMaterial[] { + const merged: PosterMaterial[] = []; + const indexByImageUrl = new Map(); + + for (const material of materials) { + if (!material.imageUrl) { + merged.push(material); + continue; + } + + const existingIndex = indexByImageUrl.get(material.imageUrl); + if (existingIndex === undefined) { + indexByImageUrl.set(material.imageUrl, merged.length); + merged.push(material); + continue; + } + + const existing = merged[existingIndex]; + merged[existingIndex] = { + ...existing, + amount: existing.amount + material.amount, + }; + } + + return merged; +}