feat: improve dev tool UX

- Show uploaded spritesheet image
- Auto-calculate frame dimensions from image size + frame count
- Add scale slider (1x-8x) for preview size

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Corey Johnson 2026-01-06 13:39:39 -08:00
parent d3e1eb00bd
commit b1a87662fb

View File

@ -3,21 +3,32 @@ import { render } from "hono/jsx/dom"
const App = () => { const App = () => {
const [spriteUrl, setSpriteUrl] = useState("") const [spriteUrl, setSpriteUrl] = useState("")
const [width, setWidth] = useState(32) const [imageWidth, setImageWidth] = useState(0)
const [height, setHeight] = useState(32) const [imageHeight, setImageHeight] = useState(0)
const [frames, setFrames] = useState(4) const [frames, setFrames] = useState(4)
const [columns, setColumns] = useState<number | undefined>() const [columns, setColumns] = useState<number | undefined>()
const [frameDuration, setFrameDuration] = useState(100) const [frameDuration, setFrameDuration] = useState(100)
const [scale, setScale] = useState(1)
const handleFileChange = (e: Event) => { const handleFileChange = (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0] const file = (e.target as HTMLInputElement).files?.[0]
if (!file) return if (!file) return
setSpriteUrl(URL.createObjectURL(file)) const url = URL.createObjectURL(file)
const img = new Image()
img.onload = () => {
setImageWidth(img.width)
setImageHeight(img.height)
setSpriteUrl(url)
}
img.src = url
} }
// Auto-calculate frame dimensions from image size and frame count
const isGrid = columns !== undefined const isGrid = columns !== undefined
const cols = isGrid ? columns : frames const cols = isGrid ? columns : frames
const rows = isGrid ? Math.ceil(frames / columns!) : 1 const rows = isGrid ? Math.ceil(frames / columns!) : 1
const width = imageWidth > 0 ? Math.floor(imageWidth / cols) : 32
const height = imageHeight > 0 ? Math.floor(imageHeight / rows) : 32
const sheetWidth = cols * width const sheetWidth = cols * width
const sheetHeight = rows * height const sheetHeight = rows * height
const totalDuration = frames * frameDuration const totalDuration = frames * frameDuration
@ -41,10 +52,10 @@ const App = () => {
} }
const previewStyle = { const previewStyle = {
width: `${width}px`, width: `${width * scale}px`,
height: `${height}px`, height: `${height * scale}px`,
backgroundImage: `url('${spriteUrl}')`, backgroundImage: `url('${spriteUrl}')`,
backgroundSize: `${sheetWidth}px ${sheetHeight}px`, backgroundSize: `${sheetWidth * scale}px ${sheetHeight * scale}px`,
animation: `${keyframeId} ${totalDuration}ms steps(${frames}) infinite`, animation: `${keyframeId} ${totalDuration}ms steps(${frames}) infinite`,
} }
@ -66,14 +77,12 @@ const App = () => {
Spritesheet Spritesheet
<input type="file" accept="image/*" onChange={handleFileChange} /> <input type="file" accept="image/*" onChange={handleFileChange} />
</label> </label>
<label> {spriteUrl && (
Frame Width (px) <div style={{ marginBottom: "1rem" }}>
<input type="number" value={width} min={1} onInput={(e) => setWidth(+(e.target as HTMLInputElement).value)} /> <small>Spritesheet ({imageWidth} x {imageHeight})</small>
</label> <img src={spriteUrl} style={{ maxWidth: "100%", imageRendering: "pixelated" }} />
<label> </div>
Frame Height (px) )}
<input type="number" value={height} min={1} onInput={(e) => setHeight(+(e.target as HTMLInputElement).value)} />
</label>
<label> <label>
Frame Count Frame Count
<input type="number" value={frames} min={1} onInput={(e) => setFrames(+(e.target as HTMLInputElement).value)} /> <input type="number" value={frames} min={1} onInput={(e) => setFrames(+(e.target as HTMLInputElement).value)} />
@ -89,6 +98,13 @@ const App = () => {
Frame Duration (ms) Frame Duration (ms)
<input type="number" value={frameDuration} min={1} onInput={(e) => setFrameDuration(+(e.target as HTMLInputElement).value)} /> <input type="number" value={frameDuration} min={1} onInput={(e) => setFrameDuration(+(e.target as HTMLInputElement).value)} />
</label> </label>
<label>
Scale ({scale}x)
<input type="range" value={scale} min={1} max={8} step={1} onInput={(e) => setScale(+(e.target as HTMLInputElement).value)} />
</label>
{spriteUrl && (
<small>Frame size: {width} x {height} px</small>
)}
</div> </div>
<div> <div>
<div class="preview-area"> <div class="preview-area">