=0||over ? "pointer":"crosshair";
+ };
+ canvas.onpointerup = (e) => {
+ if (dragIdx>=0) {
+ e.target.releasePointerCapture(e.pointerId);
+ dragIdx = -1;
+ }
+ };
+ canvas.oncontextmenu = (e) => e.preventDefault();
+
+ // recompute on step‐count change
+ const sw = node.widgets.find((w) => w.name === STEPS_NAME);
+ if (sw) sw.callback = () => applyPoints(strToPts(ta.value));
+
+ // initial draw
+ applyPoints(strToPts(ta.value));
+ new ResizeObserver(() => draw()).observe(canvas);
}
-/**
- * Updates the sigma text area display.
- */
-function updateSigmaDisplay(widgetState) {
- if (!widgetState.sigmaDisplayEl || !widgetState.stepsWidget || !widgetState._points) return;
- const steps = widgetState.stepsWidget.value;
- const sigmas = calculateSigmasJS(widgetState._points, steps);
- widgetState.sigmaDisplayEl.value = sigmas.map((s) => s.toFixed(3)).join(", ");
-}
-
-/**
- * Updates the enabled state of the remove (-) button.
- */
-function updateRemoveButtonState(widgetState) {
- if (!widgetState.removeButtonEl || !widgetState._points) return;
- const canRemove = widgetState._points.length > minGraphPoints;
- widgetState.removeButtonEl.disabled = !canRemove;
- widgetState.removeButtonEl.style.opacity = canRemove ? "1" : "0.5";
- widgetState.removeButtonEl.style.cursor = canRemove ? "pointer" : "not-allowed";
-}
-
-// --- Main Extension Registration ---
+/*—— Registration ——*/
app.registerExtension({
- name: "sigma_graph.widget",
- async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
- if (nodeData.name !== nodeClassName) return;
-
- // Patch onConfigure to set up the custom widget
- const originalOnConfigure = nodeType.prototype.onConfigure;
- nodeType.prototype.onConfigure = function (info) {
- originalOnConfigure?.apply(this, arguments);
- if (this.widgets?.find(w => w.name === customWidgetName)) { return; }
- console.log(`[SigmaGraphWidget] ${this.id}: Patched onConfigure running - setting up widget.`);
-
- const dataWidget = this.widgets.find((w) => w.name === graphDataWidgetName);
- const stepsWidget = this.widgets.find((w) => w.name === stepsWidgetName);
- if (!dataWidget) { console.error(`[SigmaGraphWidget] ${this.id}: Could not find data widget '${graphDataWidgetName}'!`); return; }
- if (!stepsWidget) { console.error(`[SigmaGraphWidget] ${this.id}: Could not find steps widget '${stepsWidgetName}'!`); }
-
- if (dataWidget.inputEl) { dataWidget.inputEl.style.display = "none"; }
- try {
- const widgetIndex = this.widgets.findIndex(w => w === dataWidget);
- if (widgetIndex !== -1 && this.inputs && this.inputs[widgetIndex]) {
- const slotElement = this.graphcanvas?.canvas?.querySelector(`.node_${this.id}_slot_${widgetIndex}`);
- if (slotElement?.previousElementSibling?.tagName === 'LABEL') {
- slotElement.previousElementSibling.style.display = 'none';
- }
- }
- } catch(e) { console.warn("[SigmaGraphWidget] Error trying to hide label:", e); }
-
- // Build the root element for the widget UI
- const rootElement = $el("div", {
- className: "sigma-graph-widget-container",
- style: { width: "100%", height: "100%", padding: "5px", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: "10px" },
- });
-
- // Initialize widget state and store key elements in widgetState object
- const widgetState = {
- _points: [],
- _saveSlots: new Array(numPresetSlots).fill(null),
- _saveSlotButtons: [],
- _recordMode: false,
- _draggingIndex: -1,
- _rightClickIndex: -1,
- rootElement: rootElement,
- node: this,
- dataWidget: dataWidget,
- stepsWidget: stepsWidget,
- sigmaDisplayEl: null,
- canvasContainerEl: null,
- canvasEl: null,
- presetContainerEl: null,
- addButtonEl: null,
- removeButtonEl: null,
- refreshButton: null, // We'll assign the refresh button here
- originalStepsCallback: stepsWidget ? stepsWidget.callback : null,
- saveTimeout: null,
- boundMouseUpHandler: null,
+ name: "TWanSigmaGraph.widget",
+ beforeRegisterNodeDef(nt, nd) {
+ if (nd.name === NODE_CLASS) {
+ const orig = nt.prototype.onConfigure;
+ nt.prototype.onConfigure = function(info) {
+ orig?.apply(this, arguments);
+ setup(this);
};
+ }
+ },
+});
- const saveStateToDataWidget = () => {
- clearTimeout(widgetState.saveTimeout);
- widgetState.saveTimeout = setTimeout(() => {
- widgetState._points.sort((a, b) => a.x - b.x);
- const serialized = JSON.stringify(widgetState._points);
- if (widgetState.dataWidget.value !== serialized) {
- widgetState.dataWidget.value = serialized;
- }
- }, 100);
- };
-
- // --- Build the UI ---
-
- // Sigma display and button grid container
- const sigmaDisplayContainer = $el("div", { style: { display: "flex", flexDirection: "row", gap: "5px", alignItems: "stretch" } });
- const sigmaDisplay = $el("textarea", {
- placeholder: "Sigma values...",
- style: {
- flexGrow: "1",
- minHeight: "5px",
- fontSize: "0.9em",
- background: "#181818",
- color: "#ccc",
- border: "1px solid #555",
- borderRadius: "3px",
- boxSizing: "border-box",
- resize: "auto",
- fontFamily: "monospace",
- padding: "2px 4px",
- }
- });
- widgetState.sigmaDisplayEl = sigmaDisplay;
- sigmaDisplayContainer.appendChild(sigmaDisplay);
-
- const buttonGridContainer = $el("div", { style: { display: "flex", flexDirection: "row", gap: "1px", height: "45px" } });
- sigmaDisplayContainer.appendChild(buttonGridContainer);
- rootElement.appendChild(sigmaDisplayContainer);
-
- // Left column for refresh & advanced buttons
- const leftButtonColumn = $el("div", { style: { display: "flex", flexDirection: "column", alignItems: "stretch", width: "20px", borderRadius: "3px", overflow: "hidden", border: "1px solid #555", background: "#444" } });
- buttonGridContainer.appendChild(leftButtonColumn);
-
- // Create refreshButton and attach it to widgetState.refreshButton
- widgetState.refreshButton = $el("button", {
- textContent: "🔄",
- style: {
- flex: "1 1 auto",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- fontSize: "13px",
- border: "none",
- borderBottom: "1px solid #555",
- background: "transparent",
- color: "#ccc",
- cursor: "pointer",
- lineHeight: "normal",
- padding: "0"
- },
- title: "Refresh text display from current graph/steps"
- });
- leftButtonColumn.appendChild(widgetState.refreshButton);
-
- // Create advancedButton (for demonstration purposes)
- const advancedButton = $el("button", {
- textContent: "⚙️",
- style: {
- flex: "1 1 auto",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- fontSize: "13px",
- border: "none",
- background: "transparent",
- color: "#666",
- cursor: "not-allowed",
- lineHeight: "normal",
- padding: "0"
- },
- title: "Advanced Mode (Not Implemented)"
- });
- leftButtonColumn.appendChild(advancedButton);
-
- // Right column for info & apply buttons
- const rightButtonColumn = $el("div", { style: { display: "flex", flexDirection: "column", alignItems: "stretch", width: "20px", borderRadius: "3px", overflow: "hidden", border: "1px solid #555", background: "#444" } });
- buttonGridContainer.appendChild(rightButtonColumn);
-
- const infoButton = $el("button", {
- textContent: "ℹ",
- style: {
- flex: "1 1 auto",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- fontSize: "13px",
- border: "none",
- borderBottom: "1px solid #555",
- background: "transparent",
- color: "#ccc",
- cursor: "pointer",
- lineHeight: "normal",
- padding: "0"
- },
- title: "Show instructions about this node"
- });
- rightButtonColumn.appendChild(infoButton);
-
- const applyButton = $el("button", {
- textContent: "▼",
- style: {
- flex: "1 1 auto",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- fontSize: "13px",
- border: "none",
- background: "transparent",
- color: "#ccc",
- cursor: "pointer",
- lineHeight: "normal",
- padding: "0"
- },
- title: "Apply sigma values from the text area to the graph points"
- });
- rightButtonColumn.appendChild(applyButton);
-
- // Info overlay element
- const infoOverlay = $el("div", {
- style: {
- position: "absolute",
- top: "55%",
- left: "50%",
- transform: "translate(-50%, -50%)",
- width: "240px",
- background: "#222",
- color: "#ccc",
- border: "1px solid #555",
- borderRadius: "6px",
- padding: "8px 12px",
- fontSize: "11px",
- lineHeight: "1.4",
- textAlign: "center",
- display: "none",
- zIndex: "9999",
- pointerEvents: "none"
- }
- });
- infoOverlay.innerHTML = `
- Sigma Schedule Editor
- Bidirectional editor for sigma schedules.
- 🔄: Refresh text from graph/steps.
- ▼: Apply text to graph points.
- Drag, double-click, right-click to edit points.
- Use +/- on graph to adjust density.
- Special thanks to huchenlei for js help!!
-
`;
- rootElement.appendChild(infoOverlay);
-
- // Canvas container and canvas element
- const canvasContainer = $el("div", {
- style: {
- position: "relative",
- width: "100%",
- flexGrow: "1",
- minHeight: "50px",
- boxSizing: "border-box",
- cursor: "crosshair",
- overflow: "hidden"
- }
- });
- const canvas = $el("canvas", {
- style: {
- display: "block",
- width: "100%",
- height: "100%",
- background: "#282828",
- border: "2px solid #555",
- borderRadius: "3px",
- boxSizing: "border-box"
- }
- });
- widgetState.canvasContainerEl = canvasContainer;
- widgetState.canvasEl = canvas;
- canvasContainer.appendChild(canvas);
- rootElement.appendChild(canvasContainer);
-
- // Add and remove buttons for graph points
- const addButton = $el("button", {
- textContent: "+",
- style: {
- position: "absolute",
- top: "4px",
- right: "30px",
- width: "22px",
- height: "22px",
- fontSize: "16px",
- lineHeight: "20px",
- textAlign: "center",
- padding: "0",
- background: "#4CAF50",
- color: "white",
- border: "1px solid #388E3C",
- borderRadius: "3px",
- cursor: "pointer",
- zIndex: "10"
- },
- title: "Add points (increase density)"
- });
- canvasContainer.appendChild(addButton);
- widgetState.addButtonEl = addButton;
-
- const removeBtn = $el("button", {
- textContent: "-",
- style: {
- position: "absolute",
- top: "4px",
- right: "4px",
- width: "22px",
- height: "22px",
- fontSize: "18px",
- lineHeight: "18px",
- textAlign: "center",
- padding: "0",
- background: "#f44336",
- color: "white",
- border: "1px solid #d32f2f",
- borderRadius: "3px",
- cursor: "pointer",
- zIndex: "10"
- },
- title: "Remove points (decrease density)"
- });
- canvasContainer.appendChild(removeBtn);
- widgetState.removeButtonEl = removeBtn;
-
- // Preset container and save mode button
- const presetContainer = $el("div", {
- style: { display: "inline-flex", flexDirection: "row", alignItems: "center", gap: "5px", marginTop: "0px" }
- });
- widgetState.presetContainerEl = presetContainer;
- rootElement.appendChild(presetContainer);
-
- const saveModeButton = $el("button", {
- textContent: "💾",
- style: {
- width: "30px",
- height: "30px",
- borderRadius: "3px",
- border: "1px solid #555",
- background: "#96322f",
- color: "#fff",
- fontSize: "15px",
- cursor: "pointer",
- padding: "0",
- lineHeight: "30px",
- textAlign: "center"
- },
- title: "Toggle Save Mode (then click a slot to save). Otherwise, click to load."
- });
- presetContainer.appendChild(saveModeButton);
-
- // Load existing preset slots from localStorage
- const savedSlotsJSON = localStorage.getItem("sigma_graph_saveSlots");
- if (savedSlotsJSON) {
- try {
- const savedSlots = JSON.parse(savedSlotsJSON);
- if (Array.isArray(savedSlots) && savedSlots.length === numPresetSlots) {
- widgetState._saveSlots = savedSlots;
- }
- } catch (err) {
- console.error("Error parsing saved slots:", err);
- }
- }
-
- const updateSaveSlotVisibility = () => {
- widgetState._saveSlotButtons.forEach((btn, idx) => {
- if (widgetState._recordMode) {
- btn.style.visibility = "visible";
- btn.style.backgroundColor = "#4caf50";
- } else {
- if (widgetState._saveSlots[idx]) {
- btn.style.visibility = "visible";
- btn.style.backgroundColor = "#273648";
- } else {
- btn.style.visibility = "hidden";
- }
- }
- });
- saveModeButton.style.background = widgetState._recordMode ? "#4caf50" : "#333";
- };
-
- for (let i = 0; i < numPresetSlots; i++) {
- const slotContainer = $el("div", { style: { width: "30px", height: "30px", display: "inline-block" } });
- const slotButton = $el("button", {
- textContent: `${i + 1}`,
- style: { width: "100%", height: "100%", boxSizing: "border-box", padding: "0", borderRadius: "3px", border: "1px solid #555", background: "#333", color: "#fff", fontSize: "15px", cursor: "pointer" },
- title: `Slot #${i + 1}`
- });
- slotButton.addEventListener("click", () => {
- if (widgetState._recordMode) {
- widgetState._saveSlots[i] = JSON.stringify(widgetState._points);
- localStorage.setItem("sigma_graph_saveSlots", JSON.stringify(widgetState._saveSlots));
- widgetState._recordMode = false;
- updateSaveSlotVisibility();
- } else {
- if (widgetState._saveSlots[i]) {
- try {
- const loadedPoints = JSON.parse(widgetState._saveSlots[i]);
- customWidget.value = loadedPoints;
- } catch (e) {
- console.error(`Error parsing slot ${i + 1}:`, e);
- }
- }
- }
- });
- widgetState._saveSlotButtons.push(slotButton);
- slotContainer.appendChild(slotButton);
- presetContainer.appendChild(slotContainer);
- }
- updateSaveSlotVisibility();
- saveModeButton.addEventListener("click", () => {
- widgetState._recordMode = !widgetState._recordMode;
- updateSaveSlotVisibility();
- });
-
- // --- Define Options for addDOMWidget ---
- const widgetOptions = {
- setValue: (value) => {
- let parsedPoints = [];
- try {
- if (typeof value === 'string') {
- parsedPoints = JSON.parse(value || '[]');
- } else if (Array.isArray(value)) {
- parsedPoints = value;
- } else {
- throw new Error("Invalid value type");
- }
- if (!Array.isArray(parsedPoints))
- throw new Error("Parsed value is not an array");
- } catch (e) {
- console.error("[SigmaGraphWidget] setValue error:", e);
- parsedPoints = [ { x: 0, y: 1 }, { x: 1, y: 0 } ];
- }
- if (parsedPoints.length < 1)
- parsedPoints = [ { x: 0, y: 1 }, { x: 1, y: 0 } ];
- if (!parsedPoints.some((p) => Math.abs(p.x - 0.0) < 1e-6))
- parsedPoints.push({ x: 0, y: 1 });
- if (!parsedPoints.some((p) => Math.abs(p.x - 1.0) < 1e-6))
- parsedPoints.push({ x: 1, y: 0 });
- parsedPoints.sort((a, b) => a.x - b.x);
- const uniquePoints = [];
- const seenX = new Set();
- for(const p of parsedPoints) {
- let isDuplicate = false;
- for(const sx of seenX) {
- if (Math.abs(p.x - sx) < 1e-6) { isDuplicate = true; break; }
- }
- if (!isDuplicate) { uniquePoints.push(p); seenX.add(p.x); }
- }
- parsedPoints = uniquePoints;
- while (parsedPoints.length < minGraphPoints) {
- const mid = Math.floor(parsedPoints.length / 2);
- const p1 = parsedPoints[mid-1];
- const p2 = parsedPoints[mid];
- parsedPoints.splice(mid, 0, { x: (p1.x+p2.x)/2, y: (p1.y+p2.y)/2 });
- }
- widgetState._points = parsedPoints;
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- saveStateToDataWidget();
- },
- getValue: () => {
- widgetState._points.sort((a, b) => a.x - b.x);
- return JSON.stringify(widgetState._points);
- },
- afterResize: () => {
- requestAnimationFrame(() => drawCurve(widgetState));
- },
- };
-
- // --- Add the Custom DOM Widget ---
- const customWidget = this.addDOMWidget(customWidgetName, "custom", rootElement, widgetOptions);
- if (!customWidget) {
- console.error(`[SigmaGraphWidget] ${this.id}: Failed to add DOM widget!`);
- return;
- }
-
- // --- Attach Event Listeners ---
- widgetState.refreshButton.addEventListener("click", () => { updateSigmaDisplay(widgetState); });
- advancedButton.addEventListener("click", () => { /* No action */ });
- infoButton.addEventListener("click", () => { infoOverlay.style.display = (infoOverlay.style.display === "none") ? "block" : "none"; });
- applyButton.addEventListener("click", () => {
- const textValue = widgetState.sigmaDisplayEl.value.trim();
- if (!textValue) return;
- const parts = textValue.split(",").map(s => s.trim()).filter(s => s !== "");
- const parsedSigmas = parts.map(Number);
- if (parsedSigmas.some(isNaN) || parsedSigmas.length === 0) return;
- let pointsChanged = false;
- widgetState._points.sort((a, b) => a.x - b.x);
- widgetState._points.forEach((pt) => {
- let targetIndex;
- if (parsedSigmas.length === 1) targetIndex = 0;
- else {
- targetIndex = Math.round(pt.x * (parsedSigmas.length - 1));
- targetIndex = Math.max(0, Math.min(parsedSigmas.length - 1, targetIndex));
- }
- const newY = Math.max(0.001, parsedSigmas[targetIndex]);
- if (pt.y !== newY) { pt.y = newY; pointsChanged = true; }
- });
- if (pointsChanged) {
- drawCurve(widgetState);
- saveStateToDataWidget();
- widgetState.node.setDirtyCanvas(true, true);
- }
- });
- addButton.addEventListener("click", () => {
- const numCurrent = widgetState._points.length;
- const numToAdd = Math.max(1, Math.floor(numCurrent * 0.5));
- widgetState._points = interpolatePoints(widgetState._points, numCurrent + numToAdd);
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- saveStateToDataWidget();
- });
- removeBtn.addEventListener("click", () => {
- const numCurrent = widgetState._points.length;
- if (numCurrent <= minGraphPoints) return;
- const numToRemove = Math.max(1, Math.floor(numCurrent * 0.5));
- let targetNumPoints = Math.max(minGraphPoints, numCurrent - numToRemove);
- const actualNumToRemove = numCurrent - targetNumPoints;
- if (actualNumToRemove <= 0) return;
- const candidates = [];
- const sortedPoints = [...widgetState._points].sort((a, b) => a.x - b.x);
- for (let i = 1; i < sortedPoints.length - 1; i++) {
- const p_prev = sortedPoints[i - 1];
- const p_curr = sortedPoints[i];
- const p_next = sortedPoints[i + 1];
- const x_diff = p_next.x - p_prev.x;
- let interpolatedY;
- if (x_diff <= 1e-6) interpolatedY = p_prev.y;
- else {
- const ratio = (p_curr.x - p_prev.x) / x_diff;
- interpolatedY = p_prev.y + ratio * (p_next.y - p_prev.y);
- }
- candidates.push({ point: p_curr, distance: Math.abs(p_curr.y - interpolatedY) });
- }
- candidates.sort((a, b) => a.distance - b.distance);
- const pointsToRemove = new Set(candidates.slice(0, actualNumToRemove).map(c => c.point));
- widgetState._points = widgetState._points.filter(pt => !pointsToRemove.has(pt));
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- saveStateToDataWidget();
- });
-
- widgetState.boundMouseUpHandler = () => {
- if (widgetState._draggingIndex >= 0) {
- widgetState._draggingIndex = -1;
- if (widgetState.canvasEl) widgetState.canvasEl.style.cursor = "crosshair";
- saveStateToDataWidget();
- }
- };
- document.addEventListener("mouseup", widgetState.boundMouseUpHandler, true);
-
- canvas.addEventListener("mousedown", (evt) => {
- if (evt.target === addButton || evt.target === removeBtn) return;
- evt.stopPropagation();
- const rect = widgetState.canvasEl.getBoundingClientRect();
- const x = evt.clientX - rect.left;
- const y = evt.clientY - rect.top;
- const clickRadiusSq = 64;
- widgetState._draggingIndex = -1;
- widgetState._rightClickIndex = -1;
- let closestDistSq = Infinity;
- let clickedIndex = -1;
- widgetState._points.forEach((pt, i) => {
- const px = pt.x * rect.width;
- const py = (1 - pt.y) * rect.height;
- const distSq = (px - x) ** 2 + (py - y) ** 2;
- if (distSq < clickRadiusSq && distSq < closestDistSq) {
- closestDistSq = distSq;
- clickedIndex = i;
- }
- });
- if (clickedIndex !== -1) {
- if (evt.button === 0) {
- widgetState._draggingIndex = clickedIndex;
- widgetState.canvasEl.style.cursor = "grabbing";
- } else if (evt.button === 2) {
- widgetState._rightClickIndex = clickedIndex;
- }
- }
- });
- canvas.addEventListener("mousemove", (evt) => {
- if (evt.target === addButton || evt.target === removeBtn) return;
- const rect = widgetState.canvasEl.getBoundingClientRect();
- const x = evt.clientX - rect.left;
- const y = evt.clientY - rect.top;
- if (widgetState._draggingIndex >= 0) {
- evt.stopPropagation();
- let nx = x / rect.width;
- let ny = 1 - y / rect.height;
- nx = Math.max(0, Math.min(1, nx));
- ny = Math.max(0, Math.min(1, ny));
- const point = widgetState._points[widgetState._draggingIndex];
- if (point.x === 0 || point.x === 1) nx = point.x;
- widgetState._points[widgetState._draggingIndex] = { x: nx, y: ny };
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- saveStateToDataWidget();
- } else {
- let hovering = false;
- const hoverRadiusSq = 64;
- widgetState._points.forEach((pt) => {
- const px = pt.x * rect.width;
- const py = (1 - pt.y) * rect.height;
- if ((px - x) ** 2 + (py - y) ** 2 < hoverRadiusSq)
- hovering = true;
- });
- widgetState.canvasEl.style.cursor = hovering ? "grab" : "crosshair";
- }
- });
- canvas.addEventListener("dblclick", (evt) => {
- if (evt.target === addButton || evt.target === removeBtn) return;
- evt.stopPropagation();
- const rect = widgetState.canvasEl.getBoundingClientRect();
- let nx = (evt.clientX - rect.left) / rect.width;
- let ny = 1 - (evt.clientY - rect.top) / rect.height;
- nx = Math.max(0, Math.min(1, nx));
- ny = Math.max(0, Math.min(1, ny));
- widgetState._points.push({ x: nx, y: ny });
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- saveStateToDataWidget();
- });
- canvas.addEventListener("contextmenu", (evt) => {
- if (evt.target === addButton || evt.target === removeBtn) { evt.preventDefault(); return; }
- evt.preventDefault();
- evt.stopPropagation();
- if (widgetState._rightClickIndex >= 0) {
- const pt = widgetState._points[widgetState._rightClickIndex];
- if (widgetState._points.length <= minGraphPoints) { /* Show a message if needed */ }
- else if (pt.x > 1e-6 && pt.x < 1.0 - 1e-6) {
- widgetState._points.splice(widgetState._rightClickIndex, 1);
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- saveStateToDataWidget();
- } else { /* Do not remove boundary points */ }
- }
- widgetState._rightClickIndex = -1;
- });
-
- // Steps Widget Hook
- if (stepsWidget) {
- stepsWidget.callback = (value) => {
- if (widgetState.originalStepsCallback) { widgetState.originalStepsCallback.call(stepsWidget, value); }
- updateSigmaDisplay(widgetState);
- };
- }
-
- // Resize Observer for canvas
- widgetState.resizeObserver = new ResizeObserver(() => {
- requestAnimationFrame(() => drawCurve(widgetState));
- });
- widgetState.resizeObserver.observe(canvasContainer);
-
- // --- Initial Setup ---
- requestAnimationFrame(() => {
- console.log(`[SigmaGraphWidget] ${this.id}: Running initial setup in rAF.`);
- if (customWidget?.value !== undefined) {
- customWidget.value = dataWidget.value;
- } else {
- console.error(`[SigmaGraphWidget] ${this.id}: customWidget or customWidget.value missing for initial load.`);
- let initialPoints = [];
- try {
- initialPoints = JSON.parse(dataWidget.value || '[]');
- } catch(e) {
- initialPoints = [{x:0,y:1},{x:1,y:0}];
- }
- widgetState._points = initialPoints;
- drawCurve(widgetState);
- updateSigmaDisplay(widgetState);
- updateRemoveButtonState(widgetState);
- }
- this.setSize(this.computeSize());
- this.setDirtyCanvas(true, true);
- if (this.graph) { this.graph.setDirtyCanvas(true, true); }
- });
-
- // --- Cleanup on Removal ---
- const originalOnRemoved = this.onRemoved;
- this.onRemoved = () => {
- console.log(`[SigmaGraphWidget] ${this.id}: Cleaning up.`);
- if (widgetState.boundMouseUpHandler) { document.removeEventListener("mouseup", widgetState.boundMouseUpHandler, true); }
- if (widgetState.resizeObserver) { widgetState.resizeObserver.disconnect(); widgetState.resizeObserver = null; }
- if (stepsWidget && widgetState.originalStepsCallback) { stepsWidget.callback = widgetState.originalStepsCallback; }
- if (originalOnRemoved) { originalOnRemoved.apply(this, arguments); }
- };
-
- // Mark node dirty after adding the widget
- this.setDirtyCanvas(true, true);
- }; // End of onConfigure patch
-
- // --- Node Appearance ---
- nodeType.prototype.resizable = true;
- const originalComputeSize = nodeType.prototype.computeSize;
- nodeType.prototype.computeSize = function(out) {
- const size = originalComputeSize ? originalComputeSize.apply(this, [null]) : [this.constructor.NODE_WIDTH || 140, 0];
- const customWidgetContentHeight = 225;
- size[0] = Math.max(290, size[0]);
- let baseHeight = this.constructor.title_height || 20;
- if (this.inputs) {
- this.inputs.forEach(input => { if (input.name !== graphDataWidgetName) { baseHeight += 20; } });
- }
- if (this.outputs) { baseHeight += this.outputs.length * 20; }
- size[1] = baseHeight + customWidgetContentHeight;
- size[1] = Math.max(280, size[1]);
- if (out) { out[0] = size[0]; out[1] = size[1]; }
- return size;
- };
- nodeType.prototype.onResize = function() { /* No-op */ };
- }, // End of beforeRegisterNodeDef
-}); // End of app.registerExtension
+if (app.on) {
+ app.on("nodeAdded", (n) => { if (n.type === NODE_CLASS) setup(n); });
+} else {
+ setInterval(() => {
+ (app.graph?._nodes||[]).forEach((n) => {
+ if (n.type === NODE_CLASS) setup(n);
+ });
+ }, 100);
+}