import { app } from "../../../scripts/app.js"; import { SettingsPopup } from "./AIDocSharedClasses.js"; class MultiIntNode { constructor(node) { this.node = node; // Default properties if (typeof node.properties.activeCount !== "number") { node.properties.activeCount = 5; } if (typeof node.properties.intStep !== "number") { node.properties.intStep = 1; } if (!Array.isArray(node.properties.labels)) { node.properties.labels = []; } if (!Array.isArray(node.properties.values)) { node.properties.values = []; } this.adjustArrays(); // Default node size if (!node.size) { node.size = [280, 200]; } // Rect arrays for label, value, pencil, gear, increment, and decrement buttons this.labelRects = []; this.valueRects = []; this.pencilRects = []; this.incrementRects = []; this.decrementRects = []; this.gearRect = null; // Setup outputs/widgets this.setupOutputsAndWidgets(); // Draw function node.onDrawForeground = this.onDrawForeground.bind(this); } setupOutputsAndWidgets() { this.node.outputs = []; this.node.widgets = []; for (let i = 0; i < this.node.properties.activeCount; i++) { // Create an INT output for each active slot this.node.addOutput("", "INT"); // Create a hidden widget for each value const w = this.node.addWidget( "number", `value${i+1}`, this.node.properties.values[i], (val) => this.onValueChanged(i, val), { hidden: true } ); w.computeSize = () => [0, -4]; w.hidden = true; } // Rename outputs to i1..iN for (let i = 0; i < this.node.outputs.length; i++) { this.node.outputs[i].name = `i${i+1}`; } } onDrawForeground(ctx) { if (this.node.flags.collapsed) return; const margin = 8; const rowHeight = 30; const rowSpacing = margin; const count = this.node.properties.activeCount; // Compute node size let totalHeight = margin + (count * (rowHeight + rowSpacing)); const MIN_WIDTH = 320; if (this.node.size[0] < MIN_WIDTH) { this.node.size[0] = MIN_WIDTH; } this.node.size[1] = totalHeight; // Draw node background (#181818) ctx.fillStyle = "#181818"; ctx.fillRect(0, 0, this.node.size[0], this.node.size[1]); // Row backgrounds const rowBgWidth = this.node.size[0] - margin - 48; // Reserve space for outputs this.labelRects = []; this.valueRects = []; this.pencilRects = []; this.incrementRects = []; this.decrementRects = []; let y = margin; for (let i = 0; i < count; i++) { // Row background // Set the radius for the rounded corners const radius = 5; // Adjust as needed // Row background with rounded corners ctx.fillStyle = "#444"; ctx.beginPath(); // Top-left corner ctx.moveTo(margin + radius, y); ctx.arcTo(margin + rowBgWidth, y, margin + rowBgWidth, y + rowHeight, radius); // Top-right corner // Bottom-right corner ctx.arcTo(margin + rowBgWidth, y + rowHeight, margin, y + rowHeight, radius); // Bottom-right corner // Bottom-left corner ctx.arcTo(margin, y + rowHeight, margin, y, radius); // Bottom-left corner // Top-left corner (to close the path) ctx.arcTo(margin, y, margin + rowBgWidth, y, radius); // Top-left corner ctx.closePath(); ctx.fill(); // Static label (left) - e.g., i1, i2, i3, etc. ctx.fillStyle = "#7d7d7d"; ctx.font = "12px Arial"; ctx.textBaseline = "middle"; ctx.textAlign = "left"; const staticLabel = `·i${i + 1}`; const staticLabelMetrics = ctx.measureText(staticLabel); const staticLabelX = margin + 2; const staticLabelY = y + rowHeight / 2; ctx.fillText(staticLabel, staticLabelX, staticLabelY); // Draw the vertical line const lineStartX = staticLabelX + staticLabelMetrics.width + 4; // Right of static label const lineStartY = staticLabelY - rowHeight / 2; // Start from the middle of the static label const lineEndY = lineStartY + rowHeight; // End after the row height (making the line same length as row) // Set the line color ctx.strokeStyle = "#7d7d7d"; // Same color as label for consistency ctx.lineWidth = 1; // Line thickness ctx.beginPath(); ctx.moveTo(lineStartX, lineStartY); // Starting point of the line ctx.lineTo(lineStartX, lineEndY); // Vertical line to the bottom of the row ctx.stroke(); // Actually draw the line // Custom label (right of static label) - e.g., INT 1, INT 2, etc. ctx.fillStyle = "#fff"; ctx.font = "14px Arial"; const customLabel = this.node.properties.labels[i] || `INT ${i + 1}`; const customLabelMetrics = ctx.measureText(customLabel); const customLabelX = staticLabelX + staticLabelMetrics.width + 10; // space between iN and INT N ctx.fillText(customLabel, customLabelX, staticLabelY); this.labelRects[i] = { x: customLabelX, y, w: customLabelMetrics.width + 8, h: rowHeight }; // Value (middle) const val = this.node.properties.values[i] ?? 0; const valStr = val.toString(); ctx.textAlign = "right"; const valMetrics = ctx.measureText(valStr); const valX = margin + rowBgWidth - 90; // space for increment/decrement buttons and pencil const valY = y + rowHeight / 2; ctx.fillText(valStr, valX, valY); this.valueRects[i] = { x: valX - valMetrics.width - 4, y, w: valMetrics.width + 8, h: rowHeight }; // Pencil icon ctx.textAlign = "left"; ctx.fillStyle = "#ccc"; ctx.font = "16px Arial"; const pencilIcon = "✎"; const pencilMetrics = ctx.measureText(pencilIcon); const pencilX = valX + 6; const pencilY = y + rowHeight / 2; ctx.fillText(pencilIcon, pencilX, pencilY); this.pencilRects[i] = { x: pencilX, y, w: pencilMetrics.width + 4, h: rowHeight }; // Create a fixed-size rounded box for the increment (+) button const incrementButtonBoxX = valX + 36; // Space for the box (positioned to the right of the value) const incrementButtonBoxY = y + (rowHeight / 4) - 2; const buttonBoxWidth = 20; // Fixed width for the button box const buttonBoxHeight = 20; // Fixed height for the button box // Set the background color for the increment button box ctx.fillStyle = "#4b3e72"; // Purple background ctx.beginPath(); ctx.moveTo(incrementButtonBoxX + 5, incrementButtonBoxY); // Top-left corner ctx.arcTo(incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY, incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY + buttonBoxHeight, 5); // Top-right corner ctx.arcTo(incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY + buttonBoxHeight, incrementButtonBoxX, incrementButtonBoxY + buttonBoxHeight, 5); // Bottom-right corner ctx.arcTo(incrementButtonBoxX, incrementButtonBoxY + buttonBoxHeight, incrementButtonBoxX, incrementButtonBoxY, 5); // Bottom-left corner ctx.arcTo(incrementButtonBoxX, incrementButtonBoxY, incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY, 5); // Top-left corner ctx.closePath(); ctx.fill(); // Draw Increment Button (+) const incrementButton = "+"; ctx.font = "12px Arial"; // Smaller font size for the button text ctx.fillStyle = "#fff"; // White color for the text inside the button ctx.fillText(incrementButton, incrementButtonBoxX + 6, incrementButtonBoxY + buttonBoxHeight / 2); // Draw + button in the middle // Create a fixed-size rounded box for the decrement (-) button const decrementButtonBoxX = incrementButtonBoxX + 24; // Space for the box to the right of the + button const decrementButtonBoxY = incrementButtonBoxY; // Same Y position as the + button box // Set the background color for the decrement button box ctx.fillStyle = "#4b3e72"; // Purple background ctx.beginPath(); ctx.moveTo(decrementButtonBoxX + 5, decrementButtonBoxY); // Top-left corner ctx.arcTo(decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY, decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY + buttonBoxHeight, 5); // Top-right corner ctx.arcTo(decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY + buttonBoxHeight, decrementButtonBoxX, decrementButtonBoxY + buttonBoxHeight, 5); // Bottom-right corner ctx.arcTo(decrementButtonBoxX, decrementButtonBoxY + buttonBoxHeight, decrementButtonBoxX, decrementButtonBoxY, 5); // Bottom-left corner ctx.arcTo(decrementButtonBoxX, decrementButtonBoxY, decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY, 5); // Top-left corner ctx.closePath(); ctx.fill(); // Draw Decrement Button (-) const decrementButton = "-"; ctx.font = "12px Arial"; // Smaller font size for the button text ctx.fillStyle = "#fff"; // White color for the text inside the button ctx.fillText(decrementButton, decrementButtonBoxX + 8, decrementButtonBoxY + buttonBoxHeight / 2); // Draw - button in the middle // Save button areas for interaction detection (with separate button box positions) this.incrementRects[i] = { x: incrementButtonBoxX, y: incrementButtonBoxY, w: buttonBoxWidth, h: buttonBoxHeight }; this.decrementRects[i] = { x: decrementButtonBoxX, y: decrementButtonBoxY, w: buttonBoxWidth, h: buttonBoxHeight }; y += rowHeight + rowSpacing; } // Gear icon at bottom-right const gearIcon = "⚙️"; ctx.font = "18px Arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "#ccc"; const gearWidth = ctx.measureText(gearIcon).width; const gearHeight = 18; const gearX = this.node.size[0] - margin - (gearWidth / 2); const gearY = this.node.size[1] - margin - (gearHeight / 2); ctx.fillText(gearIcon, gearX, gearY); this.gearRect = { x: gearX - gearWidth / 2, y: gearY - gearHeight / 2, w: gearWidth, h: gearHeight }; } startValueDragToggle(index, initialEvent) { // If already dragging, then stop dragging (commit the value) if (this.dragActive) { this.stopValueDrag(); return; } // Start drag mode for this value. this.dragActive = true; this.dragIndex = index; this.dragInitialX = initialEvent.clientX; this.dragInitialValue = this.node.properties.values[index]; const step = this.node.properties.intStep || 1; const sensitivity = 10; // pixels per step change // Get the canvas element. const canvasEl = document.getElementById("graph-canvas"); if (!canvasEl) return; // Define a pointermove handler to update the value as the pointer moves. this._onValuePointerMove = (e) => { const dx = e.clientX - this.dragInitialX; const steps = Math.floor(dx / sensitivity); const newValue = this.dragInitialValue + steps * step; if (newValue !== this.node.properties.values[this.dragIndex]) { this.node.properties.values[this.dragIndex] = newValue; if (this.node.widgets[this.dragIndex]) { this.node.widgets[this.dragIndex].value = newValue; } this.node.setDirtyCanvas(true, true); } }; // Attach the pointermove handler to the canvas. canvasEl.addEventListener("pointermove", this._onValuePointerMove); // Define a global click handler to stop the drag. this._onGlobalClickToStop = (e) => { // Prevent this click from re-triggering startValueDragToggle immediately. console.log("Global click detected—stopping drag"); this.stopValueDrag(); }; // Attach the global click handler with capture so it fires before other click handlers. document.addEventListener("click", this._onGlobalClickToStop, { capture: true }); // Also attach a keydown handler on the document so that Enter or Esc stop drag mode. this._onKeyDownToStop = (e) => { if (e.key === "Enter" || e.key === "Escape") { console.log("Keydown (" + e.key + ") detected—stopping drag"); this.stopValueDrag(); } }; document.addEventListener("keydown", this._onKeyDownToStop); } stopValueDrag() { const canvasEl = document.getElementById("graph-canvas"); if (!canvasEl) return; if (this._onValuePointerMove) { canvasEl.removeEventListener("pointermove", this._onValuePointerMove); this._onValuePointerMove = null; } if (this._onGlobalClickToStop) { document.removeEventListener("click", this._onGlobalClickToStop, { capture: true }); this._onGlobalClickToStop = null; } if (this._onKeyDownToStop) { document.removeEventListener("keydown", this._onKeyDownToStop); this._onKeyDownToStop = null; } this.dragActive = false; this.dragIndex = null; this.node.setDirtyCanvas(true, true); } onMouseDown(e, pos) { // Check pencil icons for (let i = 0; i < this.pencilRects.length; i++) { const r = this.pencilRects[i]; if (!r) continue; if (pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { console.log("Pencil clicked row:", i); const centerX = r.x + (r.w / 2); const centerY = r.y + (r.h / 2); this.showInlineIntEditor(i, { x: centerX, y: centerY, w: 80, h: 24 }, e); return true; } } // Check for custom label click to edit it for (let i = 0; i < this.labelRects.length; i++) { const r = this.labelRects[i]; if (!r) continue; if (pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { console.log("Label clicked row:", i); const centerX = r.x + (r.w / 2); const centerY = r.y + (r.h / 2); this.showInlineLabelEditor(i, { x: centerX, y: centerY, w: 80, h: 24 }, e); return true; } } // Check increment buttons for (let i = 0; i < this.incrementRects.length; i++) { const r = this.incrementRects[i]; if (pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { console.log("Increment button clicked row:", i); this.incrementValue(i); return true; } } // Check decrement buttons for (let i = 0; i < this.decrementRects.length; i++) { const r = this.decrementRects[i]; if (pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { console.log("Decrement button clicked row:", i); this.decrementValue(i); return true; } } // Check if the click is in a value rectangle and start drag-to-change behavior // Then, check if the click is in a value rectangle: for (let i = 0; i < this.valueRects.length; i++) { const r = this.valueRects[i]; if (r && pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { // Toggle drag mode on the value at index i. this.startValueDragToggle(i, e); return true; } } // Check gear icon if (this.gearRect) { const r = this.gearRect; if (pos[0] >= r.x && pos[0] <= r.x + r.w && pos[1] >= r.y && pos[1] <= r.y + r.h) { console.log("Gear icon clicked"); this.openGearPopup(e); return true; } } return false; } showInlineLabelEditor(index, rect, e) { e.preventDefault(); e.stopPropagation(); const graphCanvas = app.graphcanvas; if (!graphCanvas) { console.warn("No graphCanvas found, using fallback coords."); const fallbackX = e.clientX - (rect.w * 0.5); const fallbackY = e.clientY - (rect.h * 0.5); return this.spawnInlineLabelEditorAtScreenPos(index, fallbackX, fallbackY, rect); } const nodeX = this.node.pos[0]; const nodeY = this.node.pos[1]; const localX = rect.x + (rect.w * 0.5); const localY = rect.y + (rect.h * 0.5); const scale = graphCanvas.ds.scale; const offset = graphCanvas.ds.offset; const canvasX = (nodeX + localX) * scale + offset[0]; const canvasY = (nodeY + localY) * scale + offset[1]; const canvasRect = graphCanvas.canvas.getBoundingClientRect(); const screenX = canvasRect.left + canvasX; const screenY = canvasRect.top + canvasY; this.spawnInlineLabelEditorAtScreenPos(index, screenX, screenY, rect); } spawnInlineLabelEditorAtScreenPos(index, screenX, screenY, rect) { const input = document.createElement("input"); input.type = "text"; input.value = this.node.properties.labels[index] || `INT ${index + 1}`; input.style.position = "fixed"; const offsetX = -(rect.w * 0.5); const offsetY = -(rect.h * 0.5); const FixedOffsetX = 35; const FixedOffsetY = 10; input.style.left = (screenX + offsetX + FixedOffsetX) + "px"; input.style.top = (screenY + offsetY + FixedOffsetY) + "px"; input.style.width = Math.max(rect.w, 250) + "px"; input.style.height = Math.max(rect.h, 30) + "px"; input.style.fontSize = "14px"; input.style.zIndex = "10000"; input.style.background = "#181818"; input.style.color = "#fff"; input.style.border = "2px solid #4b3e72"; input.style.borderRadius = "6px"; input.style.outline = "none"; input.style.padding = "0 6px"; document.body.appendChild(input); setTimeout(() => { input.focus(); input.select(); }, 10); const commit = () => { const val = input.value.trim(); if (val.length > 0) { this.node.properties.labels[index] = val; this.node.setDirtyCanvas(true, true); } remove(); }; const remove = () => { document.removeEventListener("click", outsideClick, true); if (input.parentNode) { input.parentNode.removeChild(input); } }; const outsideClick = (evt) => { if (evt.target !== input) { commit(); } }; setTimeout(() => { document.addEventListener("click", outsideClick, true); }, 100); input.addEventListener("keydown", (evt) => { if (evt.key === "Enter") { evt.preventDefault(); commit(); } else if (evt.key === "Escape") { evt.preventDefault(); remove(); } }); } openGearPopup(e) { e.stopPropagation(); let nodeElem = this.node.htmlElement; if (!nodeElem) { console.warn("No node.htmlElement, using fallback coords."); this.createGearPopupFallback(e.clientX, e.clientY); return; } const nodeRect = nodeElem.getBoundingClientRect(); const popupX = nodeRect.left + 10; const popupY = nodeRect.top + 10; const popup = new SettingsPopup(this.node); popup.open(popupX, popupY); this.populateGearPopup(popup); // <-- same logic } populateGearPopup(popup) { const content = popup.getContentContainer(); popup.clearContent(); // no stale content // "Add/Remove Ints" label const addRemoveLabel = document.createElement("div"); addRemoveLabel.textContent = "Add/Remove Ints"; addRemoveLabel.style.color = "#fff"; addRemoveLabel.style.fontSize = "12px"; addRemoveLabel.style.textAlign = "center"; content.appendChild(addRemoveLabel); // Button row const buttonRow = document.createElement("div"); buttonRow.style.display = "flex"; buttonRow.style.justifyContent = "center"; // Centering buttons buttonRow.style.alignItems = "center"; buttonRow.style.width = "100%"; buttonRow.style.gap = "10px"; // Reduced space between the buttons content.appendChild(buttonRow); const minusBtn = document.createElement("button"); minusBtn.textContent = "-"; minusBtn.style.background = "#333"; minusBtn.style.color = "#fff"; minusBtn.style.padding = "3px 10px"; // Set fixed padding for size consistency minusBtn.style.fontSize = "16px"; buttonRow.appendChild(minusBtn); const plusBtn = document.createElement("button"); plusBtn.textContent = "+"; plusBtn.style.background = "#333"; plusBtn.style.color = "#fff"; plusBtn.style.padding = "3px 6px"; // Set fixed padding for size consistency plusBtn.style.fontSize = "16px"; buttonRow.appendChild(plusBtn); // Steps label const stepsLabel = document.createElement("div"); stepsLabel.textContent = "Steps"; stepsLabel.style.color = "#fff"; stepsLabel.style.fontSize = "12px"; stepsLabel.style.textAlign = "center"; content.appendChild(stepsLabel); // Steps input const stepsInput = document.createElement("input"); stepsInput.type = "number"; stepsInput.value = this.node.properties.intStep.toString(); stepsInput.min = "1"; stepsInput.step = "1"; stepsInput.style.background = "#333"; stepsInput.style.color = "#fff"; content.appendChild(stepsInput); // Steps input change stepsInput.addEventListener("change", () => { const val = parseInt(stepsInput.value, 10); if (!isNaN(val) && val >= 1) { this.node.properties.intStep = val; } else { stepsInput.value = this.node.properties.intStep.toString(); } }); // minus logic minusBtn.addEventListener("click", (evt) => { evt.stopPropagation(); // Save current width const currentWidth = this.node.size[0]; if (this.node.properties.activeCount > 1) { this.node.properties.activeCount = Math.max(1, this.node.properties.activeCount - 1); this.adjustArrays(); this.setupOutputsAndWidgets(); this.node.setDirtyCanvas(true, true); // Reset width after updating active count this.node.size[0] = currentWidth; this.node.setDirtyCanvas(true, true); setTimeout(() => popup.reposition(this.node.htmlElement, 10, 10), 50); } }); // plus logic plusBtn.addEventListener("click", (evt) => { evt.stopPropagation(); // Save current width const currentWidth = this.node.size[0]; if (this.node.properties.activeCount < 20) { this.node.properties.activeCount = Math.min(20, this.node.properties.activeCount + 1); this.adjustArrays(); this.setupOutputsAndWidgets(); this.node.setDirtyCanvas(true, true); // Reset width after updating active count this.node.size[0] = currentWidth; this.node.setDirtyCanvas(true, true); setTimeout(() => popup.reposition(this.node.htmlElement, 10, 10), 50); } }); } createGearPopupFallback(x, y) { const popup = new SettingsPopup(this.node); popup.open(x, y); this.populateGearPopup(popup); // <-- same logic again } showInlineIntEditor(index, rect, e) { e.preventDefault(); e.stopPropagation(); const graphCanvas = app.graphcanvas; if (!graphCanvas) { console.warn("No graphCanvas found, using fallback coords."); const fallbackX = e.clientX - (rect.w * 0.5); const fallbackY = e.clientY - (rect.h * 0.5); return this.spawnInlineEditorAtScreenPos(index, fallbackX, fallbackY, rect); } const nodeX = this.node.pos[0]; const nodeY = this.node.pos[1]; const localX = rect.x + (rect.w * 0.5); const localY = rect.y + (rect.h * 0.5); const scale = graphCanvas.ds.scale; const offset = graphCanvas.ds.offset; const canvasX = (nodeX + localX) * scale + offset[0]; const canvasY = (nodeY + localY) * scale + offset[1]; const canvasRect = graphCanvas.canvas.getBoundingClientRect(); const screenX = canvasRect.left + canvasX; const screenY = canvasRect.top + canvasY; this.spawnInlineEditorAtScreenPos(index, screenX, screenY, rect); } spawnInlineEditorAtScreenPos(index, screenX, screenY, rect) { const input = document.createElement("input"); input.type = "text"; input.value = this.node.properties.values[index].toString(); input.style.position = "fixed"; const offsetX = -(rect.w * 0.5); const offsetY = -(rect.h * 0.5); const FixedOffsetX = 25; const FixedOffsetY = 10; input.style.left = (screenX + offsetX + FixedOffsetX) + "px"; input.style.top = (screenY + offsetY + FixedOffsetY) + "px"; input.style.width = Math.max(rect.w, 60) + "px"; input.style.height = Math.max(rect.h, 24) + "px"; input.style.fontSize = "14px"; input.style.zIndex = "10000"; input.style.background = "#181818"; input.style.color = "#fff"; input.style.border = "2px solid #4b3e72"; input.style.borderRadius = "6px"; input.style.outline = "none"; input.style.padding = "0 6px"; document.body.appendChild(input); setTimeout(() => { input.focus(); input.select(); }, 10); const commit = () => { const val = parseInt(input.value, 10); if (!isNaN(val)) { this.node.properties.values[index] = val; if (this.node.widgets[index]) { this.node.widgets[index].value = val; } this.node.setDirtyCanvas(true, true); } remove(); }; const remove = () => { document.removeEventListener("click", outsideClick, true); if (input.parentNode) { input.parentNode.removeChild(input); } }; const outsideClick = (evt) => { if (evt.target !== input) { commit(); } }; setTimeout(() => { document.addEventListener("click", outsideClick, true); }, 100); input.addEventListener("keydown", (evt) => { if (evt.key === "Enter") { evt.preventDefault(); commit(); } else if (evt.key === "Escape") { evt.preventDefault(); remove(); } }); } onValueChanged(index, val) { this.node.properties.values[index] = parseInt(val, 10) || 0; this.node.setDirtyCanvas(true, true); } incrementValue(index) { const step = this.node.properties.intStep || 1; // Increase the value at the given index this.node.properties.values[index] += step; // Update the corresponding widget (so it gets updated on UI as well) if (this.node.widgets[index]) { this.node.widgets[index].value = this.node.properties.values[index]; } // Mark the canvas as dirty so it gets redrawn with the new value this.node.setDirtyCanvas(true, true); } decrementValue(index) { const step = this.node.properties.intStep || 1; // Decrease the value at the given index this.node.properties.values[index] -= step; // Update the corresponding widget (so it gets updated on UI as well) if (this.node.widgets[index]) { this.node.widgets[index].value = this.node.properties.values[index]; } // Mark the canvas as dirty so it gets redrawn with the new value this.node.setDirtyCanvas(true, true); } onValueChanged(index, val) { this.node.properties.values[index] = parseInt(val, 10) || 0; this.node.setDirtyCanvas(true, true); } adjustArrays() { const count = this.node.properties.activeCount; while (this.node.properties.labels.length < count) { this.node.properties.labels.push(`i${this.node.properties.labels.length + 1}`); } this.node.properties.labels.length = count; while (this.node.properties.values.length < count) { this.node.properties.values.push(0); } this.node.properties.values.length = count; } } app.registerExtension({ name: "AIDocsClinicalTools.MultiInt", async beforeRegisterNodeDef(nodeType, nodeData, appInstance) { if (nodeData.name === "MultiInt") { Object.assign(nodeType.prototype, { onNodeCreated() { this.multiIntNode = new MultiIntNode(this); }, onMouseDown(e, pos) { return this.multiIntNode.onMouseDown(e, pos); } }); } } });