extract and fix html exporter

This commit is contained in:
Reimar 2025-10-13 10:09:44 +02:00
parent cc71c91bd7
commit ff83f1e5f2
5 changed files with 114 additions and 69 deletions

View File

@ -44,11 +44,11 @@ export class Gamelib {
} }
}, 16); }, 16);
if (this.codeStopper.isStopped()) { if (this.codeStopper?.isStopped()) {
clearInterval(loopInterval); clearInterval(loopInterval);
} }
this.codeStopper.addListener(() => { this.codeStopper?.addListener(() => {
clearInterval(loopInterval); clearInterval(loopInterval);
}); });
} }

71
src/html_exporter.js Normal file
View File

@ -0,0 +1,71 @@
import { minifyJs } from "./utils.js";
export class HtmlExporter {
constructor(spriteProvider) {
this.spriteProvider = spriteProvider;
}
async export(projectName, code) {
const js = code
.split("\n")
.map((line) => " ".repeat(12) + line).join("\n");
let lib = await (await fetch("./src/gamelib.js")).text();
lib = minifyJs(lib);
const sprites = this.spriteProvider.getAll();
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="generator" content="karlkoder playground">
<title>${projectName}</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
</style>
<script type="importmap">
{
"imports": {
"gamelib": "data:text/javascript,${encodeURIComponent(lib)}"
}
}
</script>
<script type="module">
import { Gamelib } from "gamelib";
const sprites = ${JSON.stringify(sprites)}
class SpriteProvider {
url(name) {
return sprites[name];
}
}
const gamelib = new Gamelib(console, null, new SpriteProvider(), document.querySelector('canvas'));
window.karlkoder = {
lib() {
return gamelib;
},
};
</script>
<script type="module">
${js}
</script>
</head>
<body>
<canvas width="480" height="360"></canvas>
</body>
</html>`;
return html;
}
}

View File

@ -12,6 +12,8 @@ import { promptUpload } from "./prompt_upload.js";
import { GamelibCompleter } from "./gamelib_completer.js"; import { GamelibCompleter } from "./gamelib_completer.js";
import { TextCompleter } from "./text_completer.js"; import { TextCompleter } from "./text_completer.js";
import { ConsoleInput } from "./console_input.js"; import { ConsoleInput } from "./console_input.js";
import { downloadFile } from "./utils.js";
import { HtmlExporter } from "./html_exporter.js";
const playgroundConsole = new PlaygroundConsole( const playgroundConsole = new PlaygroundConsole(
document.querySelector("#console-code"), document.querySelector("#console-code"),
@ -26,6 +28,8 @@ const spriteEditor = new SpriteEditor(document.querySelector("#sprite-editor-con
new ConsoleInput(document.querySelector("#console-input"), playgroundConsole, codeRunner); new ConsoleInput(document.querySelector("#console-input"), playgroundConsole, codeRunner);
const htmlExporter = new HtmlExporter(spriteProvider);
const gamelib = new Gamelib( const gamelib = new Gamelib(
playgroundConsole, playgroundConsole,
codeStopper, codeStopper,
@ -122,32 +126,6 @@ runButton.onclick = () => {
} }
}; };
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 = () => { saveButton.onclick = () => {
if (saveButton.classList.contains("active")) { if (saveButton.classList.contains("active")) {
saveButton.classList.remove("active"); saveButton.classList.remove("active");
@ -164,48 +142,9 @@ saveJsButton.onclick = () => {
}; };
saveHtmlButton.onclick = async () => { saveHtmlButton.onclick = async () => {
const js = editor.getValue() const html = await htmlExporter.export(projectName.value, 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 = ` downloadFile(html, ".html", "text/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;
}
An iterable object such as an Array, having ArrayBuffers, TypedArrays, DataViews, Blobs, strings, or a mix of any of such elements, that will be put inside the Blob. Strings should be well-formed Unicode, and lone surrogates are sanitized using the same algorithm as String.prototype.toWellFormed().
</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>`;
downloadTextFile(html, ".html", "text/html");
}; };
function saveKarlKoder() { function saveKarlKoder() {

View File

@ -14,4 +14,14 @@ export class SpriteProvider {
} }
return `data:${sprite.mime};base64,${sprite.bytes.toBase64()}`; return `data:${sprite.mime};base64,${sprite.bytes.toBase64()}`;
} }
getAll() {
const result = {};
for (const sprite of this.sprites) {
result[sprite.name] = this.url(sprite.name);
}
return result;
}
} }

25
src/utils.js Normal file
View File

@ -0,0 +1,25 @@
export 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);
}
export function minifyJs(code) {
return code
.replace(/[\s\n]+/g, " ")
.replace(/;\s+/g, ";");
}