overhaul: modular architecture, tests, doc generation, warning system, and UX improvements
- Modular block files: split into blocks-algo, blocks-data, blocks-proc - New modules: sections.js (labelled canvas areas), proc-browser.js (procedure tree modal), lib-loader.js (lazy library loading), templates.js (project templates), errno-data.js (errno table) - Test infrastructure: browser test suite (28 tests) and Node pure-function tests (75 tests) - Warning system: unified .block-warn for disconnected inputs, unconnected error hooks, and zero-exit-in-error-path - Error path highlighting: red flow paths and block borders for error-only subgraphs - Input hooks: hidden by default, shown during linking; outputs always at bottom - Block editor modal: pencil icon, colour picker for accent, compact field labels - Doc generation: standalone node script extracts JSDoc -> docs/API.md (124 declarations) - Persistence: save/restore sections, errno selection, block colours - Fixes: _segmentsIntersect bug, WCEnd crash from closest(this), drawpath guard on detached elements
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
// ── On-demand library loader ────────────────────────────────────────────────
|
||||
//
|
||||
// Fetches procedure definitions from JSON files in the libraries/ directory.
|
||||
// At startup only the index is loaded; individual libraries are fetched when
|
||||
// the user clicks their tab for the first time.
|
||||
//
|
||||
// Depends on: app.js (addLibTab, deriveNaturalName)
|
||||
// blocks-proc.js (WCProcedure)
|
||||
// wcprogram.js (BLOCK_REGISTRY)
|
||||
|
||||
|
||||
/** @type {Set<string>} Tracks which libraries have been loaded (prevents re-loading). */
|
||||
const LOADED_LIBS = new Set();
|
||||
/** @type {Array<{id:string, name:string, group?:string}>} */
|
||||
let LIB_INDEX = [];
|
||||
|
||||
/**
|
||||
* Creates param rows on a procedure block and sets their name/type values.
|
||||
* Follows the same pattern as WCProcedure.restoreData().
|
||||
* @param {HTMLElement} proc A wc-procedure element
|
||||
* @param {Array<[string,string]>} inputs Array of [name, type] pairs
|
||||
* @param {Array<[string,string]>} outputs Array of [name, type] pairs
|
||||
*/
|
||||
function _addProcParams(proc, inputs, outputs) {
|
||||
for (const [name, type] of inputs) {
|
||||
proc._addParamRow(proc.querySelector(".proc-inputs"), "in");
|
||||
const rows = proc.querySelectorAll(".proc-inputs .proc-param-row");
|
||||
const last = rows[rows.length - 1];
|
||||
last.querySelector(".proc-param-name").value = name;
|
||||
last.querySelector(".proc-param-type").value = type;
|
||||
}
|
||||
for (const [name, type] of outputs) {
|
||||
proc._addParamRow(proc.querySelector(".proc-outputs"), "out");
|
||||
const rows = proc.querySelectorAll(".proc-outputs .proc-param-row");
|
||||
const last = rows[rows.length - 1];
|
||||
last.querySelector(".proc-param-name").value = name;
|
||||
last.querySelector(".proc-param-type").value = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches libraries/index.json and returns the library metadata array.
|
||||
* Called once at startup.
|
||||
* @returns {Promise<Array<{id:string, name:string, group?:string}>>}
|
||||
*/
|
||||
async function loadLibraryIndex() {
|
||||
try {
|
||||
const res = await fetch("libraries/index.json");
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
LIB_INDEX = await res.json();
|
||||
} catch (e) {
|
||||
showError("Could not load library index: " + e.message);
|
||||
LIB_INDEX = [];
|
||||
}
|
||||
return LIB_INDEX;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a single library by its id (e.g. "stdio", "sdl3").
|
||||
* Creates the tab if it doesn't exist yet, then populates all procedures.
|
||||
* Idempotent — subsequent calls for the same lib are no-ops.
|
||||
*/
|
||||
async function loadLibrary(libId) {
|
||||
if (LOADED_LIBS.has(libId)) return;
|
||||
LOADED_LIBS.add(libId);
|
||||
|
||||
const prog = $id("main-program");
|
||||
if (!prog) return;
|
||||
|
||||
let data;
|
||||
try {
|
||||
const res = await fetch(`libraries/${libId}.json`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
showError(`Could not load library "${libId}": ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const procs = data.procedures;
|
||||
if (!procs || !procs.length) return;
|
||||
|
||||
/* Mark this lib as the active tab if it's the first one loaded */
|
||||
const isFirst = LOADED_LIBS.size === 1;
|
||||
|
||||
/* Ensure the tab exists — but don't duplicate if already created from index */
|
||||
if (!document.querySelector(`.lib-tab[data-lib-name="${libId}"]`)) {
|
||||
addLibTab(libId, isFirst);
|
||||
}
|
||||
|
||||
for (const pd of procs) {
|
||||
const proc = prog.newProcedureNode(libId);
|
||||
if (!proc) continue;
|
||||
proc.querySelector(".proc-tech-name").textContent = pd.name;
|
||||
proc.querySelector(".proc-natural-name").value = pd.naturalName || deriveNaturalName(pd.name);
|
||||
if (pd.desc) {
|
||||
const db = proc.querySelector(".proc-desc-body");
|
||||
if (db) db.textContent = pd.desc;
|
||||
}
|
||||
proc.dataset.readonly = "true";
|
||||
if (pd.returnStyle === "value") proc.dataset.returnStyle = "value";
|
||||
if (pd.returnStyle === "error") proc.dataset.returnStyle = "error";
|
||||
if (pd.formatString) proc.dataset.formatString = "true";
|
||||
_addProcParams(proc, pd.inputs || [], pd.outputs || []);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the loaded library index (synchronous after loadLibraryIndex).
|
||||
* @returns {Array<{id:string, name:string, group?:string}>}
|
||||
*/
|
||||
function getLibraryIndex() {
|
||||
return LIB_INDEX;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a library has been loaded yet.
|
||||
* @param {string} libId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isLibraryLoaded(libId) {
|
||||
return LOADED_LIBS.has(libId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks a library as loaded (for user-created libs that don't have JSON files).
|
||||
* @param {string} libId
|
||||
*/
|
||||
function markLibLoaded(libId) {
|
||||
LOADED_LIBS.add(libId);
|
||||
}
|
||||
Reference in New Issue
Block a user