From a1aafc3fb249084bdc1c0356ff4506bc3db7e6b7 Mon Sep 17 00:00:00 2001 From: Theis Pieter Hollebeek Date: Fri, 10 Oct 2025 16:16:50 +0200 Subject: [PATCH] sprite editor --- index.html | 17 +++++---- js/index.js | 2 ++ js/sprite-editor.js | 72 ++++++++++++++++++++++++++++++++++++++ style.css | 85 +++++++++++++++++++++++++++------------------ 4 files changed, 135 insertions(+), 41 deletions(-) create mode 100644 js/sprite-editor.js diff --git a/index.html b/index.html index 2a5ca1e..5bdaf14 100644 --- a/index.html +++ b/index.html @@ -107,27 +107,30 @@ function loop(deltaT) { lib.startGameLoop(loop); - +
-
+
Sprite editor
-
+
+

+ cattttttttttttttttttttttttttttttttttttttttttttttttttttttt.png +

-
    -
  • + + +
    • hrrmimimi

    • hrrmimimi

    • hrrmimimi

    • diff --git a/js/index.js b/js/index.js index 8527265..d65c298 100644 --- a/js/index.js +++ b/js/index.js @@ -3,11 +3,13 @@ import { Debounce } from "./debounce.js"; import { PlaygroundConsole } from "./playground-console.js"; import { CodeRunner } from "./code-runner.js"; +import { SpriteEditor } from "./sprite-editor.js"; const playgroundConsole = new PlaygroundConsole( document.querySelector("#console-code"), ); const codeRunner = new CodeRunner(playgroundConsole); +new SpriteEditor(document.querySelector("#sprite-editor"), []); const editor = ace.edit("editor"); editor.setTheme("ace/theme/gruvbox"); diff --git a/js/sprite-editor.js b/js/sprite-editor.js new file mode 100644 index 0000000..16c4500 --- /dev/null +++ b/js/sprite-editor.js @@ -0,0 +1,72 @@ +export class SpriteEditor { + constructor(rootEl, sprites) { + this.list = rootEl.querySelector("#sprite-editor-sprite-list"); + this.preview = {}; + this.preview.title = rootEl.querySelector("#sprite-editor-preview-title"); + this.preview.image = rootEl.querySelector("#sprite-editor-preview-image"); + this.sprites = sprites; + + rootEl.querySelector("#sprite-editor-upload-button").addEventListener("click", () => { + this.promptUpload(); + }); + + this.renderList(); + } + + promptUpload() { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.multiple = true; + input.style = "display: none;"; + input.addEventListener("cancel", () => { + input.remove(); + }); + input.addEventListener("change", async () => { + for (const file of input.files) { + this.addSprite({ + name: file.name, + mime: file.type, + bytes: await fetch(URL.createObjectURL(file)).then((x) => x.bytes()), + }); + } + input.remove(); + }); + document.body.append(input); + input.click(); + } + + addSprite({ name, bytes, mime }) { + const id = Math.round(Math.random() * 1e6); + this.sprites.push({ id, bytes, name, mime }); + this.renderList(); + } + + deleteSprite(id) { + this.sprites = this.sprites.filter((x) => x.id !== id); + this.renderList(); + } + + setPreview(id) { + const sprite = this.sprites.find((x) => x.id === id); + this.preview.title.textContent = sprite.name; + this.preview.image.src = `data:${sprite.mime};base64,${sprite.bytes.toBase64()}`; + } + + renderList() { + const children = this.sprites + .map((sprite) => { + const listItem = document.createElement("li"); + listItem.classList.add("sprite-editor-list-item"); + const name = document.createElement("span"); + name.textContent = sprite.name; + name.addEventListener("click", () => { + this.setPreview(sprite.id); + }); + listItem.append(name); + return listItem; + }); + + this.list.replaceChildren(...children); + } +} diff --git a/style.css b/style.css index 11f4e49..cd05a9c 100644 --- a/style.css +++ b/style.css @@ -2,6 +2,10 @@ color-scheme: dark; } +body { + font-family: system-ui; +} + * { box-sizing: border-box; } @@ -16,24 +20,23 @@ main { display: flex; flex-direction: row; height: 100vh; - padding: 32px; - gap: 32px; + padding: 2rem; + gap: 2rem; } .section-header { background-color: #bdbdbd; - border-top-left-radius: 8px; - border-top-right-radius: 8px; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; border: 1px solid #222; - padding: 4px; - padding-left: 8px; - font-family: sans-serif; + padding: 0.25rem; + padding-left: 0.5rem; color: #424242; box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1); } section { - border-radius: 8px; + border-radius: 0.5rem; width: 100%; flex: 1; display: flex; @@ -47,8 +50,8 @@ section canvas { } section *:last-child { - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1); flex: 1; } @@ -58,7 +61,7 @@ div#buttons { display: flex; flex-wrap: wrap; flex: row; - gap: 5px; + gap: 0.3rem; } div#buttons > * { @@ -73,10 +76,10 @@ div#buttons button { .column { display: flex; flex-direction: column; - gap: 5px; + gap: 0.3rem; justify-content: space-evenly; align-items: flex-start; - gap: 32px; + gap: 2rem; flex: 1; min-width: 0; } @@ -123,7 +126,7 @@ div#buttons button { color: white; border: none; background-color: #222; - padding: 4px 8px; + padding: 0.25rem 0.5rem; font-family: monospace; font-size: 1rem; flex-grow: 0; @@ -138,7 +141,7 @@ div#buttons button { margin: 0; width: 100%; height: 100%; - font-size: 16px; + font-size: 1rem; } #editor * { @@ -157,13 +160,12 @@ div#buttons button { top: 100%; left: 0; right: 0; - border-radius: 4px; + border-radius: 0.25rem; box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); } .dropdown-option { - padding: 5px 10px; - font-family: sans-serif; + padding: 0.3rem 0.6rem; color: #424242; font-size: 0.8rem; cursor: pointer; @@ -177,8 +179,8 @@ button { background-color: #087aaf; border: none; box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); - padding: 8px 16px; - border-radius: 8px; + padding: 0.5rem 1rem; + border-radius: 0.5rem; cursor: pointer; transition-duration: 200ms; } @@ -203,33 +205,48 @@ footer { } #sprite-editor-preview { - background-color: #1d2021; - max-height: 450px; - object-fit: contain; - width: 100%; - border-radius: 0px; - padding-block: 32px; + display: flex; + flex-direction: column; } -#sprite-editor-list { - list-style: none; - padding: 1rem; +#sprite-editor-preview-title { + margin: 1rem; + overflow-wrap: break-word; + font-weight: bold; +} +#sprite-editor-preview-image { + background-color: #1d2021; + object-fit: contain; + width: 100%; + border-radius: 0; + max-height: min(40vh, 400px); +} + +#sprite-editor-sprite-list { margin: 0; } -#sprite-editor-list .delete-button { +#sprite-editor-sprite-list li span { + cursor: pointer; +} + +#sprite-editor-sprite-list li span:hover { + text-decoration: 1px solid underline; +} + +#sprite-editor-sprite-list .delete-button { background-color: #c83e4d; padding: 0.25rem 0.75rem; } -#sprite-editor-list .delete-button:hover { +#sprite-editor-sprite-list .delete-button:hover { background-color: #9f2d3a; } -#sprite-editor-list .delete-button:active { +#sprite-editor-sprite-list .delete-button:active { background-color: #7f242f; } -#sprite-editor-list p:first-child { - margin-top: 0; +#sprite-editor-upload-button { + margin: 1rem; }