2025-10-10 11:20:19 +02:00

149 lines
3.6 KiB
JavaScript

/// <reference types="ace" />
async function runCode(code, consoleCode) {
consoleCode.textContent += `\nRunning code....\n`;
try {
const module = await import(
`data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`
);
module.default?.();
} catch (error) {
consoleCode.textContent += `${error}\n`;
}
}
class Debounce {
timer = null;
lastCall = 0;
constructor(timeout) {
this.timeout = timeout;
}
run(fn) {
const now = Date.now();
if (this.timer === null && now - this.lastCall > this.timeout) {
fn();
this.lastCall = now;
return;
}
if (this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(() => {
fn();
this.lastCall = Date.now();
this.timer = null;
}, this.timeout);
}
}
const editor = ace.edit("editor");
editor.setTheme("ace/theme/gruvbox");
editor.session.setMode("ace/mode/javascript");
editor.setValue(sessionStorage.getItem("code") ?? editor.getValue(), -1);
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 consoleCode = document.querySelector("#console-code");
const sessionSaveDebounce = new Debounce(1000);
editor.addEventListener("change", (ev) => {
sessionSaveDebounce.run(() => {
sessionStorage.setItem("code", editor.getValue());
});
});
runButton.onclick = (ev) => {
const code = editor.getValue();
runCode(code, consoleCode);
runButton.textContent = "⚙️ Running...";
};
function downloadFile(content, mime, extension) {
const filename = prompt("Filename?");
const element = document.createElement("a");
element.href = `data:${mime};charset=utf-8,${encodeURIComponent(content)}`;
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 = (ev) => {
if (saveButton.classList.contains("active")) {
saveButton.classList.remove("active");
saveDropdown.style.display = "none";
} else {
saveButton.classList.add("active");
saveDropdown.style.display = "block";
}
};
saveJsButton.onclick = (ev) => {
downloadFile(editor.getValue(), "text/javascript", ".js");
};
saveHtmlButton.onclick = async (ev) => {
const js = editor.getValue().split("\n").map((line) =>
" ".repeat(12) + line
).join("\n");
let lib = await (await fetch("./lib.js")).text();
lib = minifyJs(lib);
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="generator" content="karlkoder playground">
<title>Game</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
</style>
<script type="importmap">
{
"imports": {
"lib": "data:text/javascript,${encodeURIComponent(lib)}"
}
}
</script>
<script type="module">
${js}
</script>
</head>
<body>
<canvas width="480" height="360"></canvas>
</body>
</html>`;
downloadFile(html, "text/html", ".html");
};