vermiparous

This commit is contained in:
Theis Pieter Hollebeek 2025-10-10 21:28:06 +02:00
parent a4f2da333f
commit 2514cd3669
5 changed files with 85 additions and 54 deletions

View File

@ -14,6 +14,8 @@
<main>
<div class="column" style="flex: 1">
<div id="buttons">
<button id="import-button">🙏 Import</button>
<a
href="docs/index.html"
target="_blank"
@ -42,6 +44,12 @@
>
Export as HTML
</div>
<div
id="save-karlkoder"
class="dropdown-option"
>
Export as Karlkoder File
</div>
</div>
</div>

View File

@ -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") {

19
src/prompt_upload.js Normal file
View File

@ -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();
});
}

View File

@ -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 }) {

View File

@ -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),