94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
import { Marked } from "marked";
|
|
import { AssetManager } from "$/client/assets.ts";
|
|
import { mod_credits } from "$/client/mods.ts";
|
|
import { canvas, draw_rect } from "$/client/renderer/mod.ts";
|
|
import { GuiScreen } from "./gui_screen.ts";
|
|
|
|
// credits come from servers' mods, so markdown only: raw html is shown as text and links can't run code
|
|
const markdown = new Marked({
|
|
renderer: {
|
|
html({ text }) {
|
|
return escape_html(text);
|
|
},
|
|
link({ href, title, tokens }) {
|
|
const text = this.parser.parseInline(tokens);
|
|
if (!/^(https?:|mailto:)/i.test(href)) {
|
|
return text;
|
|
}
|
|
const title_attribute = title ? ` title="${escape_html(title)}"` : "";
|
|
return `<a href="${
|
|
escape_html(href)
|
|
}"${title_attribute} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
},
|
|
},
|
|
});
|
|
|
|
// who made what: the engine's assets/ASSETS.md, then each loaded mod's credits file. shown as a page over the
|
|
// game since licenses are long, the game underneath keeps drawing
|
|
export class CreditsScreen extends GuiScreen {
|
|
#overlay: HTMLElement;
|
|
|
|
constructor(on_back: () => void) {
|
|
super();
|
|
|
|
this.#overlay = document.createElement("div");
|
|
this.#overlay.style.cssText = "position:fixed;inset:0;display:flex;justify-content:center;padding:32px 16px;" +
|
|
"box-sizing:border-box;color:#e5e7eb;font:15px/1.6 system-ui,sans-serif;";
|
|
|
|
const panel = document.createElement("div");
|
|
panel.style.cssText = "width:100%;max-width:760px;display:flex;flex-direction:column;gap:12px;" +
|
|
"background:rgba(17,24,39,0.95);border-radius:8px;padding:20px 24px;box-sizing:border-box;";
|
|
|
|
const header = document.createElement("div");
|
|
header.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:12px;";
|
|
const title = document.createElement("div");
|
|
title.textContent = "Credits";
|
|
title.style.cssText = "font-size:24px;font-weight:600;";
|
|
const back = document.createElement("button");
|
|
back.textContent = "Back";
|
|
back.style.cssText = "font:inherit;padding:6px 20px;cursor:pointer;";
|
|
back.addEventListener("click", on_back);
|
|
header.append(title, back);
|
|
|
|
const content = document.createElement("div");
|
|
content.style.cssText = "overflow-y:auto;flex:1;min-height:0;padding-right:8px;";
|
|
content.innerHTML = this.#sections().map(({ heading, text }) =>
|
|
`<section><h2 style="border-bottom:1px solid #374151;padding-bottom:4px">${escape_html(heading)}</h2>` +
|
|
`${markdown.parse(text, { async: false })}</section>`
|
|
).join("");
|
|
for (const pre of content.querySelectorAll("pre")) {
|
|
pre.style.cssText =
|
|
"white-space:pre-wrap;background:#0b1220;padding:12px;border-radius:6px;font-size:12px;";
|
|
}
|
|
for (const link of content.querySelectorAll("a")) {
|
|
link.style.color = "#60a5fa";
|
|
}
|
|
|
|
panel.append(header, content);
|
|
this.#overlay.append(panel);
|
|
document.body.append(this.#overlay);
|
|
}
|
|
|
|
#sections() {
|
|
const sections = [{ heading: "bworld", text: AssetManager.instance.get<string>("bworld:assets_text") ?? "" }];
|
|
for (const mod of mod_credits) {
|
|
sections.push({ heading: `${mod.name} ${mod.version}`, text: mod.text });
|
|
}
|
|
return sections;
|
|
}
|
|
|
|
on_tick(_delta: number): void {}
|
|
|
|
on_render(): void {
|
|
draw_rect(0, 0, canvas.width, canvas.height, [0, 0, 0, 0.4]);
|
|
}
|
|
|
|
on_close(): void {
|
|
this.#overlay.remove();
|
|
}
|
|
}
|
|
|
|
function escape_html(text: string) {
|
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
}
|