From 844bf29de1018c65e2f8708d72afb98308f6156a Mon Sep 17 00:00:00 2001 From: Nick O'Leary <nick.oleary@gmail.com> Date: Thu, 24 Jun 2021 17:40:26 +0100 Subject: [PATCH 1/2] Add RED.view.annotations api --- Gruntfile.js | 1 + .../editor-client/src/js/ui/common/popover.js | 4 - .../src/js/ui/view-annotations.js | 148 ++++++++++++++++++ .../@node-red/editor-client/src/js/ui/view.js | 101 ++++++------ .../editor-client/src/sass/variables.scss | 5 + 5 files changed, 200 insertions(+), 59 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js diff --git a/Gruntfile.js b/Gruntfile.js index 5d9b8ba5c..df9939a8c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -170,6 +170,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js", "packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js", "packages/node_modules/@node-red/editor-client/src/js/ui/view.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js", "packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js", "packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js", "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js index 7049c3164..8d7553069 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js @@ -308,11 +308,9 @@ RED.popover = (function() { // DOWN if (currentItem.length > 0) { if (currentItem.index() === menuOptions.length-1) { - console.log("WARP TO TOP") // Wrap to top of list list.children().first().children().first().focus(); } else { - console.log("GO DOWN ONE") currentItem.next().children().first().focus(); } } else { @@ -323,11 +321,9 @@ RED.popover = (function() { // UP if (currentItem.length > 0) { if (currentItem.index() === 0) { - console.log("WARP TO BOTTOM") // Wrap to bottom of list list.children().last().children().first().focus(); } else { - console.log("GO UP ONE") currentItem.prev().children().first().focus(); } } else { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js new file mode 100644 index 000000000..e3a6a1114 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js @@ -0,0 +1,148 @@ +RED.view.annotations = (function() { + + var annotations = {}; + + function init() { + RED.hooks.add("viewRedrawNode.annotations", function(evt) { + try { + if (evt.node.__pendingAnnotation__) { + addAnnotation(evt.node.__pendingAnnotation__,evt); + delete evt.node.__pendingAnnotation__; + } + var badgeDX = 0; + var controlDX = 0; + for (var i=0,l=evt.el.__annotations__.length;i<l;i++) { + var annotation = evt.el.__annotations__[i]; + if (annotations.hasOwnProperty(annotation.id)) { + var opts = annotations[annotation.id]; + var showAnnotation = true; + var isBadge = opts.type === 'badge'; + if (opts.show !== undefined) { + if (typeof opts.show === "string") { + showAnnotation = !!evt.node[opts.show] + } else if (typeof opts.show === "function"){ + showAnnotation = opts.show(evt.node) + } else { + showAnnotation = !!opts.show; + } + annotation.element.classList.toggle("hide", !showAnnotation); + } + if (isBadge) { + if (showAnnotation) { + var rect = annotation.element.getBoundingClientRect(); + badgeDX += rect.width; + annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)"); + badgeDX += 4; + } + } else { + if (showAnnotation) { + var rect = annotation.element.getBoundingClientRect(); + annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)"); + controlDX += rect.width + 4; + } + } + } else { + annotation.element.parentNode.removeChild(annotation.element); + evt.el.__annotations__.splice(i,1); + i--; + l--; + } + } + }catch(err) { + console.log(err) + } + }); + } + + + /** + * Register a new node annotation + * @param {string} id - unique identifier + * @param {type} opts - annotations options + * + * opts: { + * type: "badge"|"annotation" + * class: "", + * element: function(node), + * show: string|function(node), + * filter: function(node) -> boolean + * } + */ + function register(id, opts) { + annotations[id] = opts + RED.hooks.add("viewAddNode.annotation-"+id, function(evt) { + if (opts.filter && !opts.filter(evt.node)) { + return; + } + addAnnotation(id,evt); + }); + + var nodes = RED.view.getActiveNodes(); + nodes.forEach(function(n) { + n.__pendingAnnotation__ = id; + }) + RED.view.redraw(); + + } + + function addAnnotation(id,evt) { + var opts = annotations[id]; + evt.el.__annotations__ = evt.el.__annotations__ || []; + var annotationGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + annotationGroup.setAttribute("class",opts.class || ""); + evt.el.__annotations__.push({ + id:id, + element: annotationGroup + }); + var annotation = opts.element(evt.node); + if (opts.tooltip) { + annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip)); + annotation.addEventListener("mouseleave", annotationMouseLeave); + } + annotationGroup.appendChild(annotation); + evt.el.appendChild(annotationGroup); + } + + + function unregister(id) { + delete annotations[id] + RED.hooks.remove("*.annotation-"+id); + RED.view.redraw(); + } + + var badgeHoverTimeout; + var badgeHover; + function getAnnotationMouseEnter(annotation,node,tooltip) { + return function() { + var text = typeof tooltip === "function"?tooltip(node):tooltip; + if (text) { + clearTimeout(badgeHoverTimeout); + badgeHoverTimeout = setTimeout(function() { + var pos = RED.view.getElementPosition(annotation); + var rect = annotation.getBoundingClientRect(); + badgeHoverTimeout = null; + badgeHover = RED.view.showTooltip( + (pos[0]+rect.width/2), + (pos[1]), + text, + "top" + ); + },500); + } + } + } + function annotationMouseLeave() { + clearTimeout(badgeHoverTimeout); + if (badgeHover) { + badgeHover.remove(); + badgeHover = null; + } + } + + return { + init: init, + register:register, + unregister:unregister + } + +})(); 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 a30af74b4..dc8041d8d 100755 --- 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 @@ -546,10 +546,44 @@ RED.view = (function() { } }); + RED.view.annotations.init(); RED.view.navigator.init(); RED.view.tools.init(); + + + RED.view.annotations.register("red-ui-flow-node-changed",{ + type: "badge", + class: "red-ui-flow-node-changed", + element: function() { + var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); + changeBadge.setAttribute("cx",5); + changeBadge.setAttribute("cy",5); + changeBadge.setAttribute("r",5); + return changeBadge; + }, + show: function(n) { return n.changed||n.moved } + }) + + RED.view.annotations.register("red-ui-flow-node-error",{ + type: "badge", + class: "red-ui-flow-node-error", + element: function(d) { + var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); + errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z"); + return errorBadge + }, + tooltip: function(d) { + if (d.validationErrors && d.validationErrors.length > 0) { + return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ") + } + }, + show: function(n) { return !n.valid } + }) + } + + function updateGrid() { var gridTicks = []; for (var i=0;i<space_width;i+=+gridSize) { @@ -3597,31 +3631,6 @@ RED.view = (function() { } } - function errorBadgeMouseEnter(e) { - var d = this.__data__; - if (d.validationErrors && d.validationErrors.length > 0) { - clearTimeout(portLabelHoverTimeout); - var node = this; - portLabelHoverTimeout = setTimeout(function() { - var pos = getElementPosition(node); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0]), - (pos[1]), - RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - "), - "top" - ); - },500); - } - } - function errorBadgeMouseLeave() { - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - } - function redrawStatus(d,nodeEl) { if (d.z !== RED.workspaces.active()) { return; @@ -3938,31 +3947,11 @@ RED.view = (function() { nodeContents.appendChild(statusEl); - - var changeBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); - changeBadgeG.setAttribute("class","red-ui-flow-node-changed hide"); - changeBadgeG.setAttribute("transform","translate(20, -2)"); - node[0][0].__changeBadge__ = changeBadgeG; - var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); - changeBadge.setAttribute("r",5); - changeBadgeG.appendChild(changeBadge); - nodeContents.appendChild(changeBadgeG); - - - var errorBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); - errorBadgeG.setAttribute("class","red-ui-flow-node-error hide"); - errorBadgeG.setAttribute("transform","translate(0, -2)"); - node[0][0].__errorBadge__ = errorBadgeG; - var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); - errorBadge.setAttribute("d","M -5,4 l 10,0 -5,-8 z"); - errorBadgeG.appendChild(errorBadge); - errorBadge.__data__ = d; - errorBadge.addEventListener("mouseenter", errorBadgeMouseEnter); - errorBadge.addEventListener("mouseleave", errorBadgeMouseLeave); - nodeContents.appendChild(errorBadgeG); - node[0][0].appendChild(nodeContents); + + RED.hooks.trigger("viewAddNode",{node:d,el:this}) }); + node.each(function(d,i) { if (d.dirty) { var self = this; @@ -4202,10 +4191,10 @@ RED.view = (function() { ); faIcon.attr("y",(d.h+13)/2); } - this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); - this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); - this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); - this.__errorBadge__.classList.toggle("hide", d.valid); + // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); + // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); + // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); + // this.__errorBadge__.classList.toggle("hide", d.valid); thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { var port = d3.select(this); @@ -4254,8 +4243,6 @@ RED.view = (function() { // }); } - RED.hooks.trigger("viewAddNode",{node:d,el:this}) - if (d.dirtyStatus) { redrawStatus(d,this); } @@ -4270,6 +4257,8 @@ RED.view = (function() { } } } + + RED.hooks.trigger("viewRedrawNode",{node:d,el:this}) }); var link = linkLayer.selectAll(".red-ui-flow-link").data( activeLinks, @@ -5300,6 +5289,8 @@ RED.view = (function() { }, redrawStatus: redrawStatus, showQuickAddDialog:showQuickAddDialog, - calculateNodeDimensions: calculateNodeDimensions + calculateNodeDimensions: calculateNodeDimensions, + getElementPosition:getElementPosition, + showTooltip:showTooltip }; })(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index 63a4b1d14..72b573a2e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -81,6 +81,11 @@ --red-ui-node-status-changed-border: #{$node-status-changed-border}; --red-ui-node-status-changed-background: #{$node-status-changed-background}; + + + --red-ui-node-border: #{$node-border}; + --red-ui-node-port-background:#{$node-port-background}; + --red-ui-node-label-color: #{$node-label-color}; --red-ui-node-selected-color: #{$node-selected-color}; --red-ui-port-selected-color: #{$port-selected-color}; From 3255e11cfcf472df693d58cd2de996ef50c57cd0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary <nick.oleary@gmail.com> Date: Thu, 24 Jun 2021 17:59:32 +0100 Subject: [PATCH 2/2] Limit annotations to badge type --- .../@node-red/editor-client/src/js/ui/view-annotations.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js index e3a6a1114..1df8c4bd1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js @@ -61,7 +61,7 @@ RED.view.annotations = (function() { * @param {type} opts - annotations options * * opts: { - * type: "badge"|"annotation" + * type: "badge" * class: "", * element: function(node), * show: string|function(node), @@ -69,6 +69,9 @@ RED.view.annotations = (function() { * } */ function register(id, opts) { + if (opts.type !== 'badge') { + throw new Error("Unsupported annotation type: "+opts.type); + } annotations[id] = opts RED.hooks.add("viewAddNode.annotation-"+id, function(evt) { if (opts.filter && !opts.filter(evt.node)) {