/** * — upload artwork → generate print-ready files. * Product selector (sticker, poster, tee), image upload with preview, * generate button, artifact result display with download link. */ class FolkSwagDesigner extends HTMLElement { private shadow: ShadowRoot; private selectedProduct = "sticker"; private imageFile: File | null = null; private imagePreview = ""; private title = ""; private generating = false; private artifact: any = null; private error = ""; constructor() { super(); this.shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { this.render(); } private getApiBase(): string { const path = window.location.pathname; const parts = path.split("/").filter(Boolean); return parts.length >= 2 ? `/${parts[0]}/swag` : "/demo/swag"; } private async generate() { if (!this.imageFile || this.generating) return; this.generating = true; this.error = ""; this.artifact = null; this.render(); try { const formData = new FormData(); formData.append("image", this.imageFile); formData.append("product", this.selectedProduct); formData.append("title", this.title || "Untitled Design"); const res = await fetch(`${this.getApiBase()}/api/artifact`, { method: "POST", body: formData, }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || `Failed: ${res.status}`); } this.artifact = await res.json(); } catch (e) { this.error = e instanceof Error ? e.message : "Generation failed"; } finally { this.generating = false; this.render(); } } private render() { const products = [ { id: "sticker", name: "Sticker Sheet", icon: "\u{1F4CB}", desc: "A4 vinyl stickers" }, { id: "poster", name: "Poster (A3)", icon: "\u{1F5BC}", desc: "A3 art print" }, { id: "tee", name: "T-Shirt", icon: "\u{1F455}", desc: "12x16\" DTG print" }, ]; this.shadow.innerHTML = `

\u{1F3A8} Swag Designer

${products.map((p) => `
${p.icon}
${p.name}
${p.desc}
`).join("")}
${this.imagePreview ? `Preview` : `
\u{1F4C1} Click or drag to upload artwork (PNG, JPG, SVG)
`}
${this.error ? `
${this.esc(this.error)}
` : ""} ${this.artifact ? `

\u2705 ${this.esc(this.artifact.payload?.title || "Artifact")}

${this.esc(this.artifact.spec?.product_type || "")} \u2022 ${this.artifact.spec?.dimensions?.width_mm}x${this.artifact.spec?.dimensions?.height_mm}mm \u2022 ${this.artifact.spec?.dpi}dpi
${Object.entries(this.artifact.render_targets || {}).map(([key, target]: [string, any]) => ` \u{2B07} Download ${target.format.toUpperCase()} `).join("")}
Show artifact envelope \u25BC
${this.esc(JSON.stringify(this.artifact, null, 2))}
` : ""} `; // Event listeners this.shadow.querySelectorAll(".product").forEach((el) => { el.addEventListener("click", () => { this.selectedProduct = (el as HTMLElement).dataset.product || "sticker"; this.render(); }); }); const uploadArea = this.shadow.querySelector(".upload-area"); const fileInput = this.shadow.querySelector('input[type="file"]') as HTMLInputElement; uploadArea?.addEventListener("click", () => fileInput?.click()); fileInput?.addEventListener("change", () => { const file = fileInput.files?.[0]; if (file) { this.imageFile = file; this.imagePreview = URL.createObjectURL(file); this.render(); } }); this.shadow.querySelector(".title-input")?.addEventListener("input", (e) => { this.title = (e.target as HTMLInputElement).value; }); this.shadow.querySelector(".generate-btn")?.addEventListener("click", () => this.generate()); this.shadow.querySelector(".json-toggle")?.addEventListener("click", () => { const pre = this.shadow.querySelector(".json-pre"); pre?.classList.toggle("visible"); }); this.shadow.querySelector('[data-action="copy-json"]')?.addEventListener("click", () => { navigator.clipboard.writeText(JSON.stringify(this.artifact, null, 2)); }); } private esc(s: string): string { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; } } customElements.define("folk-swag-designer", FolkSwagDesigner);