From 95a39cc077921487b475c2258c2f25a473e358fe Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 31 May 2026 09:05:17 -0400 Subject: [PATCH] Pixel-art preview: one-click PNG / GIF download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The community-browser preview page for pixel-art designs now exposes a "⬇ Download PNG" (single-frame) or "⬇ Download GIF" (multi-frame, animated) button next to Use/Fork, Share, and Save JSON. Means viewers can grab the sprite asset without forking it into the editor first. Implementation reuses the existing exportPixelArtToPngBlob / exportPixelArtToGifBlob / pixelExportFilename helpers from the editor's full export menu, at the default 4× scale (matches the editor's _pixelExportOpts default). GIF carries the source design's fps + loop settings. Gated on mode === 'pixelart' && !lockedPremium — locked premium pixel-art designs only expose the button after unlock (the design data isn't available before then). Self-authored designs and any unlocked / free pixel-art design get the button regardless of who published it. Button shows "Preparing…" while encoding (matters for big multi-frame GIFs where encoding can take a second or two). Errors land as a toast. --- src/index.html | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/index.html b/src/index.html index 841b105..25d4705 100644 --- a/src/index.html +++ b/src/index.html @@ -22252,6 +22252,63 @@ actionsDiv.insertBefore(presentBtn, saveJsonBtn); } + // Pixel-art designs get a one-click Download button that emits + // a PNG for single-frame designs and an animated GIF for + // multi-frame ones. Reuses the same export blob helpers the + // editor's full export menu uses, at the default 4× scale. + // Locked premium pixel-art designs only show this after unlock + // — the design data isn't available otherwise. + if (mode === 'pixelart' && !lockedPremium) { + const pa = p.pixelArt || {}; + const frameCount = Array.isArray(pa.frames) ? pa.frames.length : 0; + const isAnimated = frameCount > 1; + const dlBtn = document.createElement('button'); + dlBtn.type = 'button'; + dlBtn.textContent = isAnimated ? '⬇ Download GIF' : '⬇ Download PNG'; + dlBtn.title = isAnimated + ? `Download as animated GIF (${frameCount} frames, ${pa.fps || 8} fps) at 4× scale` + : 'Download as PNG at 4× scale'; + dlBtn.addEventListener('click', async (e) => { + e.stopPropagation(); + const originalLabel = dlBtn.textContent; + dlBtn.disabled = true; + dlBtn.textContent = 'Preparing…'; + try { + let blob; + if (isAnimated) { + blob = exportPixelArtToGifBlob(pa, { + scale: 4, + fps: pa.fps || 8, + loop: pa.loop !== false, + }); + } else { + blob = await exportPixelArtToPngBlob(pa, { + scale: 4, + transparent: true, + includeGrid: false, + }); + } + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = pixelExportFilename(pa, 4, false, isAnimated ? 'gif' : 'png'); + document.body.appendChild(a); + a.click(); + a.remove(); + setTimeout(() => URL.revokeObjectURL(url), 1000); + toastSuccess(isAnimated + ? `Downloaded GIF (${frameCount} frames)` + : `Downloaded PNG (${pa.width || 0}×${pa.height || 0} px at 4×)`); + } catch (err) { + toastError(err?.message || 'Download failed.'); + } finally { + dlBtn.disabled = false; + dlBtn.textContent = originalLabel; + } + }); + actionsDiv.insertBefore(dlBtn, saveJsonBtn); + } + if (isDiscDesign) { const import1Btn = document.createElement('button'); import1Btn.type = 'button';