/// import { Debounce } from "./debounce.js"; import { PlaygroundConsole } from "./playground_console.js"; import { CodeRunner } from "./code_runner.js"; 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"; import { GamelibCompleter } from "./gamelib_completer.js"; import { TextCompleter } from "./text_completer.js"; import { ConsoleInput } from "./console_input.js"; const playgroundConsole = new PlaygroundConsole( document.querySelector("#console-code"), ); const codeStopper = new CodeStopper(); const spriteProvider = new SpriteProvider(); const codeRunner = new CodeRunner(playgroundConsole, codeStopper); const spriteEditor = new SpriteEditor(document.querySelector("#sprite-editor-container"), []); new ConsoleInput(document.querySelector("#console-input"), playgroundConsole, codeRunner); const gamelib = new Gamelib( playgroundConsole, codeStopper, spriteProvider, document.querySelector("canvas"), ); globalThis.karlkoder = { lib() { return gamelib; }, }; addEventListener("keydown", (ev) => { if (ev.ctrlKey && ev.key === "s") { ev.preventDefault(); saveKarlKoder(); } }); const editor = ace.edit("editor"); editor.setTheme("ace/theme/gruvbox"); editor.session.setMode("ace/mode/javascript"); const langTools = ace.require("ace/ext/language_tools"); editor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true, copyWithEmptySelection: true, }); langTools.setCompleters([new GamelibCompleter(), new TextCompleter()]); 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 projectName = document.querySelector("#project-name"); const sessionSaveDebounce = new Debounce(1000); editor.addEventListener("change", () => { sessionSaveDebounce.run(() => { sessionStorage.setItem("code", editor.getValue()); }); }); importButton.onclick = async () => { const files = await promptUpload(".karlkode", false); if (files.length === 0) { return; } if (files.length > 1) { throw new Error( `unreachable: something went wrong ! files.length > 1 : files.length = ${files.length}`, ); } const items = Vermiparous.de(await fetch(URL.createObjectURL(files[0])).then((x) => x.bytes())); const sprites = items .filter((x) => x.tag === "asset") .map((x) => { delete x.tag; return x; }); const code = items.find((x) => x.tag === "code"); delete code.tag; spriteEditor.importSprites( sprites.map(({ name, mime, content }) => ({ name, mime, bytes: content })), ); const dec = new TextDecoder(); editor.setValue(dec.decode(code.content)); }; runButton.onclick = () => { const code = editor.getValue(); karlkoder.lib().spriteProvider.injectSprites(spriteEditor.sprites); codeRunner.setCode(code); codeRunner.toggle(); document.querySelector("canvas").focus(); if (codeRunner.isRunning) { runButton.textContent = "✋ Stop"; runButton.classList.add("active"); } else { runButton.textContent = "🏃 Run"; runButton.classList.remove("active"); } }; function downloadFile(content, extension, mime) { const filename = prompt("Filename?"); if (filename === null) return; const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const element = document.createElement("a"); element.href = url; element.download = filename.endsWith(extension) ? filename : filename + extension; element.style.display = "none"; document.body.appendChild(element); element.click(); document.body.removeChild(element); } function minifyJs(code) { return code .replace(/[\s\n]+/g, " ") .replace(/;\s+/g, ";"); } saveButton.onclick = () => { if (saveButton.classList.contains("active")) { saveButton.classList.remove("active"); saveDropdown.style.display = "none"; } else { saveButton.classList.add("active"); saveDropdown.style.display = "block"; } }; saveJsButton.onclick = () => { downloadFile(editor.getValue(), ".js", "text/javascript"); }; saveHtmlButton.onclick = async () => { const js = editor.getValue() .split("\n") .map((line) => " ".repeat(12) + line).join("\n"); let lib = await (await fetch("./js/lib.js")).text(); lib = minifyJs(lib); const html = ` Game `; downloadTextFile(html, ".html", "text/html"); }; function saveKarlKoder() { downloadFile( Vermiparous.en( editor.getValue(), spriteEditor.sprites, ), ".karlkode", ); } saveKarlkoderButton.onclick = () => { saveKarlKoder(); }; toggleSpriteEditorButton.addEventListener("click", () => spriteEditor.toggleEditor());