Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions assets/theme-v2/css/gamefoundrystudio.css
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,24 @@ body.tool-focus-mode .tool-column:last-of-type {
box-shadow: 0 1px 4px var(--swatch-shadow-color)
}

.sprite-preview-shell {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire the preview styles into the loaded stylesheet

These new .sprite-preview-* rules are added to gamefoundrystudio.css, but the sprites page only links assets/theme-v2/css/theme.css, and that entrypoint does not import gamefoundrystudio.css. In the actual /toolbox/sprites/index.html page the preview/export panel therefore loses the intended shell, border, sizing, and pixelated rendering; move these rules into an imported Theme V2 module or wire this stylesheet intentionally.

Useful? React with 👍 / 👎.

display: flex;
justify-content: center;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--panel-soft)
}

.sprite-preview-canvas {
width: min(100%, 160px);
height: auto;
aspect-ratio: 1;
border: 1px solid var(--line);
background: var(--card-background);
image-rendering: pixelated
}

@media(max-width:980px) {

.grid.cols-4,
Expand Down
75 changes: 75 additions & 0 deletions assets/toolbox/sprites/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ const DEFAULT_GRID_SIZE = 16;
const SUPPORTED_GRID_SIZES = Object.freeze([16, 32]);
const DRAWING_TOOLS = Object.freeze(["pencil", "eraser", "fill"]);
const EDITOR_COLOR_KEYS = Object.freeze(["ink", "orange", "gold", "green", "blue"]);
const EDITOR_COLOR_CSS_VARIABLES = Object.freeze({
blue: "--electric-blue",
gold: "--forge-gold",
green: "--green",
ink: "--text",
orange: "--molten-orange",
});

const editorState = {
activeTool: "pencil",
Expand Down Expand Up @@ -51,6 +58,7 @@ function updateDraftStatus() {
if (status) {
status.textContent = draftStatusText();
}
renderPreview();
}

function updatePaletteStatus() {
Expand Down Expand Up @@ -114,6 +122,64 @@ function fillGrid() {
updateDraftStatus();
}

function editorColorValue(colorKey) {
const variableName = EDITOR_COLOR_CSS_VARIABLES[normalizeColorKey(colorKey)];
const value = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim();
return value || "#111111";
}

function renderPreview() {
const canvas = document.querySelector("[data-sprites-preview-canvas]");
if (!canvas) {
return;
}
const context = canvas.getContext("2d");
if (!context) {
return;
}
const size = editorState.gridSize;
const cellSize = canvas.width / size;
context.clearRect(0, 0, canvas.width, canvas.height);
for (const [key, colorKey] of editorState.paintedPixels.entries()) {
const [rowText, columnText] = key.split(":");
const row = Number(rowText);
const column = Number(columnText);
if (!Number.isFinite(row) || !Number.isFinite(column)) {
continue;
}
context.fillStyle = editorColorValue(colorKey);
context.fillRect((column - 1) * cellSize, (row - 1) * cellSize, cellSize, cellSize);
}
}

function exportPreviewPng() {
const canvas = document.querySelector("[data-sprites-preview-canvas]");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Export the sprite at the selected grid size

When the user downloads after choosing either 16x16 or 32x32, this exports the fixed preview canvas, which is declared as width="160" height="160" in toolbox/sprites/index.html, so every PNG is 160×160 instead of matching the advertised draft grid dimensions. That makes the downloaded sprite asset inconsistent with the selected canvas size; render the export from an offscreen canvas sized to editorState.gridSize and keep the 160px canvas only for display.

Useful? React with 👍 / 👎.

const status = document.querySelector("[data-sprites-export-status]");
if (!canvas) {
return;
}
canvas.toBlob((blob) => {
if (!blob) {
if (status) {
status.textContent = "PNG export is unavailable in this browser session.";
}
return;
}
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = objectUrl;
link.download = "sprite-creator-draft.png";
link.rel = "noopener";
document.body.append(link);
link.click();
link.remove();
URL.revokeObjectURL(objectUrl);
if (status) {
status.textContent = "PNG downloaded from unsaved editor draft.";
}
}, "image/png");
}

function setGridSize(size) {
const grid = document.querySelector("[data-sprites-pixel-grid]");
const status = document.querySelector("[data-sprites-grid-status]");
Expand Down Expand Up @@ -186,9 +252,18 @@ function wirePaletteButtons() {
});
}

function wireExportButton() {
const button = document.querySelector("[data-sprites-export-png]");
if (button) {
button.addEventListener("click", exportPreviewPng);
}
}

wireGridControls();
wireDrawingTools();
wirePaletteButtons();
wireExportButton();
setGridSize(DEFAULT_GRID_SIZE);
setActiveTool(editorState.activeTool);
setActiveColor(editorState.activeColor);
renderPreview();
12 changes: 12 additions & 0 deletions dev/tests/playwright/tools/SpritesToolShell.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status
await expect(page.locator("[data-sprites-pixel-grid] .is-painted")).toHaveCount(1024);
await expect(page.locator("[data-sprites-pixel-grid] .sprite-canvas-cell--blue")).toHaveCount(1024);
await expect(page.locator("[data-sprites-draft-status]")).toContainText("1024 draft pixels painted");
await expect(page.locator("[data-sprites-preview-canvas]")).toBeVisible();
const previewHasPaint = await page.locator("[data-sprites-preview-canvas]").evaluate((canvas) => {
const context = canvas.getContext("2d");
const pixel = context.getImageData(1, 1, 1, 1).data;
return pixel[3] > 0;
});
expect(previewHasPaint).toBe(true);
const downloadPromise = page.waitForEvent("download");
await page.getByRole("button", { name: "Download PNG" }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe("sprite-creator-draft.png");
await expect(page.locator("[data-sprites-export-status]")).toContainText("PNG downloaded");
await expect(page.locator("[data-sprites-shell-status]")).toContainText("Editor ready");
await expect(page.locator("main")).toContainText("Palette/Colors remains the reusable color source");
await expect(page.locator("main")).not.toContainText(/Not implemented yet|future rebuild work|Static wireframe only|Plan sprite creation/i);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# PR_26179_CHARLIE_027-sprites-preview-export

Team: CHARLIE
Workflow: stacked feature workflow
Base branch: PR_26179_CHARLIE_026-sprites-palette-panel
Canonical ZIP path: dev/workspace/zip/PR_26179_CHARLIE_027-sprites-preview-export_delta.zip

## Summary

Added live preview and PNG download/export for the unsaved Sprite Creator editor draft. Export uses canvas `toBlob` and browser download behavior. This PR does not save to library, publish, write browser storage, or add API/DB/schema changes.

## Branch Validation

PASS

- Current branch: PR_26179_CHARLIE_027-sprites-preview-export
- Based on: PR_26179_CHARLIE_026-sprites-palette-panel
- No start_of_day files changed
- No DB/API/schema files changed
- No stale PR #219-#228 code copied

## Requirement Checklist

| Requirement | Status | Notes |
| --- | --- | --- |
| Add live preview | PASS | Preview canvas reflects unsaved draft pixels. |
| Add PNG export/download | PASS | Download button exports `sprite-creator-draft.png`. |
| No save-to-library | PASS | No saved sprite library or product persistence added. |
| No publishing | PASS | Export is local download only. |
| Avoid persisted data URLs | PASS | Uses `canvas.toBlob`; no `imageDataUrl` or `toDataURL`. |
| No DB/API/schema changes | PASS | Only UI/JS/CSS/test/report files changed. |

## Validation Lane Report

Commands:

```text
node --check assets/toolbox/sprites/js/index.js
node --check dev/tests/playwright/tools/SpritesToolShell.spec.mjs
git diff --check -- toolbox/sprites/index.html assets/toolbox/sprites/js/index.js assets/theme-v2/css/gamefoundrystudio.css dev/tests/playwright/tools/SpritesToolShell.spec.mjs
rg --pcre2 -n -i "localStorage|sessionStorage|indexedDB|imageDataUrl|toDataURL|<style|style=|<script(?![^>]+src=)|on(click|change|submit|input|load|error)=|local-mem|fake-login|MEM DB" toolbox/sprites/index.html assets/toolbox/sprites/js/index.js dev/tests/playwright/tools/SpritesToolShell.spec.mjs
npx playwright test dev/tests/playwright/tools/SpritesToolShell.spec.mjs --workers=1 --reporter=list --output=<temp>
```

Results:

- Node syntax checks: PASS
- `git diff --check`: PASS
- Guard scan: PASS, no matches
- Targeted Playwright: PASS, 1 test passed

## Manual Validation Notes

1. Open `/toolbox/sprites/index.html` from the stacked branch.
2. Draw or fill pixels.
3. Confirm the preview canvas shows the draft.
4. Click Download PNG and confirm a `sprite-creator-draft.png` download starts.
5. Confirm no save-to-library or publishing controls are present.

## ZIP Path

`dev/workspace/zip/PR_26179_CHARLIE_027-sprites-preview-export_delta.zip`
2 changes: 1 addition & 1 deletion docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
assets/toolbox/sprites/js/index.js
assets/theme-v2/css/gamefoundrystudio.css
dev/tests/playwright/tools/SpritesToolShell.spec.mjs
docs_build/dev/reports/PR_26179_CHARLIE_026-sprites-palette-panel.md
docs_build/dev/reports/PR_26179_CHARLIE_027-sprites-preview-export.md
docs_build/dev/reports/codex_changed_files.txt
docs_build/dev/reports/codex_review.diff
Loading
Loading