diff --git a/Web/candoDocument.js b/Web/candoDocument.js index dd9ba8f4f..d2ae0f225 100644 --- a/Web/candoDocument.js +++ b/Web/candoDocument.js @@ -848,6 +848,21 @@ }); } + function updateGrId(name, newGrId) { + return _withEditor(() => { + if (!name || !name.trim()) + return { ok: false, error: "Node name is required" }; + const n = name.trim(); + if (!newGrId || !/^0x[0-9a-fA-F]+$/i.test(newGrId.trim())) + return { + ok: false, + error: "GR ID must be a hex value (e.g. 0x2B)", + }; + _grIds.set(n, newGrId.trim()); + return { ok: true, warnings: [] }; + }); + } + function addBus(deviceName, busPort) { return _withEditor(() => { deviceName = (deviceName || "").trim(); @@ -1424,6 +1439,7 @@ addDevice, deleteDevice, renameDevice, + updateGrId, addBus, addRoute, deleteRouteEntry, diff --git a/Web/formNodeEdit.js b/Web/formNodeEdit.js index 03a0c7edd..96951e15c 100644 --- a/Web/formNodeEdit.js +++ b/Web/formNodeEdit.js @@ -1,8 +1,10 @@ // Purpose: "Edit Node" modal form. -// Allows renaming a routing device/node entry in-place. Validates that the new -// name is non-empty and does not collide with an existing node. A no-op save -// (same name) closes without marking any change. -// Depends on: formUtils.js (FormUtils), editor.js (GrcanEditor). +// Allows editing a routing device/node entry in-place: its name and its GR ID +// (hex node ID). Validates that the new name is non-empty and does not collide +// with an existing node, and that the GR ID is hex and not already used by +// another node. A no-op save (name and ID unchanged) closes without marking any +// change. +// Depends on: formUtils.js (FormUtils), editor.js (GrcanEditor), candoDocument.js. // Registers: window.GrcanEditor.showRoutingNodeEditForm (function () { @@ -13,12 +15,22 @@ const fu = window.FormUtils; const { overlay, body, footer } = fu.createModal("Edit Node"); + const oldGrId = + (window.GrcanDocument && window.GrcanDocument.getGrId(oldDeviceName)) || + ""; + const nameF = fu.makeFormRow( "Node Name", fu.makeInput("text", oldDeviceName || "", "Node Name"), true, ); + const idF = fu.makeFormRow( + "Node ID (GR ID)", + fu.makeInput("text", oldGrId, "0x2B"), + true, + ); body.appendChild(nameF.row); + body.appendChild(idF.row); const cancelBtn = fu.makeBtn("Cancel"); cancelBtn.addEventListener("click", () => fu.closeOverlay(overlay)); @@ -28,6 +40,7 @@ saveBtn.addEventListener("click", () => { const newName = nameF.input.value.trim(); + const newId = idF.input.value.trim(); let ok = true; if (!newName) { @@ -42,18 +55,45 @@ } else { nameF.error.textContent = ""; } + + if (!newId) { + idF.error.textContent = "Required"; + ok = false; + } else if (!/^0x[0-9a-fA-F]+$/.test(newId)) { + idF.error.textContent = "Hex format (e.g. 0x2B)"; + ok = false; + } else { + idF.error.textContent = ""; + } if (!ok) return; - if (newName === oldDeviceName) { + const nameChanged = newName !== oldDeviceName; + const idChanged = newId.toLowerCase() !== String(oldGrId).toLowerCase(); + + if (!nameChanged && !idChanged) { fu.closeOverlay(overlay, { force: true }); return; } - const result = window.GrcanDocument.renameDevice(oldDeviceName, newName); - if (!result.ok) { - nameF.error.textContent = result.error; - return; + if (nameChanged) { + const result = window.GrcanDocument.renameDevice( + oldDeviceName, + newName, + ); + if (!result.ok) { + nameF.error.textContent = result.error; + return; + } } + + if (idChanged) { + const result = window.GrcanDocument.updateGrId(newName, newId); + if (!result.ok) { + idF.error.textContent = result.error; + return; + } + } + editor.markEdited("routeNode:" + newName); fu.closeOverlay(overlay, { force: true }); editor.triggerReRender();