diff --git a/Gruntfile.js b/Gruntfile.js index 979b38051..a168d7ce4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -192,6 +192,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/library.js", "packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js", "packages/node_modules/@node-red/editor-client/src/js/ui/search.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", diff --git a/package.json b/package.json index d22cf89dd..87518820e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", - "i18next": "21.8.2", + "i18next": "21.8.10", "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "4.1.0", @@ -62,7 +62,7 @@ "moment": "2.29.3", "moment-timezone": "0.5.34", "mqtt": "4.3.7", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "node-red-admin": "^3.0.0", "node-watch": "0.7.3", @@ -76,7 +76,7 @@ "semver": "7.3.7", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.15.5", + "uglify-js": "3.16.0", "uuid": "8.3.2", "ws": "7.5.6", "xml2js": "0.4.23" @@ -105,16 +105,16 @@ "grunt-sass": "~3.1.0", "grunt-simple-mocha": "~0.4.1", "grunt-simple-nyc": "^3.0.1", - "i18next-http-backend": "1.4.0", + "i18next-http-backend": "1.4.1", "jquery-i18next": "1.2.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "4.0.15", + "marked": "4.0.17", "minami": "1.2.3", "mocha": "9.2.2", "node-red-node-test-helper": "^0.2.7", "nodemon": "2.0.16", "proxy": "^1.0.2", - "sass": "1.51.0", + "sass": "1.52.3", "should": "13.2.3", "sinon": "11.1.2", "stoppable": "^1.1.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 793c5e516..769eb8f5a 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -26,7 +26,7 @@ "express": "4.18.1", "memorystore": "1.6.7", "mime": "3.0.0", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "oauth2orize": "1.11.1", "passport-http-bearer": "1.0.1", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js b/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js index 2273ae9ab..5bd9ea034 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js @@ -21,6 +21,34 @@ RED.actions = (function() { function getAction(name) { return actions[name].handler; } + function getActionLabel(name) { + let def = actions[name] + if (!def) { + return '' + } + if (!def.label) { + var options = def.options; + var key = options ? options.label : undefined; + if (!key) { + key = "action-list." +name.replace(/^.*:/,""); + } + var label = RED._(key); + if (label === key) { + // no translation. convert `name` to description + label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() { + if (arguments[5] === 0) { + return arguments[2].toUpperCase(); + } else { + return " "+arguments[4].toUpperCase(); + } + }); + } + def.label = label; + } + return def.label + } + + function invokeAction() { var args = Array.prototype.slice.call(arguments); var name = args.shift(); @@ -31,7 +59,7 @@ RED.actions = (function() { } function listActions() { var result = []; - var missing = []; + Object.keys(actions).forEach(function(action) { var def = actions[action]; var shortcut = RED.keyboard.getShortcut(action); @@ -42,28 +70,8 @@ RED.actions = (function() { isUser = !!RED.keyboard.getUserShortcut(action); } if (!def.label) { - var name = action; - var options = def.options; - var key = options ? options.label : undefined; - if (!key) { - key = "action-list." +name.replace(/^.*:/,""); - } - var label = RED._(key); - if (label === key) { - // no translation. convert `name` to description - label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() { - if (arguments[5] === 0) { - return arguments[2].toUpperCase(); - } else { - return " "+arguments[4].toUpperCase(); - } - }); - missing.push(key); - } - def.label = label; + def.label = getActionLabel(action) } - //console.log("; missing:", missing); - result.push({ id:action, scope:shortcut?shortcut.scope:undefined, @@ -79,6 +87,7 @@ RED.actions = (function() { add: addAction, remove: removeAction, get: getAction, + getLabel: getActionLabel, invoke: invokeAction, list: listActions } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 417189b33..6327f5268 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -16,6 +16,7 @@ RED.menu = (function() { var menuItems = {}; + let menuItemCount = 0 function createMenuItem(opt) { var item; @@ -59,15 +60,16 @@ RED.menu = (function() { item = $('
  • '); } else { item = $('
  • '); - + if (!opt.id) { + opt.id = 'red-ui-menu-item-'+(menuItemCount++) + } if (opt.group) { item.addClass("red-ui-menu-group-"+opt.group); - } var linkContent = ''; if (opt.toggle) { - linkContent += ''; - linkContent += ''; + linkContent += ''; + linkContent += ''; } if (opt.icon !== undefined) { @@ -77,12 +79,15 @@ RED.menu = (function() { linkContent += ' '; } } - + let label = opt.label + if (!opt.label && typeof opt.onselect === 'string') { + label = RED.actions.getLabel(opt.onselect) + } if (opt.sublabel) { - linkContent += ''+opt.label+''+ + linkContent += ''+label+''+ ''+opt.sublabel+'' } else { - linkContent += ''+opt.label+'' + linkContent += ''+label+'' } linkContent += ''; @@ -126,10 +131,21 @@ RED.menu = (function() { }); } if (opt.options) { - item.addClass("red-ui-menu-dropdown-submenu pull-left"); + item.addClass("red-ui-menu-dropdown-submenu"+(opt.direction!=='right'?" pull-left":"")); var submenu = $('').appendTo(item); for (var i=0;i",{class:"red-ui-menu red-ui-menu-dropdown pull-right"}); - + if (options.direction) { + topMenu.addClass("red-ui-menu-dropdown-direction-"+options.direction) + } if (options.id) { topMenu.attr({id:options.id+"-submenu"}); var menuParent = $("#"+options.id); @@ -175,6 +193,15 @@ RED.menu = (function() { var lastAddedSeparator = false; for (var i=0;i 0); + const hasMultipleSelection = hasSelection && selection.nodes.length > 1; + const hasLinks = selection.links && selection.links.length > 0; + const isSingleLink = !hasSelection && hasLinks && selection.links.length === 1 + const isMultipleLinks = !hasSelection && hasLinks && selection.links.length > 1 + const canDelete = hasSelection || hasLinks + const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' + + const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g + + + const menuItems = [ + { onselect: 'core:show-action-list', onpostselect: function() {} }, + { + label: 'Insert', + options: [ + { + label: 'Node', + onselect: function() { + RED.view.showQuickAddDialog({ + position: [ options.x - offset.left, options.y - offset.top ], + touchTrigger: true, + splice: isSingleLink?selection.links[0]:undefined, + // spliceMultiple: isMultipleLinks + }) + } + }, + { + label: 'Junction', + onselect: 'core:split-wires-with-junctions', + disabled: hasSelection || !hasLinks + }, + { + label: 'Link Nodes', + onselect: 'core:split-wire-with-link-nodes', + disabled: hasSelection || !hasLinks + } + ] + + + + } + ] + // menuItems.push( + // { + // label: (isSingleLink || isMultipleLinks)?'Insert into wire...':'Add node...', + // onselect: function() { + // RED.view.showQuickAddDialog({ + // position: [ options.x - offset.left, options.y - offset.top ], + // touchTrigger: true, + // splice: isSingleLink?selection.links[0]:undefined, + // spliceMultiple: isMultipleLinks + // }) + // } + // }, + // ) + // if (hasLinks && !hasSelection) { + // menuItems.push({ onselect: 'core:split-wires-with-junctions', label: 'Insert junction'}) + // } + menuItems.push( + null, + { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, + { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, + null, + { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection}, + { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, + { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() }, + { onselect: 'core:delete-selection', disabled: !canDelete }, + { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, + { onselect: 'core:select-all-nodes' } + ) + + if (hasSelection) { + menuItems.push( + null, + isGroup ? + { onselect: 'core:ungroup-selection', disabled: !isGroup } + : { onselect: 'core:group-selection', disabled: !hasSelection } + ) + if (canRemoveFromGroup) { + menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }) + } + + } + const offset = $("#red-ui-workspace-chart").offset() + menu = RED.menu.init({ + direction: 'right', + onpreselect: function() { + disposeMenu() + }, + onpostselect: function() { + RED.view.focus() + }, + options: menuItems + }); + + menu.attr("id","red-ui-workspace-context-menu"); + menu.css({ + position: "absolute" + }) + menu.appendTo("body"); + + // TODO: prevent the menu from overflowing the window. + + var top = options.y + var left = options.x + + if (top+menu.height()-$(document).scrollTop() > $(window).height()) { + top -= (top+menu.height())-$(window).height() + 22; + } + if (left+menu.width()-$(document).scrollLeft() > $(window).width()) { + left -= (left+menu.width())-$(window).width() + 18; + } + menu.css({ + top: top+"px", + left: left+"px" + }) + $(".red-ui-menu.red-ui-menu-dropdown").hide(); + $(document).on("mousedown.red-ui-workspace-context-menu", function(evt) { + if (menu && menu[0].contains(evt.target)) { + return + } + disposeMenu() + }); + menu.show(); + + // menu.show({ + // target: $('#red-ui-main-container'), + // x: options.x, + // y: options.y + // }) + + + } + + return { + show: show + } +})() 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 0fc633071..fc5b8e99e 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 @@ -104,7 +104,9 @@ RED.typeSearch = (function() { 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 (!/^_action_:/.test(n.type)) { + typesUsed[n.type] = Date.now(); + } if (n.def.outputs === 0) { confirm(n); } else { @@ -173,6 +175,8 @@ RED.typeSearch = (function() { var nodeDiv = $('
    ',{class:"red-ui-search-result-node"}).appendTo(div); if (object.type === "junction") { nodeDiv.addClass("red-ui-palette-icon-junction"); + } else if (/^_action_:/.test(object.type)) { + nodeDiv.addClass("red-ui-palette-icon-junction") } else { var colour = RED.utils.getNodeColor(object.type,def); nodeDiv.css('backgroundColor',colour); @@ -182,11 +186,14 @@ RED.typeSearch = (function() { var iconContainer = $('
    ',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, false); - if (object.type !== "junction" && def.inputs > 0) { - $('
    ',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); - } - if (object.type !== "junction" && def.outputs > 0) { - $('
    ',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv); + + if (!/^_action_:/.test(object.type) && object.type !== "junction") { + if (def.inputs > 0) { + $('
    ',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); + } + if (def.outputs > 0) { + $('
    ',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv); + } } var contentDiv = $('
    ',{class:"red-ui-search-result-description"}).appendTo(div); @@ -207,7 +214,9 @@ RED.typeSearch = (function() { } function confirm(def) { hide(); - typesUsed[def.type] = Date.now(); + if (!/^_action_:/.test(def.type)) { + typesUsed[def.type] = Date.now(); + } addCallback(def.type); } @@ -316,6 +325,7 @@ RED.typeSearch = (function() { function applyFilter(filter,type,def) { return !filter || ( + (!filter.spliceMultiple) && (!filter.type || type === filter.type) && (!filter.input || type === 'junction' || def.inputs > 0) && (!filter.output || type === 'junction' || def.outputs > 0) @@ -330,6 +340,13 @@ RED.typeSearch = (function() { 'inject','debug','function','change','switch','junction' ].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); }); + // if (opts.filter && opts.filter.input && opts.filter.output && !opts.filter.type) { + // if (opts.filter.spliceMultiple) { + // common.push('_action_:core:split-wires-with-junctions') + // } + // common.push('_action_:core:split-wire-with-link-nodes') + // } + var recentlyUsed = Object.keys(typesUsed); recentlyUsed.sort(function(a,b) { return typesUsed[b]-typesUsed[a]; @@ -354,6 +371,8 @@ RED.typeSearch = (function() { var itemDef = RED.nodes.getType(common[i]); if (common[i] === 'junction') { itemDef = { inputs:1, outputs: 1, label: 'junction', type: 'junction'} + } else if (/^_action_:/.test(common[i]) ) { + itemDef = { inputs:1, outputs: 1, label: common[i], type: common[i]} } if (itemDef) { item = { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index c3ea0ddd1..2c4cdca6b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -1032,6 +1032,8 @@ RED.utils = (function() { return "font-awesome/fa-circle-o" } else if (def.category === 'config') { return RED.settings.apiRootUrl+"icons/node-red/cog.svg" + } else if ((node && /^_action_:/.test(node.type)) || /^_action_:/.test(def.type)) { + return "font-awesome/fa-cogs" } else if (node && node.type === 'tab') { return "red-ui-icons/red-ui-icons-flow" // return RED.settings.apiRootUrl+"images/subflow_tab.svg" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 699e5f222..888fb4d7f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -336,17 +336,17 @@ RED.view.tools = (function() { } - function addNode() { - var selection = RED.view.selection(); - if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) { - var selectedNode = selection.nodes[0]; - RED.view.showQuickAddDialog([ - selectedNode.x + selectedNode.w + 50,selectedNode.y - ]) - } else { - RED.view.showQuickAddDialog(); - } - } + // function addNode() { + // var selection = RED.view.selection(); + // if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) { + // var selectedNode = selection.nodes[0]; + // RED.view.showQuickAddDialog([ + // selectedNode.x + selectedNode.w + 50,selectedNode.y + // ]) + // } else { + // RED.view.showQuickAddDialog(); + // } + // } function gotoNearestNode(direction) { @@ -815,6 +815,9 @@ RED.view.tools = (function() { */ function splitWiresWithLinkNodes(wires) { let wiresToSplit = wires || RED.view.selection().links; + if (!wiresToSplit) { + return + } if (!Array.isArray(wiresToSplit)) { wiresToSplit = [wiresToSplit]; } @@ -1047,6 +1050,135 @@ RED.view.tools = (function() { } } + function addJunctionsToWires(wires) { + let wiresToSplit = wires || RED.view.selection().links; + if (!wiresToSplit) { + return + } + if (!Array.isArray(wiresToSplit)) { + wiresToSplit = [wiresToSplit]; + } + if (wiresToSplit.length === 0) { + return; + } + + var removedLinks = new Set() + var addedLinks = [] + var addedJunctions = [] + + var groupedLinks = {} + wiresToSplit.forEach(function(l) { + var sourceId = l.source.id+":"+l.sourcePort + groupedLinks[sourceId] = groupedLinks[sourceId] || [] + groupedLinks[sourceId].push(l) + + groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + groupedLinks[l.target.id].push(l) + }); + var linkGroups = Object.keys(groupedLinks) + linkGroups.sort(function(A,B) { + return groupedLinks[B].length - groupedLinks[A].length + }) + linkGroups.forEach(function(gid) { + var links = groupedLinks[gid] + var junction = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + links = links.filter(function(l) { return !removedLinks.has(l) }) + if (links.length === 0) { + return + } + let pointCount = 0 + links.forEach(function(l) { + if (l._sliceLocation) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + delete l._sliceLocation + pointCount++ + } else { + junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2 + junction.y += l.source.y + l.target.y + pointCount += 2 + } + }) + junction.x = Math.round(junction.x/pointCount) + junction.y = Math.round(junction.y/pointCount) + if (RED.view.snapGrid) { + let gridSize = RED.view.gridSize() + junction.x = (gridSize*Math.round(junction.x/gridSize)); + junction.y = (gridSize*Math.round(junction.y/gridSize)); + } + + var nodeGroups = new Set() + + RED.nodes.addJunction(junction) + addedJunctions.push(junction) + let newLink + if (gid === links[0].source.id+":"+links[0].sourcePort) { + newLink = { + source: links[0].source, + sourcePort: links[0].sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: links[0].target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + links.forEach(function(l) { + removedLinks.add(l) + RED.nodes.removeLink(l) + let newLink + if (gid === l.target.id) { + newLink = { + source: l.source, + sourcePort: l.sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: l.target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + nodeGroups.add(l.source.g || "__NONE__") + nodeGroups.add(l.target.g || "__NONE__") + }) + if (nodeGroups.size === 1) { + var group = nodeGroups.values().next().value + if (group !== "__NONE__") { + RED.group.addToGroup(RED.nodes.group(group), junction) + } + } + }) + if (addedJunctions.length > 0) { + RED.history.push({ + t: 'add', + links: addedLinks, + junctions: addedJunctions, + removedLinks: Array.from(removedLinks) + }) + RED.nodes.dirty(true) + } + RED.view.redraw(true); + } + return { init: function() { RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) @@ -1109,6 +1241,7 @@ RED.view.tools = (function() { RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() }) RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() }); + RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() }); RED.actions.add("core:generate-node-names", generateNodeNames ) 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 f1268f156..bca3f47a6 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 @@ -206,7 +206,15 @@ RED.view = (function() { function init() { chart = $("#red-ui-workspace-chart"); - + chart.on('contextmenu', function(evt) { + evt.preventDefault() + evt.stopPropagation() + RED.contextMenu.show({ + x:evt.clientX-5, + y:evt.clientY-5 + }) + return false + }) outer = d3.select("#red-ui-workspace-chart") .append("svg:svg") .attr("width", space_width) @@ -230,6 +238,7 @@ RED.view = (function() { .on("mousedown", canvasMouseDown) .on("mouseup", canvasMouseUp) .on("mouseenter", function() { + d3.select(document).on('mouseup.red-ui-workspace-tracker', null) if (lasso) { if (d3.event.buttons !== 1) { lasso.remove(); @@ -245,6 +254,7 @@ RED.view = (function() { } } }) + .on("mouseleave", canvasMouseLeave) .on("touchend", function() { d3.event.preventDefault(); clearTimeout(touchStartTime); @@ -385,6 +395,9 @@ RED.view = (function() { drag_lines = []; RED.events.on("workspace:change",function(event) { + // Just in case the mouse left the workspace whilst doing an action, + // put us back into default mode so the refresh works + mouse_mode = 0 if (event.old !== 0) { workspaceScrollPositions[event.old] = { left:chart.scrollLeft(), @@ -972,7 +985,7 @@ RED.view = (function() { } function canvasMouseDown() { - if (RED.view.DEBUG) { + if (RED.view.DEBUG) { console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } if (mouse_mode === RED.state.SELECTING_NODE) { @@ -987,7 +1000,10 @@ RED.view = (function() { scroll_position = [chart.scrollLeft(),chart.scrollTop()]; return; } - if (!mousedown_node && !mousedown_link && !mousedown_group) { + if (d3.event.button === 2) { + return + } + if (!mousedown_node && !mousedown_link && !mousedown_group && !d3.event.shiftKey) { selectedLinks.clear(); updateSelection(); } @@ -1041,6 +1057,7 @@ RED.view = (function() { options = options || {}; var point = options.position || lastClickPosition; var spliceLink = options.splice; + var spliceMultipleLinks = options.spliceMultiple var targetGroup = options.group; var touchTrigger = options.touchTrigger; @@ -1053,6 +1070,10 @@ RED.view = (function() { var ox = point[0]; var oy = point[1]; + const offset = $("#red-ui-workspace-chart").offset() + var clientX = ox + offset.left + var clientY = oy + offset.top + if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') point[0] = Math.round(point[0] / gridSize) * gridSize; @@ -1104,8 +1125,12 @@ RED.view = (function() { } hideDragLines(); } - if (spliceLink) { - filter = {input:true, output:true} + if (spliceLink || spliceMultipleLinks) { + filter = { + input:true, + output:true, + spliceMultiple: spliceMultipleLinks + } } var rebuildQuickAddLink = function() { @@ -1130,8 +1155,8 @@ RED.view = (function() { var lastAddedWidth; RED.typeSearch.show({ - 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]), + x:clientX-mainPos.left-node_width/2 - (ox-point[0]), + y:clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), disableFocus: touchTrigger, filter: filter, move: function(dx,dy) { @@ -1159,7 +1184,7 @@ RED.view = (function() { hideDragLines(); redraw(); }, - add: function(type,keepAdding) { + add: function(type, keepAdding) { if (touchTrigger) { keepAdding = false; resetMouseVars(); @@ -1167,7 +1192,13 @@ RED.view = (function() { var nn; var historyEvent; - if (type === 'junction') { + if (/^_action_:/.test(type)) { + const actionName = type.substring(9) + quickAddActive = false; + ghostNode.remove(); + RED.actions.invoke(actionName) + return + } else if (type === 'junction') { nn = { _def: {defaults:{}}, type: 'junction', @@ -1733,10 +1764,17 @@ RED.view = (function() { redraw(); } } - + function canvasMouseLeave() { + if (mouse_mode !== 0 && d3.event.buttons !== 0) { + d3.select(document).on('mouseup.red-ui-workspace-tracker', function() { + d3.select(document).on('mouseup.red-ui-workspace-tracker', null) + canvasMouseUp.call(this) + }) + } + } function canvasMouseUp() { lastClickPosition = [d3.event.offsetX/scaleFactor,d3.event.offsetY/scaleFactor]; - if (RED.view.DEBUG) { + if (RED.view.DEBUG) { console.warn("canvasMouseUp", { mouse_mode, point: d3.mouse(this), event: d3.event }); } var i; @@ -1832,8 +1870,20 @@ RED.view = (function() { } } }) - - + activeLinks.forEach(function(link) { + if (!link.selected) { + var sourceY = link.source.y + var targetY = link.target.y + var sourceX = link.source.x+(link.source.w/2) + 10 + var targetX = link.target.x-(link.target.w/2) - 10 + if ( + sourceX > x && sourceX < x2 && sourceY > y && sourceY < y2 && + targetX > x && targetX < x2 && targetY > y && targetY < y2 + ) { + selectedLinks.add(link); + } + } + }) // var selectionChanged = false; // do { @@ -1881,114 +1931,118 @@ RED.view = (function() { slicePath = null; RED.view.redraw(true); } else if (mouse_mode == RED.state.SLICING_JUNCTION) { - var removedLinks = new Set() - var addedLinks = [] - var addedJunctions = [] - - var groupedLinks = {} - selectedLinks.forEach(function(l) { - var sourceId = l.source.id+":"+l.sourcePort - groupedLinks[sourceId] = groupedLinks[sourceId] || [] - groupedLinks[sourceId].push(l) - - groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] - groupedLinks[l.target.id].push(l) - }); - var linkGroups = Object.keys(groupedLinks) - linkGroups.sort(function(A,B) { - return groupedLinks[B].length - groupedLinks[A].length - }) - linkGroups.forEach(function(gid) { - var links = groupedLinks[gid] - var junction = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - links = links.filter(function(l) { return !removedLinks.has(l) }) - if (links.length === 0) { - return - } - links.forEach(function(l) { - junction.x += l._sliceLocation.x - junction.y += l._sliceLocation.y - }) - junction.x = Math.round(junction.x/links.length) - junction.y = Math.round(junction.y/links.length) - if (snapGrid) { - junction.x = (gridSize*Math.round(junction.x/gridSize)); - junction.y = (gridSize*Math.round(junction.y/gridSize)); - } - - var nodeGroups = new Set() - - RED.nodes.addJunction(junction) - addedJunctions.push(junction) - let newLink - if (gid === links[0].source.id+":"+links[0].sourcePort) { - newLink = { - source: links[0].source, - sourcePort: links[0].sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: links[0].target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - links.forEach(function(l) { - removedLinks.add(l) - RED.nodes.removeLink(l) - let newLink - if (gid === l.target.id) { - newLink = { - source: l.source, - sourcePort: l.sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: l.target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - nodeGroups.add(l.source.g || "__NONE__") - nodeGroups.add(l.target.g || "__NONE__") - }) - if (nodeGroups.size === 1) { - var group = nodeGroups.values().next().value - if (group !== "__NONE__") { - RED.group.addToGroup(RED.nodes.group(group), junction) - } - } - }) + RED.actions.invoke("core:split-wires-with-junctions") slicePath.remove(); slicePath = null; - if (addedJunctions.length > 0) { - RED.history.push({ - t: 'add', - links: addedLinks, - junctions: addedJunctions, - removedLinks: Array.from(removedLinks) - }) - RED.nodes.dirty(true) - } - RED.view.redraw(true); + // var removedLinks = new Set() + // var addedLinks = [] + // var addedJunctions = [] + // + // var groupedLinks = {} + // selectedLinks.forEach(function(l) { + // var sourceId = l.source.id+":"+l.sourcePort + // groupedLinks[sourceId] = groupedLinks[sourceId] || [] + // groupedLinks[sourceId].push(l) + // + // groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + // groupedLinks[l.target.id].push(l) + // }); + // var linkGroups = Object.keys(groupedLinks) + // linkGroups.sort(function(A,B) { + // return groupedLinks[B].length - groupedLinks[A].length + // }) + // linkGroups.forEach(function(gid) { + // var links = groupedLinks[gid] + // var junction = { + // _def: {defaults:{}}, + // type: 'junction', + // z: RED.workspaces.active(), + // id: RED.nodes.id(), + // x: 0, + // y: 0, + // w: 0, h: 0, + // outputs: 1, + // inputs: 1, + // dirty: true + // } + // links = links.filter(function(l) { return !removedLinks.has(l) }) + // if (links.length === 0) { + // return + // } + // links.forEach(function(l) { + // junction.x += l._sliceLocation.x + // junction.y += l._sliceLocation.y + // }) + // junction.x = Math.round(junction.x/links.length) + // junction.y = Math.round(junction.y/links.length) + // if (snapGrid) { + // junction.x = (gridSize*Math.round(junction.x/gridSize)); + // junction.y = (gridSize*Math.round(junction.y/gridSize)); + // } + // + // var nodeGroups = new Set() + // + // RED.nodes.addJunction(junction) + // addedJunctions.push(junction) + // let newLink + // if (gid === links[0].source.id+":"+links[0].sourcePort) { + // newLink = { + // source: links[0].source, + // sourcePort: links[0].sourcePort, + // target: junction + // } + // } else { + // newLink = { + // source: junction, + // sourcePort: 0, + // target: links[0].target + // } + // } + // addedLinks.push(newLink) + // RED.nodes.addLink(newLink) + // links.forEach(function(l) { + // removedLinks.add(l) + // RED.nodes.removeLink(l) + // let newLink + // if (gid === l.target.id) { + // newLink = { + // source: l.source, + // sourcePort: l.sourcePort, + // target: junction + // } + // } else { + // newLink = { + // source: junction, + // sourcePort: 0, + // target: l.target + // } + // } + // addedLinks.push(newLink) + // RED.nodes.addLink(newLink) + // nodeGroups.add(l.source.g || "__NONE__") + // nodeGroups.add(l.target.g || "__NONE__") + // }) + // if (nodeGroups.size === 1) { + // var group = nodeGroups.values().next().value + // if (group !== "__NONE__") { + // RED.group.addToGroup(RED.nodes.group(group), junction) + // } + // } + // }) + // slicePath.remove(); + // slicePath = null; + // + // if (addedJunctions.length > 0) { + // RED.history.push({ + // t: 'add', + // links: addedLinks, + // junctions: addedJunctions, + // removedLinks: Array.from(removedLinks) + // }) + // RED.nodes.dirty(true) + // } + // RED.view.redraw(true); } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (movingSet.length() > 0) { @@ -3736,7 +3790,7 @@ RED.view = (function() { function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) } function linkMouseDown(d) { - if (RED.view.DEBUG) { + if (RED.view.DEBUG) { console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } if (mouse_mode === RED.state.SELECTING_NODE) { @@ -5736,7 +5790,12 @@ RED.view = (function() { node.dirty = true; node.dirtyStatus = true; node.changed = true; - RED.events.emit("nodes:change",node); + if (node.type === "junction") { + RED.events.emit("junctions:change",node); + } + else { + RED.events.emit("nodes:change",node); + } } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 98ab3bd3b..4104fd83e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -46,7 +46,7 @@ & > li > a, & > li > a:focus { display: block; - padding: 4px 12px 4px 32px; + padding: 4px 20px 4px 12px; clear: both; font-weight: normal; line-height: 20px; @@ -54,7 +54,10 @@ white-space: normal !important; outline: none; } - + & > li.pull-left > a, + & > li.pull-left > a:focus { + padding: 4px 12px 4px 32px; + } & > .active > a, & > .active > a:hover, & > .active > a:focus { @@ -145,8 +148,8 @@ position: relative; & > .red-ui-menu-dropdown { top: 0; - left: 100%; - margin-top: -6px; + left: calc(100% - 5px); + margin-top: 0; margin-left: -1px; } &.open > .red-ui-menu-dropdown, @@ -175,10 +178,10 @@ } } -.red-ui-menu-dropdown-submenu>a:after { +.red-ui-menu-dropdown-submenu.pull-left>a:after { display: none; } -.red-ui-menu-dropdown-submenu>a:before { +.red-ui-menu-dropdown-submenu.pull-left>a:before { display: block; float: left; width: 0; @@ -192,7 +195,25 @@ border-width: 5px 5px 5px 0; content: " "; } - +.red-ui-menu-dropdown-direction-right { + .red-ui-menu-dropdown-submenu>a:after { + display: none; + } + .red-ui-menu-dropdown-submenu>a:before { + display: block; + float: right; + width: 0; + height: 0; + margin-top: 5px; + margin-right: -15px; + /* Caret Arrow */ + border-color: transparent; + border-left-color: $menuCaret; + border-style: solid; + border-width: 5px 0 5px 5px; + content: " "; + } +} .red-ui-menu-dropdown-submenu.disabled > a:before { border-right-color: $menuCaret; } diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png b/packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png new file mode 100644 index 000000000..1acaab48b Binary files /dev/null and b/packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png differ diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 219aaec7b..4b6f5f66d 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,15 +1,27 @@ export default { - version: "3.0.0-beta.1", + version: "3.0.0-beta.3", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 1!", - "ja": "Node-RED 3.0 ベータ1へようこそ!" + "en-US": "Welcome to Node-RED 3.0 Beta 3!", + "ja": "Node-RED 3.0 ベータ3へようこそ!" }, description: { - "en-US": "

    This is the first Beta release of Node-RED 3.0. It contains just about everything we have planned for the final release.

    Let's take a moment to discover the new features in this release.

    ", - "ja": "

    これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

    本リリースの新機能を見つけてみましょう。

    " + "en-US": "

    This is the final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", + // "ja": "

    これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

    本リリースの新機能を見つけてみましょう。

    " + } + }, + { + title: { + "en-US": "Context Menu" + }, + image: 'images/context-menu.png', + description: { + "en-US": `

    The editor now has its own context menu when you + right-click in the workspace.

    +

    This makes many of the built-in actions much easier + to access.

    ` } }, { @@ -19,12 +31,13 @@ export default { }, image: 'images/junction-slice.gif', description: { - "en-US": `

    To make it easier to route wires around your flows, it is now possible to - add junction nodes that give you more control.

    -

    Junctions can be added to wires by holding the Shift key, then click and drag with - the right-hand mouse button across the wires.

    `, - "ja": `

    フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

    -

    シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

    ` + "en-US": `

    To make it easier to route wires around your flows, + it is now possible to add junction nodes that give + you more control.

    +

    Junctions can be added to wires by holding the Alt key + then click and drag the mouse across the wires.

    `, + // "ja": `

    フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

    + //

    シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

    ` }, }, { diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 6309ae9ad..156abd29d 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -460,7 +460,7 @@ } }); - var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs) { + var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) { var editor = RED.editor.createEditor({ id: id, mode: 'ace/mode/nrjavascript', @@ -484,14 +484,14 @@ extraLibs: extraLibs }); if (defaultValue && value === "") { - editor.moveCursorTo(defaultValue.split("\n").length - 1, 0); + editor.moveCursorTo(defaultValue.split("\n").length +offset, 0); } editor.__stateId = stateId; return editor; } - this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize")) - this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || []) - this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize")) + this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize"), undefined, 0); + this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || [], undefined, -1); + this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize"), undefined, 0); RED.library.create({ url:"functions", // where to get the data from diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html index b726b537d..6b19ebaa8 100755 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html @@ -198,8 +198,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:"filename"}, - filenameType: {value:"msg"}, + filename: {value:""}, + filenameType: {value:"str"}, appendNewline: {value:true}, createDir: {value:false}, overwriteFile: {value:"false"}, @@ -236,8 +236,8 @@ label: node._("file.encoding.setbymsg") }).text(label).appendTo(encSel); $("#node-input-filename").typedInput({ - default: "msg", - types: ["str", "msg", "jsonata", "env"], + default: "str", + types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"], typeField: $("#node-input-filenameType") }); if(typeof node.filenameType == 'undefined') { @@ -297,8 +297,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:"filename"}, - filenameType: {value:"msg"}, + filename: {value:""}, + filenameType: {value:"str"}, format: {value:"utf8"}, chunk: {value:false}, sendError: {value: false}, @@ -341,8 +341,8 @@ label: label }).text(label).appendTo(encSel); $("#node-input-filename").typedInput({ - default: "msg", - types: ["str", "msg", "jsonata", "env"], + default: "str", + types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"], typeField: $("#node-input-filenameType") }); if(typeof node.filenameType == 'undefined') { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 77d832abd..62d5f351f 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -928,6 +928,7 @@ "write": "write file", "read": "read file", "filename": "Filename", + "path": "path", "action": "Action", "addnewline": "Add newline (\\n) to each payload?", "createdir": "Create directory if it doesn't exist?", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 2a0d7e7c1..8c9d5adf7 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -36,7 +36,7 @@ "js-yaml": "4.1.0", "media-typer": "1.1.0", "mqtt": "4.3.7", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "node-watch": "0.7.3", "on-headers": "1.0.2", diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index 62080b4a8..da4006ecc 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -88,9 +88,10 @@ function getLocalFile(file) { /** * Synchronously walks the directory looking for node files. * @param dir the directory to search + * @param skipValidNodeRedModules a flag to skip lading icons & files if the directory a valid node-red module * @return an array of fully-qualified paths to .js files */ -function getLocalNodeFiles(dir) { +function getLocalNodeFiles(dir, skipValidNodeRedModules) { dir = path.resolve(dir); var result = []; @@ -102,6 +103,14 @@ function getLocalNodeFiles(dir) { return {files: [], icons: []}; } files.sort(); + // when loading local files, if the path is a valid node-red module + // dont include it (will be picked up in scanTreeForNodesModules) + if(skipValidNodeRedModules && files.indexOf("package.json") >= 0) { + const package = getPackageDetails(dir) + if(package.isNodeRedModule) { + return {files: [], icons: []}; + } + } files.forEach(function(fn) { var stats = fs.statSync(path.join(dir,fn)); if (stats.isFile()) { @@ -114,7 +123,7 @@ function getLocalNodeFiles(dir) { } else if (stats.isDirectory()) { // Ignore /.dirs/, /lib/ /node_modules/ if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) { - var subDirResults = getLocalNodeFiles(path.join(dir,fn)); + var subDirResults = getLocalNodeFiles(path.join(dir,fn), skipValidNodeRedModules); result = result.concat(subDirResults.files); icons = icons.concat(subDirResults.icons); } else if (fn === "icons") { @@ -126,21 +135,30 @@ function getLocalNodeFiles(dir) { return {files: result, icons: icons} } -function scanDirForNodesModules(dir,moduleName) { - var results = []; - var scopeName; +function scanDirForNodesModules(dir,moduleName,package) { + let results = []; + let scopeName; + let files try { - var files = fs.readdirSync(dir); - if (moduleName) { - var m = /^(?:(@[^/]+)[/])?([^@/]+)/.exec(moduleName); - if (m) { - scopeName = m[1]; - moduleName = m[2]; + let isNodeRedModule = false + if(package) { + dir = path.join(package.moduleDir,'..') + files = [path.basename(package.moduleDir)] + moduleName = (package.package ? package.package.name : null) || moduleName + isNodeRedModule = package.isNodeRedModule + } else { + files = fs.readdirSync(dir); + if (moduleName) { + var m = /^(?:(@[^/]+)[/])?([^@/]+)/.exec(moduleName); + if (m) { + scopeName = m[1]; + moduleName = m[2]; + } } } - for (var i=0;i look for node_modules + 2. exist(package.json) && package.json.has(node-red) => load this only + 3. in original scan of nodesDir, ignore if:(exist(package.json) && package.json.has(node-red)) + */ + if (nodesDir) { + for (let dirIndex = 0; dirIndex < nodesDir.length; dirIndex++) { + const nodeDir = nodesDir[dirIndex]; + const packageDetails = getPackageDetails(nodeDir) + if(packageDetails.isNodeRedModule) { + //we have found a node-red module, scan it + const nrModules = scanDirForNodesModules(nodeDir, packageDetails.package.name, packageDetails); + results = results.concat(nrModules); + + } else if (packageDetails.has_node_modules) { + //If this dir has a `node_modues` dir, scan it + const nodeModulesDir = path.join(nodeDir, 'node_modules') + const nrModules = scanDirForNodesModules(nodeModulesDir, moduleName ); + results = results.concat(nrModules); + + } else { + //If this is not a node-red module AND it does NOT have a node_modules dir, + //it may be a directory of project directories or a node_modules dir? + //scan this instead + const nrModules = scanDirForNodesModules(nodeDir, moduleName); + results = results.concat(nrModules); + } } } return results; @@ -274,24 +328,26 @@ function getModuleNodeFiles(module) { } function getNodeFiles(disableNodePathScan) { - var dir; // Find all of the nodes to load - var nodeFiles = []; - var results; - - var dir; - var iconList = []; + let results; + let nodesDir; + if(settings.nodesDir) { + nodesDir = Array.isArray(settings.nodesDir) ? settings.nodesDir : [settings.nodesDir] + } + let dir; + let nodeFiles = []; + let iconList = []; if (settings.coreNodesDir) { results = getLocalNodeFiles(path.resolve(settings.coreNodesDir)); nodeFiles = nodeFiles.concat(results.files); iconList = iconList.concat(results.icons); - var defaultLocalesPath = path.join(settings.coreNodesDir,"locales"); + let defaultLocalesPath = path.join(settings.coreNodesDir,"locales"); i18n.registerMessageCatalog("node-red",defaultLocalesPath,"messages.json"); } if (settings.userDir) { dir = path.join(settings.userDir,"lib","icons"); - var icons = scanIconDir(dir); + let icons = scanIconDir(dir); if (icons.length > 0) { iconList.push({path:dir,icons:icons}); } @@ -301,13 +357,9 @@ function getNodeFiles(disableNodePathScan) { nodeFiles = nodeFiles.concat(results.files); iconList = iconList.concat(results.icons); } - if (settings.nodesDir) { - dir = settings.nodesDir; - if (typeof settings.nodesDir == "string") { - dir = [dir]; - } - for (var i=0;i diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.html b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.html new file mode 100644 index 000000000..8f392056c --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js new file mode 100644 index 000000000..624cfe6d8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from loose1.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg new file mode 100644 index 000000000..250348861 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html new file mode 100644 index 000000000..e23e2880a --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js new file mode 100644 index 000000000..e1cde2a82 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from loose2.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js new file mode 100644 index 000000000..e2e0781d1 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from regular module main.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json new file mode 100644 index 000000000..26d4151fb --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json @@ -0,0 +1,19 @@ +{ + "name": "node-red-node-testnode", + "version": "1.0.0", + "description": "A node-red node that does nothing other than exist", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js new file mode 100644 index 000000000..e2e0781d1 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from regular module main.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json new file mode 100644 index 000000000..23cfd7e86 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json @@ -0,0 +1,14 @@ +{ + "name": "regular_node", + "version": "1.0.0", + "description": "A regular node that does nothing other than exist", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "test" + ], + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html new file mode 100644 index 000000000..0e45b3007 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js new file mode 100644 index 000000000..1fe9d89a3 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from @test/testnode index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json new file mode 100644 index 000000000..a32462631 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json @@ -0,0 +1,20 @@ +{ + "name": "@test/testnode", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html new file mode 100644 index 000000000..5d9f2b0ec --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js new file mode 100644 index 000000000..0f0eba392 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from testnode2 index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json new file mode 100644 index 000000000..f9e8aecbe --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json @@ -0,0 +1,20 @@ +{ + "name": "testnode2", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode2": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js new file mode 100644 index 000000000..fb2e22289 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js @@ -0,0 +1,3 @@ +(function() { + console.log("Hi from test plugin client side") +})() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js new file mode 100644 index 000000000..3202aeb8b --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js @@ -0,0 +1,14 @@ +module.exports = function (RED) { + RED.plugins.registerPlugin('test-theme', { + type: 'node-red-theme', + scripts: [ + 'files/clientside.js' + ], + css: [ + 'files/theme.css', + ], + monacoOptions: { + theme: "vs" + } + }) +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css new file mode 100644 index 000000000..872c93e10 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css @@ -0,0 +1 @@ +:root{--red-ui-primary-background: #f2f3fb;} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json new file mode 100644 index 000000000..b8608c175 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json @@ -0,0 +1,24 @@ +{ + "name": "test-theme2", + "version": "0.0.1", + "description": "test theme for Node-RED", + + "keywords": [ + "node-red", + "plugin", + "theme" + ], + "author": { + "name": "testy-McTesterson" + }, + "license": "MIT", + "node-red": { + "version": ">=2.2.0", + "plugins": { + "test-theme2": "files/plugin.js" + } + }, + "engines": { + "node": ">=12.x" + } +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html new file mode 100644 index 000000000..0e45b3007 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js new file mode 100644 index 000000000..1fe9d89a3 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from @test/testnode index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json new file mode 100644 index 000000000..a32462631 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json @@ -0,0 +1,20 @@ +{ + "name": "@test/testnode", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js new file mode 100644 index 000000000..fb2e22289 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js @@ -0,0 +1,3 @@ +(function() { + console.log("Hi from test plugin client side") +})() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js new file mode 100644 index 000000000..3202aeb8b --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js @@ -0,0 +1,14 @@ +module.exports = function (RED) { + RED.plugins.registerPlugin('test-theme', { + type: 'node-red-theme', + scripts: [ + 'files/clientside.js' + ], + css: [ + 'files/theme.css', + ], + monacoOptions: { + theme: "vs" + } + }) +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css new file mode 100644 index 000000000..872c93e10 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css @@ -0,0 +1 @@ +:root{--red-ui-primary-background: #f2f3fb;} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json new file mode 100644 index 000000000..56b1bfb6d --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json @@ -0,0 +1,24 @@ +{ + "name": "@test/test-theme3", + "version": "0.0.1", + "description": "test theme for Node-RED", + + "keywords": [ + "node-red", + "plugin", + "theme" + ], + "author": { + "name": "testy-McTesterson" + }, + "license": "MIT", + "node-red": { + "version": ">=2.2.0", + "plugins": { + "test-theme3": "files/plugin.js" + } + }, + "engines": { + "node": ">=12.x" + } +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html new file mode 100644 index 000000000..a4034f340 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js new file mode 100644 index 000000000..855768ad8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from testnode3 index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json new file mode 100644 index 000000000..41e9bb8f8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json @@ -0,0 +1,20 @@ +{ + "name": "testnode3", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode3": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +}