diff --git a/frontend/app.js b/frontend/app.js index e850b2d..df9e3c6 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -53,6 +53,12 @@ const el = { netList: document.getElementById("netList"), instanceFilter: document.getElementById("instanceFilter"), netFilter: document.getElementById("netFilter"), + newComponentRefInput: document.getElementById("newComponentRefInput"), + newComponentTypeSelect: document.getElementById("newComponentTypeSelect"), + addComponentBtn: document.getElementById("addComponentBtn"), + newQuickNetNameInput: document.getElementById("newQuickNetNameInput"), + newQuickNetClassSelect: document.getElementById("newQuickNetClassSelect"), + addQuickNetBtn: document.getElementById("addQuickNetBtn"), canvasViewport: document.getElementById("canvasViewport"), canvasInner: document.getElementById("canvasInner"), selectionBox: document.getElementById("selectionBox"), @@ -295,6 +301,17 @@ function nextRefLike(baseRef) { return candidate; } +function defaultRefSeedForPart(partName) { + const part = String(partName ?? "").toLowerCase(); + if (part === "resistor") return "R1"; + if (part === "capacitor") return "C1"; + if (part === "inductor") return "L1"; + if (part === "diode" || part === "led") return "D1"; + if (part === "connector") return "J1"; + if (part === "generic") return "X1"; + return "U1"; +} + function escHtml(text) { return String(text ?? "") .replaceAll("&", "&") @@ -2257,6 +2274,65 @@ function setupEvents() { }); el.instanceList.addEventListener("scroll", renderInstances, { passive: true }); el.netList.addEventListener("scroll", renderNets, { passive: true }); + if (el.newComponentRefInput && el.newComponentTypeSelect) { + const syncRefPlaceholder = () => { + el.newComponentRefInput.placeholder = defaultRefSeedForPart(el.newComponentTypeSelect.value); + }; + syncRefPlaceholder(); + el.newComponentTypeSelect.addEventListener("change", syncRefPlaceholder); + } + + el.addComponentBtn?.addEventListener("click", async () => { + if (!state.model) { + return; + } + + const part = String(el.newComponentTypeSelect?.value ?? "generic").toLowerCase(); + const rawRef = normalizeRef(el.newComponentRefInput?.value ?? ""); + const ref = rawRef || nextRefLike(defaultRefSeedForPart(part)); + if (instanceByRef(ref)) { + el.jsonFeedback.textContent = `Component '${ref}' already exists.`; + return; + } + + pushHistory("add-component"); + state.model.instances.push({ + ref, + part, + properties: {}, + placement: { x: null, y: null, rotation: 0, locked: false } + }); + el.newComponentRefInput.value = ""; + setSelectedRefs([ref]); + state.selectedNet = null; + state.selectedPin = null; + await compileModel(state.model, { keepView: true, source: "add-component" }); + el.jsonFeedback.textContent = `Added component ${ref} (${part}).`; + }); + + el.addQuickNetBtn?.addEventListener("click", async () => { + if (!state.model) { + return; + } + const rawName = normalizeNetName(el.newQuickNetNameInput?.value ?? ""); + const name = rawName || nextAutoNetName(); + const netClass = NET_CLASSES.includes(el.newQuickNetClassSelect?.value ?? "") ? el.newQuickNetClassSelect.value : "signal"; + if (netByName(name)) { + el.jsonFeedback.textContent = `Net '${name}' already exists.`; + return; + } + + pushHistory("add-net"); + const nodes = state.selectedPin ? [{ ref: state.selectedPin.ref, pin: state.selectedPin.pin }] : []; + state.model.nets.push({ name, class: netClass, nodes }); + el.newQuickNetNameInput.value = ""; + state.selectedNet = name; + await compileModel(state.model, { keepView: true, source: "add-net" }); + el.jsonFeedback.textContent = nodes.length + ? `Added net ${name} (${netClass}) and connected selected pin.` + : `Added net ${name} (${netClass}).`; + }); + [el.componentSection, el.symbolSection, el.pinSection, el.netSection].forEach((section) => { if (!section) { return; diff --git a/frontend/index.html b/frontend/index.html index f3526fe..5f28258 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -41,6 +41,19 @@ +