fix various stuff

This commit is contained in:
Simon From Jakobsen 2025-09-29 15:31:12 +02:00
parent 4087c6ade8
commit c21497bad1
7 changed files with 158 additions and 93 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

@ -7,13 +7,24 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Document</title> <title>Document</title>
<link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="./style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.2/ace.js" type="text/javascript" charset="utf-8"></script> <script type="importmap">
<script src="./index.js" defer></script> {
"imports": {
"lib": "./lib.js"
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.2/ace.js"></script>
<script src="./index.js" type="module"></script>
</head> </head>
<body> <body>
<main> <main>
<div class="column" style="flex: 1"> <div class="column" style="flex: 1">
<div id="buttons">
<button id="run-button">▶️ Run</button>
<button id="save-button">💾 Save/Export</button>
</div>
<section> <section>
<div class="section-header">Output</div> <div class="section-header">Output</div>
@ -33,16 +44,12 @@
</div> </div>
<div class="column" style="flex: 3"> <div class="column" style="flex: 3">
<div>
<button id="start-stop">Start</button>
<button id="save">Save</button>
</div>
<section> <section>
<div class="section-header">Code editor</div> <div class="section-header">Code editor</div>
<div id="editor-area"> <div id="editor-area">
<pre id="editor"> <pre id="editor">import * as lib from "lib";
lib.clear("green"); lib.clear("green");
@ -61,7 +68,6 @@ function loop(deltaT) {
lib.startGameLoop(loop); lib.startGameLoop(loop);
return 5;
</pre> </pre>
</div> </div>
</section> </section>

150
index.js
View File

@ -1,91 +1,75 @@
"use strict"; /// <reference types="ace" />
let _gameLoopTimeout = undefined;
(function () {
const editor = ace.edit("editor");
editor.setTheme("ace/theme/gruvbox");
editor.session.setMode("ace/mode/javascript");
if (editor.getValue() === "") {
editor.setValue(
``,
);
}
let running = false;
const startStopButton = document.querySelector("#start-stop");
const saveButton = document.querySelector("#save");
const consoleCode = document.querySelector("#console-code");
startStopButton.onclick = (ev) => {
if (running) {
if (_gameLoopTimeout) {
clearTimeout(_gameLoopTimeout);
_gameLoopTimeout = undefined;
}
startStopButton.textContent = "Start";
running = false;
} else {
const code = editor.getValue();
runCode(code, consoleCode);
startStopButton.textContent = "Stop";
running = true;
}
};
})();
const lib = (() => {
const consoleCode = document.querySelector("#console-code");
const width = 480;
const height = 360;
const canvas = document.querySelector("canvas");
canvas.width = width;
canvas.height = height;
const cx = canvas.getContext("2d");
cx.imageSmoothingEnabled = false;
function rgb(red, green, blue) {
return `rgb(${red}, ${green}, ${blue})`;
}
function clear(color) {
cx.fillStyle = color;
cx.fillRect(0, 0, width, height);
}
function drawRect(x, y, width, height, color) {
cx.fillStyle = color;
cx.fillRect(x, y, width, height);
}
function println(msg) {
consoleCode.textContent += `${msg}\n`;
}
function startGameLoop(loopFunction) {
let before = Date.now();
_gameLoopTimeout = setInterval(() => {
const now = Date.now();
const deltaT = (now - before) / 1000;
before = now;
loopFunction(deltaT);
}, 16);
}
return { width, height, rgb, clear, drawRect, println, startGameLoop };
})();
async function runCode(code, consoleCode) { async function runCode(code, consoleCode) {
lib;
consoleCode.textContent += `\nRunning code....\n`; consoleCode.textContent += `\nRunning code....\n`;
try { try {
const result = await eval(`(async function () {${code}})()`); const module = await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`);
consoleCode.textContent += `Code returned ${result}\n`; module.default?.();
} catch (error) { } catch (error) {
consoleCode.textContent += `${error}\n`; 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 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...";
};
saveButton.onclick = (ev) => {
const code = editor.getValue();
const element = document.createElement("a");
element.setAttribute("href", `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`);
const filename = prompt("Filename?")
element.setAttribute("download", filename.endsWith(".js") ? filename : `${filename}.js`);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};

38
lib.js Normal file
View File

@ -0,0 +1,38 @@
const consoleCode = document.querySelector("#console-code");
export const width = 480;
export const height = 360;
const canvas = document.querySelector("canvas");
canvas.width = width;
canvas.height = height;
const cx = canvas.getContext("2d");
cx.imageSmoothingEnabled = false;
export function rgb(red, green, blue) {
return `rgb(${red}, ${green}, ${blue})`;
}
export function clear(color) {
cx.fillStyle = color;
cx.fillRect(0, 0, width, height);
}
export function drawRect(x, y, width, height, color) {
cx.fillStyle = color;
cx.fillRect(x, y, width, height);
}
export function println(msg) {
consoleCode.textContent += `${msg}\n`;
}
export function startGameLoop(loopFunction) {
let before = Date.now();
setInterval(() => {
const now = Date.now();
const deltaT = (now - before) / 1000;
before = now;
loopFunction(deltaT);
}, 16);
}

19
package-lock.json generated Normal file
View File

@ -0,0 +1,19 @@
{
"name": "karlkoder-playground",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"@types/ace": "^0.0.52"
}
},
"node_modules/@types/ace": {
"version": "0.0.52",
"resolved": "https://registry.npmjs.org/@types/ace/-/ace-0.0.52.tgz",
"integrity": "sha512-YPF9S7fzpuyrxru+sG/rrTpZkC6gpHBPF14W3x70kqVOD+ks6jkYLapk4yceh36xej7K4HYxcyz9ZDQ2lTvwgQ==",
"dev": true,
"license": "MIT"
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/ace": "^0.0.52"
}
}

View File

@ -1,11 +1,12 @@
* { * {
box-sizing: border-box; box-sizing: border-box;
color-scheme: light dark;
} }
body { body {
margin: 0; margin: 0;
height: 100vh; height: 100vh;
background-color: #EEE; background-color: light-dark(#EEE, #333);
} }
main { main {
@ -19,7 +20,7 @@ main {
background-color: white; background-color: white;
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-top-right-radius: 8px; border-top-right-radius: 8px;
border: 1px solid #BDBDBD; border: 1px solid light-dark(#BDBDBD, #222);
padding: 4px; padding: 4px;
padding-left: 8px; padding-left: 8px;
font-family: sans-serif; font-family: sans-serif;
@ -41,6 +42,17 @@ section *:last-child {
flex: 1; flex: 1;
} }
div#buttons {
width: 100%;
display: flex;
flex: row;
justify-content: space-between;
gap: 5px;
}
div#buttons > * {
width: 100%;
}
.column { .column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;