import { Colord } from "colord"; import { base64url } from "jose"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ColorPalette, DefaultPattern, Pattern, } from "../../core/CosmeticSchemas"; import { PatternDecoder } from "../../core/PatternDecoder"; import { PlayerPattern } from "../../core/Schemas"; import { translateText } from "../Utils"; export const BUTTON_WIDTH = 150; @customElement("pattern-button") export class PatternButton extends LitElement { @property({ type: Boolean }) selected: boolean = false; @property({ type: Object }) pattern: Pattern | null = null; @property({ type: Object }) colorPalette: ColorPalette | null = null; @property({ type: Boolean }) requiresPurchase: boolean = false; @property({ type: Function }) onSelect?: (pattern: PlayerPattern | null) => void; @property({ type: Function }) onPurchase?: (pattern: Pattern, colorPalette: ColorPalette | null) => void; createRenderRoot() { return this; } private translateCosmetic(prefix: string, patternName: string): string { const translation = translateText(`${prefix}.${patternName}`); if (translation.startsWith(prefix)) { return patternName .split("_") .filter((word) => word.length > 0) .map((word) => word[0].toUpperCase() + word.substring(1)) .join(" "); } return translation; } private handleClick() { if (this.pattern === null) { this.onSelect?.(null); return; } this.onSelect?.({ name: this.pattern!.name, patternData: this.pattern!.pattern, colorPalette: this.colorPalette ?? undefined, } satisfies PlayerPattern); } private handlePurchase(e: Event) { e.stopPropagation(); if (this.pattern?.product) { this.onPurchase?.(this.pattern, this.colorPalette ?? null); } } render() { const isDefaultPattern = this.pattern === null; return html`
${this.requiresPurchase && this.pattern?.product ? html`
` : null}
`; } } export function renderPatternPreview( pattern: PlayerPattern | null, width: number, height: number, ): TemplateResult { if (pattern === null) { return renderBlankPreview(width, height); } return html`Pattern preview`; } function renderBlankPreview(width: number, height: number): TemplateResult { return html`
`; } const patternCache = new Map(); const DEFAULT_PRIMARY = new Colord("#ffffff").toRgb(); // White const DEFAULT_SECONDARY = new Colord("#000000").toRgb(); // Black function generatePreviewDataUrl( pattern?: PlayerPattern, width?: number, height?: number, ): string { pattern ??= DefaultPattern; const patternLookupKey = [ pattern.name, pattern.colorPalette?.primaryColor ?? "undefined", pattern.colorPalette?.secondaryColor ?? "undefined", width, height, ].join("-"); if (patternCache.has(patternLookupKey)) { return patternCache.get(patternLookupKey)!; } // Calculate canvas size let decoder: PatternDecoder; try { decoder = new PatternDecoder( { name: pattern.name, patternData: pattern.patternData, colorPalette: pattern.colorPalette, }, base64url.decode, ); } catch (e) { console.error("Error decoding pattern", e); return ""; } const scaledWidth = decoder.scaledWidth(); const scaledHeight = decoder.scaledHeight(); width = width === undefined ? scaledWidth : Math.max(1, Math.floor(width / scaledWidth)) * scaledWidth; height = height === undefined ? scaledHeight : Math.max(1, Math.floor(height / scaledHeight)) * scaledHeight; // Create the canvas const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("2D context not supported"); // Create an image const imageData = ctx.createImageData(width, height); const data = imageData.data; const primary = pattern.colorPalette?.primaryColor ? new Colord(pattern.colorPalette.primaryColor).toRgb() : DEFAULT_PRIMARY; const secondary = pattern.colorPalette?.secondaryColor ? new Colord(pattern.colorPalette.secondaryColor).toRgb() : DEFAULT_SECONDARY; let i = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const rgba = decoder.isPrimary(x, y) ? primary : secondary; data[i++] = rgba.r; data[i++] = rgba.g; data[i++] = rgba.b; data[i++] = 255; // Alpha } } // Create a data URL ctx.putImageData(imageData, 0, 0); const dataUrl = canvas.toDataURL("image/png"); patternCache.set(patternLookupKey, dataUrl); return dataUrl; }