99 lines
4.9 KiB
JavaScript
99 lines
4.9 KiB
JavaScript
// Copies the desktop renderer into public/legacy/ for the web host, without editing the scripts:
|
|
// js/<file>.js renderer scripts, byte for byte except the fixes in tools/legacy-patches.mjs
|
|
// (app.js and monitor.js are left out)
|
|
// views/<view>.html each <section id="view-<view>" class="view"> of index.html
|
|
// chrome.html shared overlays: #menu, #modal, #toast, #task
|
|
// legacy.css app.css scoped under .lsx (CSS nesting), @keyframes hoisted out,
|
|
// :root tokens moved onto .lsx so the web theme can override them
|
|
// usage: node tools/extract-legacy.mjs
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { applyPatches, PATCHES } from "./legacy-patches.mjs";
|
|
|
|
const REF = path.resolve("../reference/lstools-desktop/src/renderer");
|
|
const OUT = path.resolve("public/legacy");
|
|
const SKIP = new Set(["app.js", "monitor.js"]);
|
|
const DROP_VIEWS = new Set(["iop", "pm", "mon", "pw"]); // IOP Manager UI comes back later as its own page
|
|
|
|
fs.rmSync(OUT, { recursive: true, force: true });
|
|
fs.mkdirSync(path.join(OUT, "js"), { recursive: true });
|
|
fs.mkdirSync(path.join(OUT, "views"), { recursive: true });
|
|
|
|
// ---------- scripts ----------
|
|
const scripts = fs.readdirSync(REF).filter((f) => f.endsWith(".js") && !SKIP.has(f));
|
|
for (const f of scripts) {
|
|
if (!PATCHES.some((p) => p.file === f)) {
|
|
fs.copyFileSync(path.join(REF, f), path.join(OUT, "js", f));
|
|
continue;
|
|
}
|
|
fs.writeFileSync(path.join(OUT, "js", f), applyPatches(f, fs.readFileSync(path.join(REF, f), "utf8")));
|
|
console.log(`patched ${f}: ${PATCHES.filter((p) => p.file === f).length}`);
|
|
}
|
|
|
|
// ---------- markup ----------
|
|
const html = fs.readFileSync(path.join(REF, "index.html"), "utf8");
|
|
|
|
/** The element starting at `start` (an opening tag) up to its matching close tag. */
|
|
function element(src, start) {
|
|
const tag = src.slice(start + 1).match(/^[\w-]+/)[0];
|
|
const re = new RegExp(`<(/?)${tag}\\b[^>]*>`, "g");
|
|
re.lastIndex = start;
|
|
let depth = 0;
|
|
for (let m; (m = re.exec(src)); ) {
|
|
if (m[0].endsWith("/>")) continue;
|
|
depth += m[1] ? -1 : 1;
|
|
if (depth === 0) return src.slice(start, re.lastIndex);
|
|
}
|
|
throw new Error(`unclosed <${tag}> at ${start}`);
|
|
}
|
|
|
|
const views = [];
|
|
for (const m of html.matchAll(/<section id="view-([\w-]+)" class="view[^"]*">/g)) {
|
|
const name = m[1];
|
|
if (DROP_VIEWS.has(name)) continue;
|
|
fs.writeFileSync(path.join(OUT, "views", `${name}.html`), element(html, m.index));
|
|
views.push(name);
|
|
}
|
|
|
|
const chrome = [];
|
|
for (const id of ["menu", "modal", "toast", "task"]) {
|
|
const m = html.match(new RegExp(`<[\\w-]+[^>]*\\bid="${id}"[^>]*>`));
|
|
if (m) chrome.push(element(html, m.index));
|
|
else console.warn(`no #${id} in index.html`);
|
|
}
|
|
fs.writeFileSync(path.join(OUT, "chrome.html"), chrome.join("\n"));
|
|
|
|
// ---------- css ----------
|
|
let css = fs.readFileSync(path.join(REF, "app.css"), "utf8");
|
|
const keyframes = [];
|
|
css = css.replace(/@keyframes\s+[\w-]+\s*\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}/g, (k) => (keyframes.push(k), ""));
|
|
css = css.replace(/:root\s*\{/, "& {");
|
|
fs.writeFileSync(path.join(OUT, "legacy.css"), `/* Generated by tools/extract-legacy.mjs from the desktop app.css. Do not edit. */\n${keyframes.join("\n")}\n.lsx {\n${css}\n}\n`);
|
|
|
|
// ---------- host.js: the shared helpers of app.js, copied line for line ----------
|
|
// Line ranges of the reference app.js (1-based, inclusive). Only helpers the tool scripts use;
|
|
// IOP Manager / Patch Manager / Monitor code stays out.
|
|
const APP_RANGES = [
|
|
[6, 103], // $, bridge, call, el, size, hex, fmtDos, MAX_ENTRIES, toast, modal, confirmBox, prompt, runTask
|
|
[162, 162], // baseName
|
|
[494, 558], // ICONS, icon()
|
|
[564, 593], // showMenu, hideMenu, cmd
|
|
[818, 823], // replay
|
|
[1051, 1063], // unsavedWork (profileSource / profileSourceOption: web versions in host-web-after.js)
|
|
];
|
|
const appLines = fs.readFileSync(path.join(REF, "app.js"), "utf8").split(/\r?\n/);
|
|
const copied = APP_RANGES.map(([a, b]) => `// ---- app.js ${a}-${b} ----\n` + appLines.slice(a - 1, b).join("\n")).join("\n\n");
|
|
const webPart = fs.readFileSync(path.resolve("src/legacy/host-web.js"), "utf8");
|
|
const webAfter = fs.readFileSync(path.resolve("src/legacy/host-web-after.js"), "utf8");
|
|
fs.writeFileSync(
|
|
path.join(OUT, "host.js"),
|
|
`"use strict";\n// Generated by tools/extract-legacy.mjs: helpers copied from the desktop app.js + web additions.\n\n${webPart}\n\n${copied}\n\n${webAfter}\n`,
|
|
);
|
|
|
|
// Load order = the desktop index.html order (scripts depend on earlier ones' globals)
|
|
const order = [...html.matchAll(/<script src="([\w.-]+\.js)"><\/script>/g)].map((m) => m[1]).filter((f) => !SKIP.has(f));
|
|
const missing = scripts.filter((s) => !order.includes(s));
|
|
if (missing.length) console.warn("scripts not in index.html (not loaded):", missing.join(", "));
|
|
fs.writeFileSync(path.join(OUT, "manifest.json"), JSON.stringify({ order, views }, null, 2));
|
|
console.log(`scripts ${scripts.length}, views ${views.join(", ")}, keyframes ${keyframes.length}, chrome ${chrome.length}`);
|