From 5d4c7b9749f477c19b62abe7730ada567013a064 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 11 Mar 2026 17:21:23 +0000 Subject: [PATCH] Update reveal node styling --- .../editor-client/src/js/ui/tab-config.js | 18 +--- .../@node-red/editor-client/src/js/ui/view.js | 85 ++++++++++------- .../editor-client/src/sass/flow.scss | 95 ++++++++++++++++++- .../editor-client/src/sass/tab-config.scss | 9 +- 4 files changed, 152 insertions(+), 55 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js index 22ade35c9..de1602c6e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js @@ -421,24 +421,16 @@ RED.sidebar.config = (function() { //cancel current flashing node before flashing new node clearInterval(flashingConfigNodeTimer); flashingConfigNodeTimer = null; - flashingConfigNode.children("div").removeClass('highlighted'); + flashingConfigNode.children("div").removeClass('red-ui-flow-node-highlighted'); flashingConfigNode = null; } if(!el || !el.children("div").length) { return; } - flashingConfigNodeTimer = setInterval(function(flashEndTime) { - if (flashEndTime >= Date.now()) { - const highlighted = el.children("div").hasClass("highlighted"); - el.children("div").toggleClass('highlighted', !highlighted) - } else { - clearInterval(flashingConfigNodeTimer); - flashingConfigNodeTimer = null; - flashingConfigNode = null; - el.children("div").removeClass('highlighted'); - } - }, 100, Date.now() + 2200); + flashingConfigNodeTimer = setTimeout(function() { + flashingConfigNode.children("div").removeClass('red-ui-flow-node-highlighted'); + }, 8100) flashingConfigNode = el; - el.children("div").addClass('highlighted'); + flashingConfigNode.children("div").addClass('red-ui-flow-node-highlighted'); } function show(id) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index f2180133f..a05c6a440 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3446,6 +3446,9 @@ RED.view = (function() { movingSet.clear(); selectedLinks.clear(); selectedGroups.clear(); + $(".red-ui-flow-node-highlighted").removeClass("red-ui-flow-node-highlighted"); + $(".red-ui-flow-group-highlighted").removeClass("red-ui-flow-group-highlighted"); + cancelFlash(); } var lastSelection = null; @@ -5220,6 +5223,7 @@ RED.view = (function() { // - node is disabled if (!showStatus || !d.status || d.d === true) { nodeEl.__statusGroup__.style.display = "none"; + d.statusHeight = 0; } else { nodeEl.__statusGroup__.style.display = "inline"; let backgroundWidth = 15 @@ -5246,6 +5250,7 @@ RED.view = (function() { if (backgroundWidth > 0 && textSize.width > 0) { backgroundWidth += 6 } + d.statusHeight = nodeEl.__statusGroup__.getBBox().height nodeEl.__statusBackground__.setAttribute('width', backgroundWidth) } delete d.dirtyStatus; @@ -5290,7 +5295,7 @@ RED.view = (function() { .on("touchstart",nodeTouchStart) .on("touchend",nodeTouchEnd) nodeContents.appendChild(mainRect); - + const port_label_group = document.createElementNS("http://www.w3.org/2000/svg","g"); port_label_group.setAttribute("x",0); port_label_group.setAttribute("y",0); @@ -5704,6 +5709,15 @@ RED.view = (function() { nodeContents.appendChild(statusEl); + const nodeHalo = document.createElementNS("http://www.w3.org/2000/svg","rect"); + nodeHalo.setAttribute("class", "red-ui-flow-node-highlight"); + nodeHalo.setAttribute("rx", 5); + nodeHalo.setAttribute("ry", 5); + nodeHalo.setAttribute("x", -10) + nodeHalo.setAttribute("y", -10) + node[0][0].__halo__ = nodeHalo; + nodeContents.appendChild(nodeHalo); + node[0][0].appendChild(nodeContents); if (!d.__ghost) { @@ -5771,11 +5785,15 @@ RED.view = (function() { // This might be the first redraw after a node has been click-dragged to start a move. // So its selected state might have changed since the last redraw. this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + this.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); if (mouse_mode != RED.state.MOVING_ACTIVE) { this.classList.toggle("red-ui-flow-node-disabled", d.d === true); this.__mainRect__.setAttribute("width", d.w) this.__mainRect__.setAttribute("height", d.h) - this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + this.__halo__.setAttribute("width", d.w + 20) + this.__halo__.setAttribute("height", d.h + 20 + (d.statusHeight || 0)) + if (labelParts) { // The label has changed @@ -5996,6 +6014,7 @@ RED.view = (function() { }); if (d._def.button) { + let buttonVisible = true var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); if (RED.runtime && RED.runtime.started !== undefined) { @@ -6016,11 +6035,18 @@ RED.view = (function() { if (typeof d._def.button.visible === "function") { // is defined and a function... if (d._def.button.visible.call(d) === false) { this.__buttonGroup__.style.display = "none"; - } - else { + buttonVisible = false + } else { this.__buttonGroup__.style.display = "inherit"; } } + // Need to adjust the halo to encompass the button + if (buttonVisible) { + this.__halo__.setAttribute("width", d.w + 40) + if (d._def.align !== 'right') { + this.__halo__.setAttribute("x", -30) + } + } } // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); // thisNode.selectAll("text.node_badge_label").text(function(d,i) { @@ -6571,6 +6597,11 @@ RED.view = (function() { } else { selectGroup.classList.remove("red-ui-flow-group-selected") } + if (d.highlighted) { + selectGroup.classList.add("red-ui-flow-group-highlighted") + } else { + selectGroup.classList.remove("red-ui-flow-group-highlighted") + } var selectGroupRect = selectGroup.children[0]; // Background selectGroupRect.setAttribute("width",d.w+6) @@ -6579,19 +6610,12 @@ RED.view = (function() { selectGroupRect = selectGroup.children[1]; selectGroupRect.setAttribute("width",d.w+6) selectGroupRect.setAttribute("height",d.h+6) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + // selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; // Line selectGroupRect = selectGroup.children[2]; selectGroupRect.setAttribute("width",d.w+6) selectGroupRect.setAttribute("height",d.h+6) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; - - if (d.highlighted) { - selectGroup.classList.add("red-ui-flow-node-highlighted"); - } else { - selectGroup.classList.remove("red-ui-flow-node-highlighted"); - } - + // selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; g.selectAll(".red-ui-flow-group-body") .attr("width",d.w) @@ -7304,33 +7328,28 @@ RED.view = (function() { return result; } - - function flashNode(n) { - let node = n; - if(typeof node === "string") { node = RED.nodes.node(n); } - if(!node) { return; } - - const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId); - if(flashingNode) { - //cancel current flashing node before flashing new node - clearInterval(flashingNode.__flashTimer); + function cancelFlash() { + const flashingNode = flashingNodeId && (RED.nodes.node(flashingNodeId) || RED.nodes.group(flashingNodeId)); + if (flashingNode) { + clearTimeout(flashingNode.__flashTimer); delete flashingNode.__flashTimer; flashingNode.dirty = true; flashingNode.highlighted = false; + _redraw() } - node.__flashTimer = setInterval(function(flashEndTime, n) { + } + function flashNode(n) { + let node = n; + if (typeof node === "string") { node = RED.nodes.node(n); } + if (!node) { return; } + cancelFlash() + node.__flashTimer = setTimeout(function(n) { n.dirty = true; - if (flashEndTime >= Date.now()) { - n.highlighted = !n.highlighted; - } else { - clearInterval(n.__flashTimer); - delete n.__flashTimer; - flashingNodeId = null; - n.highlighted = false; - } + n.highlighted = false; RED.view.redraw(); - }, 100, Date.now() + 2200, node) + }, 8100, node) flashingNodeId = node.id; + node.dirty = true; node.highlighted = true; RED.view.redraw(); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index 944536845..90a94e5fa 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -135,6 +135,25 @@ } } +.red-ui-flow-group.red-ui-flow-group-selected { + .red-ui-flow-group-outline-select-outline, + .red-ui-flow-group-outline-select-line { + stroke-opacity: 0.8;// !important; + } +} + +.red-ui-flow-group.red-ui-flow-group-highlighted { + .red-ui-flow-group-outline-select-line { + stroke-width: 4; + stroke-opacity: 1; + stroke-dasharray: 8, 4; + animation-duration: 8s; + animation-name: node-reveal-highlight; + animation-timing-function: ease-out; + animation-iteration-count: 1; + } +} + svg:not(.red-ui-workspace-lasso-active) { .red-ui-flow-group:not(.red-ui-flow-group-selected) { .red-ui-flow-group-outline-select.red-ui-flow-group-outline-select-background:hover { @@ -273,17 +292,83 @@ g.red-ui-flow-node-selected { stroke: var(--red-ui-node-selected-color) !important; } } -.red-ui-flow-node-highlighted { + +.red-ui-flow-node-highlight { border-color: var(--red-ui-node-selected-color) !important; border-style: dashed !important; stroke: var(--red-ui-node-selected-color); - stroke-width: 3; + stroke-width: 4; + stroke-opacity: 0; stroke-dasharray: 8, 4; + fill: none; + pointer-events: none; + .red-ui-flow-node-highlighted & { + animation-duration: 8s; + animation-name: node-reveal-highlight; + animation-timing-function: ease-out; + animation-iteration-count: 1; + } } - -.red-ui-flow-subflow .red-ui-flow-node { +@keyframes node-reveal-highlight { + from { + stroke-opacity: 1; + } + 2% { stroke-opacity: 1;} + 3% { stroke-opacity: 0 ;} + 4% { stroke-opacity: 1 ;} + 5% { stroke-opacity: 0 ;} + 6% { stroke-opacity: 1 ;} + 7% { stroke-opacity: 0 ;} + 8% { stroke-opacity: 1 ;} + 9% { stroke-opacity: 0 ;} + 10% { stroke-opacity: 1 ;} + 11% { stroke-opacity: 0 ;} + 12% { stroke-opacity: 1;} + 13% { stroke-opacity: 0 ;} + 14% { stroke-opacity: 1 ;} + 15% { stroke-opacity: 0 ;} + 16% { stroke-opacity: 1 ;} + 80% { + stroke-opacity: 1; + } + to { + stroke-opacity: 0; + } +} +.red-ui-palette-node.red-ui-flow-node-highlighted { + border-color: transparent; + outline: dashed var(--red-ui-node-selected-color) 4px; + animation-duration: 8s; + animation-name: config-node-reveal-highlight; + animation-timing-function: ease-out; + animation-iteration-count: 1; +} +@keyframes config-node-reveal-highlight { + from { + outline-color: var(--red-ui-node-selected-color); + } + 2% { outline-color: var(--red-ui-node-selected-color);} + 3% { outline-color: transparent ;} + 4% { outline-color: var(--red-ui-node-selected-color) ;} + 5% { outline-color: transparent ;} + 6% { outline-color: var(--red-ui-node-selected-color) ;} + 7% { outline-color: transparent ;} + 8% { outline-color: var(--red-ui-node-selected-color) ;} + 9% { outline-color: transparent ;} + 10% { outline-color: var(--red-ui-node-selected-color) ;} + 11% { outline-color: transparent ;} + 12% { outline-color: var(--red-ui-node-selected-color) ;} + 13% { outline-color: transparent ;} + 14% { outline-color: var(--red-ui-node-selected-color) ;} + 15% { outline-color: transparent ;} + 16% { outline-color: var(--red-ui-node-selected-color) ;} + 80% { + outline-color: var(--red-ui-node-selected-color) ; + } + to { + outline-color: transparent ; + } } - .red-ui-workspace-disabled { .red-ui-flow-node { stroke-dasharray: 8, 3; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 4592998c3..e98a3fb30 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -62,10 +62,11 @@ ul.red-ui-sidebar-node-config-list { border-color: transparent; box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); } - &.highlighted { - border-color: transparent; - outline: dashed var(--red-ui-node-selected-color) 4px; - } + // Highlighted state handled in flow.scss + // &.red-ui-flow-node-highlighted { + // border-color: transparent; + // outline: dashed var(--red-ui-node-selected-color) 4px; + // } } .red-ui-palette-label { margin-left: 8px;