From 766ccf85c25fb135a6804fba1e76d3f82f0d19f7 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 20 Jan 2019 14:43:17 +0000 Subject: [PATCH 1/4] add fast entry via keyboard for string of nodes --- .../editor-client/src/js/ui/typeSearch.js | 16 +++++++++++++--- .../@node-red/editor-client/src/js/ui/view.js | 16 +++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 532d84274..a14986f83 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -48,7 +48,7 @@ RED.typeSearch = (function() { //shade = $('
',{class:"red-ui-type-search-shade"}).appendTo("#main-container"); dialog = $("
",{id:"red-ui-type-search",class:"red-ui-search red-ui-type-search"}).appendTo("#main-container"); var searchDiv = $("
",{class:"red-ui-search-container"}).appendTo(dialog); - searchInput = $('').attr("placeholder",RED._("search.addNode")).appendTo(searchDiv).searchBox({ + searchInput = $('').attr("placeholder",RED._("search.addNode")).appendTo(searchDiv).searchBox({ delay: 50, change: function() { search($(this).val()); @@ -79,6 +79,17 @@ RED.typeSearch = (function() { $(children[selected]).addClass('selected'); ensureSelectedIsVisible(); evt.preventDefault(); + } else if ((evt.metaKey || evt.ctrlKey) && evt.keyCode === 13 ) { + // (ctrl or cmd) and enter + var index = Math.max(0,selected); + if (index < children.length) { + var n = $(children[index]).find(".red-ui-editableList-item-content").data('data'); + typesUsed[n.type] = Date.now(); + if (n.def.outputs === 0) { confirm(n); } + else { addCallback(n.type,true); } + $("#red-ui-type-search-input").val(""); + } + evt.preventDefault(); } else if (evt.keyCode === 13) { // Enter var index = Math.max(0,selected); @@ -202,7 +213,7 @@ RED.typeSearch = (function() { } refreshTypeList(opts); addCallback = opts.add; - closeCallback = opts.close; + cancelCallback = opts.cancel; RED.events.emit("type-search:open"); //shade.show(); dialog.css({left:opts.x+"px",top:opts.y+"px"}).show(); @@ -230,7 +241,6 @@ RED.typeSearch = (function() { $(document).off('click.type-search'); } } - function getTypeLabel(type, def) { var label = type; if (typeof def.paletteLabel !== "undefined") { 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 6c8951b34..c02cce13f 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 @@ -650,10 +650,8 @@ RED.view = (function() { mouse_mode = RED.state.PANNING; mouse_position = [d3.event.pageX,d3.event.pageY] scroll_position = [chart.scrollLeft(),chart.scrollTop()]; - return; } - if (!mousedown_node && !mousedown_link) { selected_link = null; updateSelection(); @@ -692,17 +690,21 @@ RED.view = (function() { cancel: function() { quickAddActive = false; resetMouseVars(); + updateSelection(); + hideDragLines(); + redraw(); }, - add: function(type) { + add: function(type,auto) { quickAddActive = false; + if (auto === true) { mouse_mode = RED.state.QUICK_JOINING; } var result = addNode(type); if (!result) { return; } var nn = result.node; var historyEvent = result.historyEvent; - nn.x = point[0]; - nn.y = point[1]; + nn.x = Math.ceil((point[0] - 50) / gridSize) * gridSize + 50; + nn.y = parseInt(point[1] / gridSize) * gridSize; var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) { @@ -796,6 +798,7 @@ RED.view = (function() { updateActiveNodes(); updateSelection(); redraw(); + if (auto === true) { point[0] = point[0] + ((nn.w < 100) ? 100 : nn.w) + gridSize * 2; } } }); @@ -850,7 +853,6 @@ RED.view = (function() { mouse_position = d3.touches(this)[0]||d3.mouse(this); - if (lasso) { var ox = parseInt(lasso.attr("ox")); var oy = parseInt(lasso.attr("oy")); @@ -1139,7 +1141,7 @@ RED.view = (function() { updateSelection(); lasso.remove(); lasso = null; - } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey&& !d3.event.metaKey ) { + } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { clearSelection(); updateSelection(); } From 1d1ab5b7b27fd83110bcf6576dd1f367ca1cb906 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 21 Jan 2019 16:15:38 +0000 Subject: [PATCH 2/4] don't pin new nodes to grid if not using grid --- .../@node-red/editor-client/src/js/ui/view.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 c02cce13f..ac2d1dcc6 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 @@ -703,8 +703,14 @@ RED.view = (function() { } var nn = result.node; var historyEvent = result.historyEvent; - nn.x = Math.ceil((point[0] - 50) / gridSize) * gridSize + 50; - nn.y = parseInt(point[1] / gridSize) * gridSize; + if (RED.settings.get("editor").view['view-snap-grid']) { + nn.x = Math.ceil((point[0] - 50) / gridSize) * gridSize + 50; + nn.y = parseInt(point[1] / gridSize) * gridSize; + } + else { + nn.x = point[0]; + nn.y = point[1]; + } var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) { From e2ee88de84b053033598c17ba3d2769faf09d1b6 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 21 Jan 2019 22:08:25 +0000 Subject: [PATCH 3/4] offset menu so you can see node placement --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ac2d1dcc6..c4d603616 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 @@ -685,7 +685,7 @@ RED.view = (function() { quickAddActive = true; RED.typeSearch.show({ x:d3.event.clientX-mainPos.left-node_width/2, - y:d3.event.clientY-mainPos.top-node_height/2, + y:d3.event.clientY-mainPos.top-node_height/2 + 30, filter: filter, cancel: function() { quickAddActive = false; From aa9a37da383f08879e6b7366739ddad0c8f21f8f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Jan 2019 16:27:13 +0000 Subject: [PATCH 4/4] Add placeholder node when in quick-add mode --- .../editor-client/src/js/ui/typeSearch.js | 30 +- .../@node-red/editor-client/src/js/ui/view.js | 278 ++++++++++++------ .../editor-client/src/sass/flow.scss | 8 + 3 files changed, 224 insertions(+), 92 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index a14986f83..1b608c91c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -80,16 +80,18 @@ RED.typeSearch = (function() { ensureSelectedIsVisible(); evt.preventDefault(); } else if ((evt.metaKey || evt.ctrlKey) && evt.keyCode === 13 ) { - // (ctrl or cmd) and enter + // (ctrl or cmd) and enter var index = Math.max(0,selected); if (index < children.length) { var n = $(children[index]).find(".red-ui-editableList-item-content").data('data'); typesUsed[n.type] = Date.now(); - if (n.def.outputs === 0) { confirm(n); } - else { addCallback(n.type,true); } - $("#red-ui-type-search-input").val(""); + if (n.def.outputs === 0) { + confirm(n); + } else { + addCallback(n.type,true); + } + $("#red-ui-type-search-input").val("").keyup(); } - evt.preventDefault(); } else if (evt.keyCode === 13) { // Enter var index = Math.max(0,selected); @@ -202,20 +204,27 @@ RED.typeSearch = (function() { createDialog(); } visible = true; - setTimeout(function() { - $(document).on('mousedown.type-search',handleMouseActivity); - $(document).on('mouseup.type-search',handleMouseActivity); - $(document).on('click.type-search',handleMouseActivity); - },200); } else { dialog.hide(); searchResultsDiv.hide(); } + $(document).off('mousedown.type-search'); + $(document).off('mouseup.type-search'); + $(document).off('click.type-search'); + setTimeout(function() { + $(document).on('mousedown.type-search',handleMouseActivity); + $(document).on('mouseup.type-search',handleMouseActivity); + $(document).on('click.type-search',handleMouseActivity); + },200); + refreshTypeList(opts); addCallback = opts.add; cancelCallback = opts.cancel; RED.events.emit("type-search:open"); //shade.show(); + if ($("#main-container").height() - opts.y - 150 < 0) { + opts.y = opts.y - 235; + } dialog.css({left:opts.x+"px",top:opts.y+"px"}).show(); searchResultsDiv.slideDown(300); setTimeout(function() { @@ -344,6 +353,7 @@ RED.typeSearch = (function() { return { show: show, + refresh: refreshTypeList, hide: hide }; 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 c4d603616..9b6415624 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 @@ -55,6 +55,7 @@ RED.view = (function() { mouse_mode = 0, moving_set = [], lasso = null, + ghostNode = null, showStatus = false, lastClickNode = null, dblClickPrimed = null, @@ -665,6 +666,16 @@ RED.view = (function() { if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if (d3.event.metaKey || d3.event.ctrlKey) { point = d3.mouse(this); + var ox = point[0]; + var oy = point[1]; + + if (RED.settings.get("editor").view['view-snap-grid']) { + // vis.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') + point[0] = Math.round(point[0] / gridSize) * gridSize; + point[1] = Math.round(point[1] / gridSize) * gridSize; + // vis.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue') + } + d3.event.stopPropagation(); var mainPos = $("#main-container").position(); @@ -672,6 +683,24 @@ RED.view = (function() { mouse_mode = RED.state.QUICK_JOINING; $(window).on('keyup',disableQuickJoinEventHandler); } + quickAddActive = true; + + if (ghostNode) { + ghostNode.remove(); + } + ghostNode = vis.append("g").attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + ghostNode.append("rect") + .attr("class","node_placeholder") + .attr("rx", 5) + .attr("ry", 5) + .attr("width",node_width) + .attr("height",node_height) + .attr("fill","none") + // var ghostLink = ghostNode.append("svg:path") + // .attr("class","link_link") + // .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2)) + // .attr("opacity",0); + var filter = undefined; if (drag_lines.length > 0) { if (drag_lines[0].virtualLink) { @@ -681,116 +710,176 @@ RED.view = (function() { } else { filter = {output:true} } + + quickAddLink = { + node: drag_lines[0].node, + port: drag_lines[0].port, + portType: drag_lines[0].portType, + } + if (drag_lines[0].virtualLink) { + quickAddLink.virtualLink = true; + } + hideDragLines(); } - quickAddActive = true; + var rebuildQuickAddLink = function() { + if (!quickAddLink) { + return; + } + if (!quickAddLink.el) { + quickAddLink.el = dragGroup.append("svg:path").attr("class", "drag_line"); + } + var numOutputs = (quickAddLink.portType === PORT_TYPE_OUTPUT)?(quickAddLink.node.outputs || 1):1; + var sourcePort = quickAddLink.port; + var portY = -((numOutputs-1)/2)*13 +13*sourcePort; + var sc = (quickAddLink.portType === PORT_TYPE_OUTPUT)?1:-1; + quickAddLink.el.attr("d",generateLinkPath(quickAddLink.node.x+sc*quickAddLink.node.w/2,quickAddLink.node.y+portY,point[0]-sc*node_width/2,point[1],sc)); + } + if (quickAddLink) { + rebuildQuickAddLink(); + } + + + var lastAddedX; + var lastAddedWidth; + RED.typeSearch.show({ - x:d3.event.clientX-mainPos.left-node_width/2, - y:d3.event.clientY-mainPos.top-node_height/2 + 30, + x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), + y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), filter: filter, cancel: function() { + if (quickAddLink) { + if (quickAddLink.el) { + quickAddLink.el.remove(); + } + quickAddLink = null; + } quickAddActive = false; + if (ghostNode) { + ghostNode.remove(); + } resetMouseVars(); updateSelection(); hideDragLines(); redraw(); }, - add: function(type,auto) { - quickAddActive = false; - if (auto === true) { mouse_mode = RED.state.QUICK_JOINING; } + add: function(type,keepAdding) { var result = addNode(type); if (!result) { return; } - var nn = result.node; - var historyEvent = result.historyEvent; - if (RED.settings.get("editor").view['view-snap-grid']) { - nn.x = Math.ceil((point[0] - 50) / gridSize) * gridSize + 50; - nn.y = parseInt(point[1] / gridSize) * gridSize; - } - else { - nn.x = point[0]; - nn.y = point[1]; + if (keepAdding) { + mouse_mode = RED.state.QUICK_JOINING; } + var nn = result.node; + var historyEvent = result.historyEvent; + nn.x = point[0]; + nn.y = point[1]; var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) { nn.l = showLabel; } + if (quickAddLink) { + var drag_line = quickAddLink; + var src = null,dst,src_port; + if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { + src = drag_line.node; + src_port = drag_line.port; + dst = nn; + } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { + src = nn; + dst = drag_line.node; + src_port = 0; + } - if (mouse_mode === RED.state.QUICK_JOINING || quickAddLink) { - if (quickAddLink || drag_lines.length > 0) { - var drag_line = quickAddLink||drag_lines[0]; - var src = null,dst,src_port; - if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { - src = drag_line.node; - src_port = drag_line.port; - dst = nn; - } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { - src = nn; - dst = drag_line.node; - src_port = 0; - } - - if (src !== null) { - if (drag_line.virtualLink) { - historyEvent = { - t:'multi', - events: [historyEvent] - } - var oldSrcLinks = $.extend(true,{},{v:src.links}).v - var oldDstLinks = $.extend(true,{},{v:dst.links}).v - src.links.push(dst.id); - dst.links.push(src.id); - src.dirty = true; - dst.dirty = true; - - historyEvent.events.push({ - t:'edit', - node: src, - dirty: RED.nodes.dirty(), - changed: src.changed, - changes: { - links:oldSrcLinks - } - }); - historyEvent.events.push({ - t:'edit', - node: dst, - dirty: RED.nodes.dirty(), - changed: dst.changed, - changes: { - links:oldDstLinks - } - }); - src.changed = true; - dst.changed = true; - } else { - var link = {source: src, sourcePort:src_port, target: dst}; - RED.nodes.addLink(link); - historyEvent.links = [link]; + if (src !== null) { + // Joining link nodes via virual wires. Need to update + // the src and dst links property + if (drag_line.virtualLink) { + historyEvent = { + t:'multi', + events: [historyEvent] } - hideDragLines(); - if (!quickAddLink && drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { + var oldSrcLinks = $.extend(true,{},{v:src.links}).v + var oldDstLinks = $.extend(true,{},{v:dst.links}).v + src.links.push(dst.id); + dst.links.push(src.id); + src.dirty = true; + dst.dirty = true; + + historyEvent.events.push({ + t:'edit', + node: src, + dirty: RED.nodes.dirty(), + changed: src.changed, + changes: { + links:oldSrcLinks + } + }); + historyEvent.events.push({ + t:'edit', + node: dst, + dirty: RED.nodes.dirty(), + changed: dst.changed, + changes: { + links:oldDstLinks + } + }); + src.changed = true; + dst.changed = true; + } else { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + historyEvent.links = [link]; + } + if (!keepAdding) { + quickAddLink.el.remove(); + quickAddLink = null; + if (mouse_mode === RED.state.QUICK_JOINING) { + if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + } + } else { + quickAddLink.node = nn; + quickAddLink.port = 0; + } + } else { + hideDragLines(); + resetMouseVars(); + } + } else { + if (!keepAdding) { + if (mouse_mode === RED.state.QUICK_JOINING) { + if (nn.outputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { + } else if (nn.inputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); } else { resetMouseVars(); } - } else { - hideDragLines(); - resetMouseVars(); } } else { if (nn.outputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_OUTPUT + } } else if (nn.inputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_INPUT + } } else { resetMouseVars(); } } - quickAddLink = null; } RED.history.push(historyEvent); @@ -804,7 +893,37 @@ RED.view = (function() { updateActiveNodes(); updateSelection(); redraw(); - if (auto === true) { point[0] = point[0] + ((nn.w < 100) ? 100 : nn.w) + gridSize * 2; } + // At this point the newly added node will have a real width, + // so check if the position needs nudging + if (lastAddedX !== undefined) { + var lastNodeRHEdge = lastAddedX + lastAddedWidth/2; + var thisNodeLHEdge = nn.x - nn.w/2; + var gap = thisNodeLHEdge - lastNodeRHEdge; + if (gap != gridSize *2) { + nn.x = nn.x + gridSize * 2 - gap; + nn.dirty = true; + nn.x = Math.ceil(nn.x / gridSize) * gridSize; + redraw(); + } + } + if (keepAdding) { + if (lastAddedX === undefined) { + // ghostLink.attr("opacity",1); + setTimeout(function() { + RED.typeSearch.refresh({filter:{input:true}}); + },100); + } + + lastAddedX = nn.x; + lastAddedWidth = nn.w; + + point[0] = nn.x + nn.w/2 + node_width/2 + gridSize * 2; + ghostNode.attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + rebuildQuickAddLink(); + } else { + quickAddActive = false; + ghostNode.remove(); + } } }); @@ -943,7 +1062,7 @@ RED.view = (function() { redraw(); mouse_mode = RED.state.JOINING; } - } else if (mousedown_node) { + } else if (mousedown_node && !quickAddLink) { showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); } selected_link = null; @@ -956,7 +1075,6 @@ RED.view = (function() { var portY = -((numOutputs-1)/2)*13 +13*sourcePort; var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1; - drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc)); } d3.event.preventDefault(); @@ -1624,9 +1742,6 @@ RED.view = (function() { function disableQuickJoinEventHandler(evt) { // Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari) if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) { - if (quickAddActive && drag_lines.length > 0) { - quickAddLink = drag_lines[0]; - } resetMouseVars(); hideDragLines(); redraw(); @@ -1651,7 +1766,6 @@ RED.view = (function() { if (d3.event.ctrlKey || d3.event.metaKey) { mouse_mode = RED.state.QUICK_JOINING; showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); - quickAddLink = null; $(window).on('keyup',disableQuickJoinEventHandler); } } 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 ecd95847f..26e7443df 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 @@ -92,6 +92,14 @@ stroke-dasharray:10,4; stroke: #f33; } +.node_placeholder { + stroke-dasharray:10,4; + stroke: #aaa; + fill: #eee; + opacity: 0.5; + stroke-width: 2; +} + .tool_arrow { stroke-width: 1; stroke: #999;