Merge pull request #3032 from node-red/view-annotations

Add RED.view.annotations api
This commit is contained in:
Nick O'Leary 2021-06-29 14:08:08 +01:00 committed by GitHub
commit 5b980e8c13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 59 deletions

View File

@ -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",

View File

@ -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 {

View File

@ -0,0 +1,151 @@
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"
* class: "",
* element: function(node),
* show: string|function(node),
* filter: function(node) -> boolean
* }
*/
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)) {
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
}
})();

View File

@ -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
};
})();

View File

@ -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};