- Replace if/else wrapping in WCProcCall.generateOwnCode with branch-aware generation via _branchDescendants, _emitBranch, and _generateCallWithError - Shared blocks (reachable from both error and success paths) now appear only once after the if-block, avoiding deeply nested code - Add 8 browser tests covering merge, decision passthrough, value style, sequential calls, and shared-only error paths - Increase template block spacing (16px -> 40px) for readability - Add error WCEnd blocks to console and SDL3 templates with error-hook connections to all failure-mode proc calls - Hide editable WCProcCall input fields behind field-display spans, matching existing algorithm-block convention
474 lines
20 KiB
JavaScript
474 lines
20 KiB
JavaScript
// ── New Project modal & templates ──────────────────────────────────────────
|
|
//
|
|
// Each template is a function that receives the project name and builds the
|
|
// initial workspace (blocks, variables, connections, libraries) in place.
|
|
//
|
|
// Load order: loaded after persist.js, before app.js
|
|
// Depends on: dom.js ($id), helpers.js, draw.js, wcblock.js, blocks-*.js,
|
|
// wcprogram.js, persist.js, lib-loader.js
|
|
|
|
/**
|
|
* Removes all user-created blocks, connections, library tabs and panes,
|
|
* resetting flow arrays and LOADED_LIBS. Used by templates (new project) and
|
|
* persist.js (load workspace, with keepStorage:true to preserve localStorage).
|
|
* @param {{ keepStorage?: boolean }} [opts]
|
|
*/
|
|
function _clearWorkspace(opts = {}) {
|
|
const { keepStorage } = opts;
|
|
const conn = $id("connections");
|
|
if (conn) {
|
|
for (const p of conn.querySelectorAll("path.flow, path.flow-data")) p.remove();
|
|
}
|
|
for (const block of document.querySelectorAll("[data-block]")) {
|
|
if (block.wcForeFlow) block.wcForeFlow = [];
|
|
if (block.wcNextFlow) block.wcNextFlow = [];
|
|
}
|
|
const prog = $id("main-program");
|
|
if (prog) {
|
|
for (const block of [...prog.children]) {
|
|
if (block.id !== "main-start" && block.id !== "main-end") block.remove();
|
|
}
|
|
}
|
|
const dataArea = $id("data-area");
|
|
if (dataArea) {
|
|
for (const block of [...dataArea.querySelectorAll("[data-block]")]) block.remove();
|
|
}
|
|
const varArea = $id("variable-area");
|
|
if (varArea) {
|
|
for (const block of [...varArea.querySelectorAll("[data-block]")]) block.remove();
|
|
}
|
|
const libTabBar = document.querySelector(".lib-tab-bar");
|
|
if (libTabBar) libTabBar.innerHTML = "";
|
|
for (const pane of document.querySelectorAll(".lib-pane")) pane.remove();
|
|
for (const tab of document.querySelectorAll(".lib-tab")) tab.remove();
|
|
LOADED_LIBS.clear();
|
|
if (!keepStorage) localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
|
|
function _pos(el, left, top) {
|
|
el.style.left = left + "px";
|
|
el.style.top = top + "px";
|
|
}
|
|
|
|
const ALG_GAP = 40;
|
|
function _placeBelow(prev, block, gap = ALG_GAP) {
|
|
block.style.top = (prev.offsetTop + prev.offsetHeight + gap) + "px";
|
|
}
|
|
|
|
function _createAssign(prog, targetId, value) {
|
|
const el = prog.newNode("wc-assign");
|
|
if (!el) return null;
|
|
el.style.left = "50px";
|
|
el._el.target.value = targetId;
|
|
el._el.value.value = String(value);
|
|
el._syncType();
|
|
el._updateValueVisibility();
|
|
return el;
|
|
}
|
|
|
|
function _createVarRef(prog, varId) {
|
|
const el = prog.newVarRefNode();
|
|
if (!el) return null;
|
|
el.style.left = "50px";
|
|
el._el.select.value = varId;
|
|
const target = varId ? $id(varId) : null;
|
|
const hook = el.querySelector(".hook-out");
|
|
if (hook) hook.dataset.type = target ? (target.dataType === "vector" ? "scalar" : target.dataType) : "scalar";
|
|
return el;
|
|
}
|
|
|
|
function _createProcCall(prog, qname, resultName) {
|
|
const el = prog.newProcCallNode();
|
|
if (!el) return null;
|
|
el.style.left = "50px";
|
|
if (el._el.nameInput) el._el.nameInput.value = resultName || "";
|
|
el.setProcedure(qname);
|
|
return el;
|
|
}
|
|
|
|
function _createScalarVar(prog, name) {
|
|
const el = prog.newScalarNode();
|
|
if (!el) return null;
|
|
el.style.left = "700px";
|
|
const nameEl = el.querySelector(".var-name");
|
|
if (nameEl) nameEl.value = name;
|
|
return el;
|
|
}
|
|
|
|
function _createBooleanVar(prog, name, value) {
|
|
const el = prog.newBooleanNode();
|
|
if (!el) return null;
|
|
el.style.left = "700px";
|
|
const nameEl = el.querySelector(".var-name");
|
|
if (nameEl) nameEl.value = name;
|
|
const valEl = el.querySelector(".var-value");
|
|
if (valEl) valEl.value = value || "false";
|
|
return el;
|
|
}
|
|
|
|
async function _loadLib(libId) {
|
|
const index = await loadLibraryIndex();
|
|
const entry = index.find(e => e.id === libId);
|
|
if (entry) await loadLibrary(libId);
|
|
}
|
|
|
|
const TEMPLATES = {};
|
|
|
|
TEMPLATES.console = {
|
|
name: "Console Program",
|
|
icon: "\u25A0",
|
|
desc: "Basic C program with stdio",
|
|
async create(prog, projectName) {
|
|
_clearWorkspace();
|
|
await _loadLib("stdio");
|
|
refreshAll();
|
|
|
|
const start = $id("main-start");
|
|
const end = $id("main-end");
|
|
_pos(start, 50, 50);
|
|
|
|
const printfCall = _createProcCall(prog, "stdio/printf", "");
|
|
_placeBelow(start, printfCall);
|
|
if (printfCall) {
|
|
const arg = printfCall.querySelector(".proccall-inputs .proccall-arg-input");
|
|
if (arg) arg.value = '"Hello, World!\\n"';
|
|
}
|
|
|
|
const errEnd = prog.newNode("wc-end");
|
|
_pos(errEnd, 360, start.offsetTop);
|
|
errEnd._selectByCode(1);
|
|
errEnd._enforceZeroEnd();
|
|
|
|
_pos(end, 50, printfCall.offsetTop + printfCall.offsetHeight + ALG_GAP);
|
|
|
|
if (start && end && printfCall && errEnd) {
|
|
_linkFlow(start, ".hook", printfCall, ".hook-in");
|
|
_linkFlow(printfCall, ".hook-out", end, ".hook");
|
|
_linkFlow(printfCall, ".proccall-error-hook", errEnd, ".hook");
|
|
}
|
|
}
|
|
};
|
|
|
|
TEMPLATES["sdl3-triangle"] = {
|
|
name: "SDL3 Hello Triangle",
|
|
icon: "\u25B3",
|
|
desc: "SDL3 + OpenGL colored triangle",
|
|
async create(prog, projectName) {
|
|
_clearWorkspace();
|
|
await _loadLib("sdl3");
|
|
await _loadLib("gl");
|
|
refreshAll();
|
|
|
|
const start = $id("main-start");
|
|
const end = $id("main-end");
|
|
_pos(start, 50, 50);
|
|
|
|
/* Error exit — right of the init chain, collects all failure paths */
|
|
const errEnd = prog.newNode("wc-end");
|
|
_pos(errEnd, 420, start.offsetTop);
|
|
errEnd._selectByCode(1);
|
|
errEnd._enforceZeroEnd();
|
|
|
|
/* Variables column (x=700) */
|
|
let varTop = start;
|
|
function varBelow(block) { _placeBelow(varTop, block, 20); varTop = block; }
|
|
|
|
const varRunning = _createBooleanVar(prog, "running", "true"); varBelow(varRunning);
|
|
const varWindow = _createScalarVar(prog, "window"); varBelow(varWindow);
|
|
const varRenderer = _createScalarVar(prog, "renderer"); varBelow(varRenderer);
|
|
const varVS = _createScalarVar(prog, "vs"); varBelow(varVS);
|
|
const varFS = _createScalarVar(prog, "fs"); varBelow(varFS);
|
|
const varProgram = _createScalarVar(prog, "program"); varBelow(varProgram);
|
|
const varVAO = _createScalarVar(prog, "vao"); varBelow(varVAO);
|
|
|
|
/* ── Initialisation chain (x=50) — each block placed below the previous ── */
|
|
let prev = start;
|
|
function algBelow(block) { _placeBelow(prev, block); prev = block; }
|
|
|
|
const initCall = _createProcCall(prog, "sdl3/SDL_Init", "ret"); algBelow(initCall);
|
|
if (initCall) {
|
|
const inputs = initCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "SDL_INIT_VIDEO";
|
|
}
|
|
|
|
const setMetaCall = _createProcCall(prog, "sdl3/SDL_SetAppMetadata", ""); algBelow(setMetaCall);
|
|
if (setMetaCall) {
|
|
const inputs = setMetaCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = '"' + projectName + '"';
|
|
if (inputs[1]) inputs[1].value = '"1.0"';
|
|
if (inputs[2]) inputs[2].value = '"com.example.triangle"';
|
|
}
|
|
|
|
const winCall = _createProcCall(prog, "sdl3/SDL_CreateWindow", "window"); algBelow(winCall);
|
|
if (winCall) {
|
|
const inputs = winCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = '"Hello Triangle"';
|
|
if (inputs[1]) inputs[1].value = "640";
|
|
if (inputs[2]) inputs[2].value = "480";
|
|
if (inputs[3]) inputs[3].value = "0";
|
|
}
|
|
|
|
const renCall = _createProcCall(prog, "sdl3/SDL_CreateRenderer", "renderer"); algBelow(renCall);
|
|
if (renCall) {
|
|
const inputs = renCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "window";
|
|
if (inputs[1]) inputs[1].value = "NULL";
|
|
}
|
|
|
|
const vsAssign = _createAssign(prog, varVS ? varVS.id : "", '"#version 330\\nlayout(location=0) in vec3 pos; void main(){gl_Position=vec4(pos,1);}"'); algBelow(vsAssign);
|
|
const fsAssign = _createAssign(prog, varFS ? varFS.id : "", '"#version 330\\nout vec4 c; void main(){c=vec4(1,0,0,1);}"'); algBelow(fsAssign);
|
|
|
|
const vsCreateCall = _createProcCall(prog, "gl/glCreateShader", "vs"); algBelow(vsCreateCall);
|
|
if (vsCreateCall) {
|
|
const inputs = vsCreateCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "GL_VERTEX_SHADER";
|
|
}
|
|
|
|
const vsSrcCall = _createProcCall(prog, "gl/glShaderSource", ""); algBelow(vsSrcCall);
|
|
if (vsSrcCall) {
|
|
const inputs = vsSrcCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "vs";
|
|
if (inputs[1]) inputs[1].value = "1";
|
|
if (inputs[2]) inputs[2].value = "vs";
|
|
}
|
|
|
|
const vsCompCall = _createProcCall(prog, "gl/glCompileShader", ""); algBelow(vsCompCall);
|
|
if (vsCompCall) {
|
|
const inputs = vsCompCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "vs";
|
|
}
|
|
|
|
const fsCreateCall = _createProcCall(prog, "gl/glCreateShader", "fs"); algBelow(fsCreateCall);
|
|
if (fsCreateCall) {
|
|
const inputs = fsCreateCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "GL_FRAGMENT_SHADER";
|
|
}
|
|
|
|
const fsSrcCall = _createProcCall(prog, "gl/glShaderSource", ""); algBelow(fsSrcCall);
|
|
if (fsSrcCall) {
|
|
const inputs = fsSrcCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "fs";
|
|
if (inputs[1]) inputs[1].value = "1";
|
|
if (inputs[2]) inputs[2].value = "fs";
|
|
}
|
|
|
|
const fsCompCall = _createProcCall(prog, "gl/glCompileShader", ""); algBelow(fsCompCall);
|
|
if (fsCompCall) {
|
|
const inputs = fsCompCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "fs";
|
|
}
|
|
|
|
const progCreate = _createProcCall(prog, "gl/glCreateProgram", "program"); algBelow(progCreate);
|
|
const progAttach1 = _createProcCall(prog, "gl/glAttachShader", ""); algBelow(progAttach1);
|
|
if (progAttach1) {
|
|
const inputs = progAttach1.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "program";
|
|
if (inputs[1]) inputs[1].value = "vs";
|
|
}
|
|
const progAttach2 = _createProcCall(prog, "gl/glAttachShader", ""); algBelow(progAttach2);
|
|
if (progAttach2) {
|
|
const inputs = progAttach2.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "program";
|
|
if (inputs[1]) inputs[1].value = "fs";
|
|
}
|
|
const progLink = _createProcCall(prog, "gl/glLinkProgram", ""); algBelow(progLink);
|
|
if (progLink) {
|
|
const inputs = progLink.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "program";
|
|
}
|
|
|
|
const vaoGen = _createProcCall(prog, "gl/glGenVertexArrays", "vao"); algBelow(vaoGen);
|
|
if (vaoGen) {
|
|
const inputs = vaoGen.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "1";
|
|
}
|
|
const vaoBind = _createProcCall(prog, "gl/glBindVertexArray", ""); algBelow(vaoBind);
|
|
if (vaoBind) {
|
|
const inputs = vaoBind.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "vao";
|
|
}
|
|
|
|
const bufGen = _createProcCall(prog, "gl/glGenBuffers", ""); algBelow(bufGen);
|
|
if (bufGen) {
|
|
const inputs = bufGen.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "1";
|
|
}
|
|
const bufBind = _createProcCall(prog, "gl/glBindBuffer", ""); algBelow(bufBind);
|
|
if (bufBind) {
|
|
const inputs = bufBind.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "GL_ARRAY_BUFFER";
|
|
if (inputs[1]) inputs[1].value = "vbo";
|
|
}
|
|
|
|
/* ── Main loop ── */
|
|
const loopDec = prog.newDecisionNode(); loopDec.style.left = "50px"; algBelow(loopDec);
|
|
const pollCall = _createProcCall(prog, "sdl3/SDL_PollEvent", "event"); algBelow(pollCall);
|
|
if (pollCall) {
|
|
const inputs = pollCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "event";
|
|
}
|
|
|
|
const checkEvent = prog.newDecisionNode(); checkEvent.style.left = "50px"; algBelow(checkEvent);
|
|
const stopRunning = _createAssign(prog, varRunning ? varRunning.id : "", "false"); algBelow(stopRunning);
|
|
const clearCall = _createProcCall(prog, "gl/glClear", ""); algBelow(clearCall);
|
|
if (clearCall) {
|
|
const inputs = clearCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "GL_COLOR_BUFFER_BIT";
|
|
}
|
|
const useProgCall = _createProcCall(prog, "gl/glUseProgram", ""); algBelow(useProgCall);
|
|
if (useProgCall) {
|
|
const inputs = useProgCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "program";
|
|
}
|
|
const drawCall = _createProcCall(prog, "gl/glDrawArrays", ""); algBelow(drawCall);
|
|
if (drawCall) {
|
|
const inputs = drawCall.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "GL_TRIANGLES";
|
|
if (inputs[1]) inputs[1].value = "0";
|
|
if (inputs[2]) inputs[2].value = "3";
|
|
}
|
|
|
|
const renClear = _createProcCall(prog, "sdl3/SDL_RenderClear", ""); algBelow(renClear);
|
|
if (renClear) {
|
|
const inputs = renClear.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "renderer";
|
|
}
|
|
const renPresent = _createProcCall(prog, "sdl3/SDL_RenderPresent", ""); algBelow(renPresent);
|
|
if (renPresent) {
|
|
const inputs = renPresent.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "renderer";
|
|
}
|
|
|
|
/* ── Cleanup (after loop body) ── */
|
|
const renDestroy = _createProcCall(prog, "sdl3/SDL_DestroyRenderer", ""); algBelow(renDestroy);
|
|
if (renDestroy) {
|
|
const inputs = renDestroy.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "renderer";
|
|
}
|
|
const winDestroy = _createProcCall(prog, "sdl3/SDL_DestroyWindow", ""); algBelow(winDestroy);
|
|
if (winDestroy) {
|
|
const inputs = winDestroy.querySelectorAll(".proccall-inputs .proccall-arg-input");
|
|
if (inputs[0]) inputs[0].value = "window";
|
|
}
|
|
const quitCall = _createProcCall(prog, "sdl3/SDL_Quit", ""); algBelow(quitCall);
|
|
_pos(end, 50, prev.offsetTop + prev.offsetHeight + ALG_GAP);
|
|
|
|
/* ── Wire up flow connections ── */
|
|
_linkFlow(start, ".hook", initCall, ".hook-in");
|
|
if (initCall) _linkFlow(initCall, ".hook-out", setMetaCall, ".hook-in");
|
|
if (setMetaCall) _linkFlow(setMetaCall, ".hook-out", winCall, ".hook-in");
|
|
if (winCall) _linkFlow(winCall, ".hook-out", renCall, ".hook-in");
|
|
if (renCall) _linkFlow(renCall, ".hook-out", vsAssign, "#hook-fore");
|
|
if (vsAssign) _linkFlow(vsAssign, "#hook-next", fsAssign, "#hook-fore");
|
|
if (fsAssign) _linkFlow(fsAssign, "#hook-next", vsCreateCall, ".hook-in");
|
|
if (vsCreateCall) _linkFlow(vsCreateCall, ".hook-out", vsSrcCall, ".hook-in");
|
|
if (vsSrcCall) _linkFlow(vsSrcCall, ".hook-out", vsCompCall, ".hook-in");
|
|
if (vsCompCall) _linkFlow(vsCompCall, ".hook-out", fsCreateCall, ".hook-in");
|
|
if (fsCreateCall) _linkFlow(fsCreateCall, ".hook-out", fsSrcCall, ".hook-in");
|
|
if (fsSrcCall) _linkFlow(fsSrcCall, ".hook-out", fsCompCall, ".hook-in");
|
|
if (fsCompCall) _linkFlow(fsCompCall, ".hook-out", progCreate, ".hook-in");
|
|
if (progCreate) _linkFlow(progCreate, ".hook-out", progAttach1, ".hook-in");
|
|
if (progAttach1) _linkFlow(progAttach1, ".hook-out", progAttach2, ".hook-in");
|
|
if (progAttach2) _linkFlow(progAttach2, ".hook-out", progLink, ".hook-in");
|
|
if (progLink) _linkFlow(progLink, ".hook-out", vaoGen, ".hook-in");
|
|
if (vaoGen) _linkFlow(vaoGen, ".hook-out", vaoBind, ".hook-in");
|
|
if (vaoBind) _linkFlow(vaoBind, ".hook-out", bufGen, ".hook-in");
|
|
if (bufGen) _linkFlow(bufGen, ".hook-out", bufBind, ".hook-in");
|
|
|
|
if (bufBind) _linkFlow(bufBind, ".hook-out", loopDec, "#hook-fore");
|
|
if (loopDec) _linkFlow(loopDec, "#hook-out1", pollCall, ".hook-in");
|
|
if (pollCall) _linkFlow(pollCall, ".hook-out", checkEvent, "#hook-fore");
|
|
if (checkEvent) _linkFlow(checkEvent, "#hook-out1", stopRunning, ".hook-in");
|
|
if (stopRunning) _linkFlow(stopRunning, "#hook-next", clearCall, ".hook-in");
|
|
if (checkEvent) _linkFlow(checkEvent, "#hook-out0", clearCall, ".hook-in");
|
|
if (clearCall) _linkFlow(clearCall, ".hook-out", useProgCall, ".hook-in");
|
|
if (useProgCall) _linkFlow(useProgCall, ".hook-out", drawCall, ".hook-in");
|
|
if (drawCall) _linkFlow(drawCall, ".hook-out", renClear, ".hook-in");
|
|
if (renClear) _linkFlow(renClear, ".hook-out", renPresent, ".hook-in");
|
|
|
|
if (loopDec) _linkFlow(loopDec, "#hook-out0", renDestroy, ".hook-in");
|
|
if (renDestroy) _linkFlow(renDestroy, ".hook-out", winDestroy, ".hook-in");
|
|
if (winDestroy) _linkFlow(winDestroy, ".hook-out", quitCall, ".hook-in");
|
|
if (quitCall) _linkFlow(quitCall, ".hook-out", end, ".hook");
|
|
|
|
/* ── Error paths — connect all proc calls with error hooks to the error END ── */
|
|
for (const block of [initCall, setMetaCall, winCall, renCall, renClear, renPresent]) {
|
|
if (block) _linkFlow(block, ".proccall-error-hook", errEnd, ".hook");
|
|
}
|
|
}
|
|
};
|
|
|
|
function showNewProjectModal() {
|
|
const overlay = $id("new-project-overlay");
|
|
if (!overlay) return;
|
|
const nameInput = $id("project-name");
|
|
if (nameInput) nameInput.value = "Untitled";
|
|
/* Reset to default template */
|
|
_selectedTemplateId = "console";
|
|
for (const c of document.querySelectorAll(".template-card")) c.classList.remove("selected");
|
|
const def = document.querySelector('.template-card[data-template="console"]');
|
|
if (def) def.classList.add("selected");
|
|
overlay.style.display = "";
|
|
}
|
|
|
|
function hideNewProjectModal() {
|
|
const overlay = $id("new-project-overlay");
|
|
if (overlay) overlay.style.display = "none";
|
|
}
|
|
|
|
/* Expose for app.js */
|
|
window.TEMPLATES = TEMPLATES;
|
|
window.showNewProjectModal = showNewProjectModal;
|
|
window.hideNewProjectModal = hideNewProjectModal;
|
|
|
|
/* Wire up modal interactivity on DOMContentLoaded */
|
|
let _selectedTemplateId = "console";
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const overlay = $id("new-project-overlay");
|
|
if (!overlay) return;
|
|
const list = overlay.querySelector("#template-list");
|
|
|
|
/* Template card selection via event delegation */
|
|
if (list) {
|
|
list.addEventListener("click", e => {
|
|
const card = e.target.closest(".template-card");
|
|
if (!card) return;
|
|
for (const c of list.children) c.classList.remove("selected");
|
|
card.classList.add("selected");
|
|
_selectedTemplateId = card.dataset.template;
|
|
});
|
|
}
|
|
|
|
/* Cancel button */
|
|
const cancelBtn = overlay.querySelector(".modal-cancel");
|
|
if (cancelBtn) cancelBtn.addEventListener("click", hideNewProjectModal);
|
|
|
|
/* Create button */
|
|
const createBtn = overlay.querySelector(".modal-create");
|
|
if (createBtn) {
|
|
createBtn.addEventListener("click", async () => {
|
|
const nameInput = $id("project-name");
|
|
const name = nameInput ? nameInput.value.trim() || "Untitled" : "Untitled";
|
|
const template = TEMPLATES[_selectedTemplateId];
|
|
if (!template) return;
|
|
|
|
hideNewProjectModal();
|
|
const prog = $id("main-program");
|
|
if (!prog) return;
|
|
|
|
try {
|
|
await template.create(prog, name);
|
|
} catch (e) {
|
|
showError("Template creation failed: " + (e.message || e));
|
|
return;
|
|
}
|
|
refreshAll();
|
|
});
|
|
}
|
|
|
|
/* Close on overlay click (background) */
|
|
overlay.addEventListener("click", e => {
|
|
if (e.target === overlay) hideNewProjectModal();
|
|
});
|
|
});
|