- 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
137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
// ── 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);
|
|
}
|