import { base64url } from "jose";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Pattern } from "../../core/CosmeticSchemas";
import { PatternDecoder } from "../../core/PatternDecoder";
import { translateText } from "../Utils";
export const BUTTON_WIDTH = 150;
@customElement("pattern-button")
export class PatternButton extends LitElement {
@property({ type: Object })
pattern: Pattern | null = null;
@property({ type: Function })
onSelect?: (pattern: Pattern | null) => void;
@property({ type: Function })
onPurchase?: (pattern: Pattern) => void;
createRenderRoot() {
return this;
}
private translatePatternName(prefix: string, patternName: string): string {
const translation = translateText(`${prefix}.${patternName}`);
if (translation.startsWith(prefix)) {
return patternName[0].toUpperCase() + patternName.substring(1);
}
return translation;
}
private handleClick() {
const isDefaultPattern = this.pattern === null;
if (isDefaultPattern || this.pattern?.product === null) {
this.onSelect?.(this.pattern);
}
}
private handlePurchase(e: Event) {
e.stopPropagation();
if (this.pattern?.product) {
this.onPurchase?.(this.pattern);
}
}
render() {
const isDefaultPattern = this.pattern === null;
const isPurchasable = !isDefaultPattern && this.pattern?.product !== null;
return html`
${isPurchasable
? html`
`
: null}
`;
}
}
export function renderPatternPreview(
pattern: string | null,
width: number,
height: number,
): TemplateResult {
if (pattern === null) {
return renderBlankPreview(width, height);
}
const dataUrl = generatePreviewDataUrl(pattern, width, height);
return html`
`;
}
function renderBlankPreview(width: number, height: number): TemplateResult {
return html`
`;
}
const patternCache = new Map();
const DEFAULT_PATTERN_B64 = "AAAAAA"; // Empty 2x2 pattern
const COLOR_SET = [0, 0, 0, 255]; // Black
const COLOR_UNSET = [255, 255, 255, 255]; // White
function generatePreviewDataUrl(
pattern?: string,
width?: number,
height?: number,
): string {
pattern ??= DEFAULT_PATTERN_B64;
const patternLookupKey = `${pattern}-${width}-${height}`;
if (patternCache.has(patternLookupKey)) {
return patternCache.get(patternLookupKey)!;
}
// Calculate canvas size
let decoder: PatternDecoder;
try {
decoder = new PatternDecoder(pattern, 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;
let i = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const rgba = decoder.isSet(x, y) ? COLOR_SET : COLOR_UNSET;
data[i++] = rgba[0]; // Red
data[i++] = rgba[1]; // Green
data[i++] = rgba[2]; // Blue
data[i++] = rgba[3]; // Alpha
}
}
// Create a data URL
ctx.putImageData(imageData, 0, 0);
const dataUrl = canvas.toDataURL("image/png");
patternCache.set(patternLookupKey, dataUrl);
return dataUrl;
}