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:
parent
d3e1eb00bd
commit
b1a87662fb
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user