// ── 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"; } function _placeBelow(prev, block, gap = 16) { 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"'; } _pos(end, 50, printfCall.offsetTop + printfCall.offsetHeight + 16); if (start && end && printfCall) { _linkFlow(start, ".hook", printfCall, ".hook-in"); _linkFlow(printfCall, ".hook-out", end, ".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); /* Variables column (x=700) */ let varTop = start; function varBelow(block) { _placeBelow(varTop, block); 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 + 16); /* ── 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"); } }; 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(); }); });