From 2514cd3669ef44cbdc94547b7e4b1b561ff6cebf Mon Sep 17 00:00:00 2001 From: Theis Pieter Hollebeek Date: Fri, 10 Oct 2025 21:28:06 +0200 Subject: [PATCH] vermiparous --- index.html | 8 ++++ src/index.js | 28 ++++++++++++++ src/prompt_upload.js | 19 ++++++++++ src/sprite_editor.js | 51 ++++++++++---------------- src/{vermiparous.ts => vermiparous.js} | 33 +++++------------ 5 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 src/prompt_upload.js rename src/{vermiparous.ts => vermiparous.js} (79%) diff --git a/index.html b/index.html index 7c0eda0..f1a70f3 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,8 @@
diff --git a/src/index.js b/src/index.js index 2108071..6615e77 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,8 @@ import { SpriteEditor } from "./sprite_editor.js"; import { SpriteProvider } from "./sprite_provider.js"; import { Gamelib } from "./gamelib.js"; import { CodeStopper } from "./code_stopper.js"; +import { Vermiparous } from "./vermiparous.js"; +import { promptUpload } from "./prompt_upload.js"; const playgroundConsole = new PlaygroundConsole( document.querySelector("#console-code"), @@ -38,11 +40,13 @@ editor.session.setMode("ace/mode/javascript"); editor.setValue(sessionStorage.getItem("code") ?? editor.getValue(), -1); +const importButton = document.querySelector("#import-button"); const runButton = document.querySelector("#run-button"); const saveButton = document.querySelector("#save-button"); const saveDropdown = document.querySelector("#save-dropdown"); const saveJsButton = document.querySelector("#save-js"); const saveHtmlButton = document.querySelector("#save-html"); +const saveKarlkoderButton = document.querySelector("#save-karlkoder"); const toggleSpriteEditorButton = document.querySelector("#toggle-sprite-editor-button"); const sessionSaveDebounce = new Debounce(1000); @@ -52,6 +56,17 @@ editor.addEventListener("change", (ev) => { }); }); +importButton.onclick = async (ev) => { + const files = await promptUpload("", false); + if (files.length === 0) { + return; + } + if (files.length > 1) { + throw new Error("unreachable: something went wrong !"); + } + const item = Vermiparous.de(await fetch(URL.createObjectURL(files[0])).then((x) => x.bytes())); +}; + runButton.onclick = (ev) => { const code = editor.getValue(); @@ -148,6 +163,19 @@ ${js} downloadFile(html, "text/html", ".html"); }; +saveKarlkoderButton.onclick = (ev) => { + downloadFile( + Vermiparous.en( + editor.getValue() + .split("\n") + .map((line) => " ".repeat(12) + line).join("\n"), + spriteEditor.sprites, + ), + "", + ".karlkode", + ); +}; + toggleSpriteEditorButton.addEventListener("click", () => { const cont = document.querySelector("#sprite-editor-container"); if (cont.style.getPropertyValue("display") === "none") { diff --git a/src/prompt_upload.js b/src/prompt_upload.js new file mode 100644 index 0000000..0c53a34 --- /dev/null +++ b/src/prompt_upload.js @@ -0,0 +1,19 @@ +export function promptUpload(accept, multiple) { + return new Promise((resolve) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = accept; + input.multiple = multiple; + input.style = "display: none;"; + input.addEventListener("cancel", () => { + resolve([]); + input.remove(); + }); + input.addEventListener("change", () => { + resolve(input.files); + input.remove(); + }); + document.body.append(input); + input.click(); + }); +} diff --git a/src/sprite_editor.js b/src/sprite_editor.js index cc19918..a37b204 100644 --- a/src/sprite_editor.js +++ b/src/sprite_editor.js @@ -1,3 +1,5 @@ +import { promptUpload } from "./prompt_upload.js"; + export class SpriteEditor { constructor(rootEl, sprites) { this.list = rootEl.querySelector("#sprite-editor-sprite-list"); @@ -13,39 +15,26 @@ export class SpriteEditor { 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) { - const rootName = file.name; - let fullName = file.name; - for (let i = 0; this.sprites.some((x) => x.name === fullName); ++i) { - const extensionIdx = rootName.split("").findLastIndex((x) => x === "."); - let name = rootName; - let extension = ""; - if (extensionIdx !== -1) { - name = rootName.slice(0, extensionIdx); - extension = rootName.slice(extensionIdx); - } - fullName = `${name}-${i}${extension}`; + async promptUpload() { + for (const file of await promptUpload("image/*", true)) { + const rootName = file.name; + let fullName = file.name; + for (let i = 0; this.sprites.some((x) => x.name === fullName); ++i) { + const extensionIdx = rootName.split("").findLastIndex((x) => x === "."); + let name = rootName; + let extension = ""; + if (extensionIdx !== -1) { + name = rootName.slice(0, extensionIdx); + extension = rootName.slice(extensionIdx); } - this.addSprite({ - name: fullName, - mime: file.type, - bytes: await fetch(URL.createObjectURL(file)).then((x) => x.bytes()), - }); + fullName = `${name}-${i}${extension}`; } - input.remove(); - }); - document.body.append(input); - input.click(); + this.addSprite({ + name: fullName, + mime: file.type, + bytes: await fetch(URL.createObjectURL(file)).then((x) => x.bytes()), + }); + } } addSprite({ name, bytes, mime }) { diff --git a/src/vermiparous.ts b/src/vermiparous.js similarity index 79% rename from src/vermiparous.ts rename to src/vermiparous.js index b985a3d..ee2f0e5 100644 --- a/src/vermiparous.ts +++ b/src/vermiparous.js @@ -2,43 +2,30 @@ const KEYWORDS = ["asset", "code"]; const MIN_KW_LENGTH = Math.min(...KEYWORDS.map((x) => x.length)); const SEMICOLON_CHARCODE = ";".charCodeAt(0); -function isKeyword(buffer: number[]) { - const kw = String.fromCharCode(...buffer); +function isKeyword(buffer) { + const kw = buffer.map((x) => String.fromCharCode(x)).join(""); return KEYWORDS.includes(kw); } -function strToBytes(str: string): number[] { +function strToBytes(str) { return str.split("").map((x) => x.charCodeAt(0)); } -type EncAsset = { - name: string; - mime: string; - content: Uint8Array; -}; - -type DecAssic = { - tag: "code" | "asset"; - name: string; - mime: string; - content: Uint8Array; -}; - export class Vermiparous { - private constructor() {} - static en(code: string, assets: EncAsset[]): Uint8Array { - const ret: number[] = []; + static en(code, assets) { + const ret = []; for (const asset of assets) { + console.log(asset); ret.push(...strToBytes("asset")); ret.push(...strToBytes(asset.name.length.toString())); ret.push(...strToBytes(";")); ret.push(...strToBytes(asset.mime.length.toString())); ret.push(...strToBytes(";")); - ret.push(...strToBytes(asset.content.length.toString())); + ret.push(...strToBytes(asset.bytes.length.toString())); ret.push(...strToBytes(";")); ret.push(...strToBytes(asset.name)); ret.push(...strToBytes(asset.mime)); - ret.push(...asset.content); + ret.push(...asset.bytes); } ret.push(...strToBytes("code")); ret.push(...strToBytes("0;0;")); @@ -48,7 +35,7 @@ export class Vermiparous { return new Uint8Array(ret); } - static de(bytes: Uint8Array): DecAssic[] { + static de(bytes) { const ret = []; let buffer = []; let idx = 0; @@ -92,7 +79,7 @@ export class Vermiparous { } const [name, mime, content] = consoom; ret.push({ - tag: String.fromCharCode(...tag) as DecAssic["tag"], + tag: String.fromCharCode(...tag), name: String.fromCharCode(...name), mime: String.fromCharCode(...mime), content: new Uint8Array(content),