mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge branch 'node-red:dev' into dev
This commit is contained in:
		
							
								
								
									
										36
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								package.json
									
									
									
									
									
								
							| @@ -26,13 +26,13 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "acorn": "8.7.1", | ||||
|         "acorn": "8.8.1", | ||||
|         "acorn-walk": "8.2.0", | ||||
|         "ajv": "8.11.0", | ||||
|         "async-mutex": "0.3.2", | ||||
|         "ajv": "8.11.2", | ||||
|         "async-mutex": "0.4.0", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.0", | ||||
|         "body-parser": "1.20.1", | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "clone": "2.1.2", | ||||
|         "content-type": "1.0.4", | ||||
| @@ -40,16 +40,16 @@ | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
|         "denque": "2.0.1", | ||||
|         "express": "4.18.1", | ||||
|         "denque": "2.1.0", | ||||
|         "express": "4.18.2", | ||||
|         "express-session": "1.17.3", | ||||
|         "form-data": "4.0.0", | ||||
|         "fs-extra": "10.1.0", | ||||
|         "got": "11.8.5", | ||||
|         "hash-sum": "2.0.0", | ||||
|         "hpagent": "1.0.0", | ||||
|         "hpagent": "1.2.0", | ||||
|         "https-proxy-agent": "5.0.1", | ||||
|         "i18next": "21.8.14", | ||||
|         "i18next": "21.10.0", | ||||
|         "iconv-lite": "0.6.3", | ||||
|         "is-utf8": "0.2.1", | ||||
|         "js-yaml": "4.1.0", | ||||
| @@ -60,7 +60,7 @@ | ||||
|         "memorystore": "1.6.7", | ||||
|         "mime": "3.0.0", | ||||
|         "moment": "2.29.4", | ||||
|         "moment-timezone": "0.5.34", | ||||
|         "moment-timezone": "0.5.39", | ||||
|         "mqtt": "4.3.7", | ||||
|         "multer": "1.4.5-lts.1", | ||||
|         "mustache": "4.2.0", | ||||
| @@ -73,19 +73,19 @@ | ||||
|         "passport-http-bearer": "1.0.1", | ||||
|         "passport-oauth2-client-password": "0.1.2", | ||||
|         "raw-body": "2.5.1", | ||||
|         "semver": "7.3.7", | ||||
|         "tar": "6.1.11", | ||||
|         "tough-cookie": "4.0.0", | ||||
|         "uglify-js": "3.16.2", | ||||
|         "semver": "7.3.8", | ||||
|         "tar": "6.1.12", | ||||
|         "tough-cookie": "4.1.2", | ||||
|         "uglify-js": "3.17.4", | ||||
|         "uuid": "8.3.2", | ||||
|         "ws": "7.5.6", | ||||
|         "xml2js": "0.4.23" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "bcrypt": "5.0.1" | ||||
|         "bcrypt": "5.1.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "dompurify": "2.3.9", | ||||
|         "dompurify": "2.4.1", | ||||
|         "grunt": "1.5.3", | ||||
|         "grunt-chmod": "~1.1.1", | ||||
|         "grunt-cli": "~1.4.3", | ||||
| @@ -108,13 +108,13 @@ | ||||
|         "i18next-http-backend": "1.4.1", | ||||
|         "jquery-i18next": "1.2.1", | ||||
|         "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", | ||||
|         "marked": "4.0.18", | ||||
|         "marked": "4.2.3", | ||||
|         "minami": "1.2.3", | ||||
|         "mocha": "9.2.2", | ||||
|         "node-red-node-test-helper": "^0.3.0", | ||||
|         "nodemon": "2.0.19", | ||||
|         "nodemon": "2.0.20", | ||||
|         "proxy": "^1.0.2", | ||||
|         "sass": "1.53.0", | ||||
|         "sass": "1.56.1", | ||||
|         "should": "13.2.3", | ||||
|         "sinon": "11.1.2", | ||||
|         "stoppable": "^1.1.0", | ||||
|   | ||||
| @@ -19,11 +19,11 @@ | ||||
|         "@node-red/util": "3.1.0-beta.0", | ||||
|         "@node-red/editor-client": "3.1.0-beta.0", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.0", | ||||
|         "body-parser": "1.20.1", | ||||
|         "clone": "2.1.2", | ||||
|         "cors": "2.8.5", | ||||
|         "express-session": "1.17.3", | ||||
|         "express": "4.18.1", | ||||
|         "express": "4.18.2", | ||||
|         "memorystore": "1.6.7", | ||||
|         "mime": "3.0.0", | ||||
|         "multer": "1.4.5-lts.1", | ||||
| @@ -35,6 +35,6 @@ | ||||
|         "ws": "7.5.6" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "bcrypt": "5.0.1" | ||||
|         "bcrypt": "5.1.0" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -57,18 +57,22 @@ | ||||
|         "addFlowToRight": "Add flow to the right", | ||||
|         "hideFlow": "Hide flow", | ||||
|         "hideOtherFlows": "Hide other flows", | ||||
|         "showAllFlows": "Show all flows", | ||||
|         "showAllFlows": "Show all flows (__count__ hidden)", | ||||
|         "hideAllFlows": "Hide all flows", | ||||
|         "hiddenFlows": "List __count__ hidden flow", | ||||
|         "hiddenFlows_plural": "List __count__ hidden flows", | ||||
|         "showLastHiddenFlow": "Show last hidden flow", | ||||
|         "showLastHiddenFlow": "Reopen hidden flow", | ||||
|         "listFlows": "List flows", | ||||
|         "listSubflows": "List subflows", | ||||
|         "status": "Status", | ||||
|         "enabled": "Enabled", | ||||
|         "disabled": "Disabled", | ||||
|         "info": "Description", | ||||
|         "selectNodes": "Click nodes to select" | ||||
|         "selectNodes": "Click nodes to select", | ||||
|         "enableFlow": "Enable flow", | ||||
|         "disableFlow": "Disable flow", | ||||
|         "moveToStart": "Move flow to start", | ||||
|         "moveToEnd": "Move flow to end" | ||||
|     }, | ||||
|     "menu": { | ||||
|         "label": { | ||||
|   | ||||
| @@ -2834,7 +2834,7 @@ RED.nodes = (function() { | ||||
|         }, | ||||
|         addWorkspace: addWorkspace, | ||||
|         removeWorkspace: removeWorkspace, | ||||
|         getWorkspaceOrder: function() { return workspacesOrder }, | ||||
|         getWorkspaceOrder: function() { return [...workspacesOrder] }, | ||||
|         setWorkspaceOrder: function(order) { workspacesOrder = order; }, | ||||
|         workspace: getWorkspace, | ||||
|  | ||||
|   | ||||
| @@ -423,11 +423,10 @@ RED.clipboard = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function showImportNodes(mode) { | ||||
|     function showImportNodes(library = 'clipboard') { | ||||
|         if (disabled) { | ||||
|             return; | ||||
|         } | ||||
|         mode = mode || "clipboard"; | ||||
|  | ||||
|         dialogContainer.empty(); | ||||
|         dialogContainer.append($(importNodesDialog)); | ||||
| @@ -533,8 +532,8 @@ RED.clipboard = (function() { | ||||
|             $("#red-ui-clipboard-dialog-import-file-upload").trigger("click"); | ||||
|         }) | ||||
|  | ||||
|         tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+mode); | ||||
|         if (mode === 'clipboard') { | ||||
|         tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+library); | ||||
|         if (library === 'clipboard') { | ||||
|             setTimeout(function() { | ||||
|                 $("#red-ui-clipboard-dialog-import-text").trigger("focus"); | ||||
|             },100) | ||||
| @@ -558,13 +557,16 @@ RED.clipboard = (function() { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function showExportNodes(mode) { | ||||
|     /** | ||||
|      * Show the export dialog | ||||
|      * @params library which export destination to show | ||||
|      * @params mode whether to default to 'auto' (default) or 'flow' | ||||
|      **/ | ||||
|     function showExportNodes(library = 'clipboard', mode = 'auto' ) { | ||||
|         if (disabled) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         mode = mode || "clipboard"; | ||||
|  | ||||
|         dialogContainer.empty(); | ||||
|         dialogContainer.append($(exportNodesDialog)); | ||||
|  | ||||
| @@ -766,12 +768,15 @@ RED.clipboard = (function() { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (mode === 'flow' && !$("#red-ui-clipboard-dialog-export-rng-flow").hasClass('disabled')) { | ||||
|             $("#red-ui-clipboard-dialog-export-rng-flow").trigger("click"); | ||||
|         } | ||||
|         if (format === "red-ui-clipboard-dialog-export-fmt-full") { | ||||
|             $("#red-ui-clipboard-dialog-export-fmt-full").trigger("click"); | ||||
|         } else { | ||||
|             $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click"); | ||||
|         } | ||||
|         tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode); | ||||
|         tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+library); | ||||
|  | ||||
|         var dialogHeight = 400; | ||||
|         var winHeight = $(window).height(); | ||||
|   | ||||
| @@ -94,8 +94,8 @@ RED.menu = (function() { | ||||
|  | ||||
|             var link = $(linkContent).appendTo(item); | ||||
|             opt.link = link; | ||||
|             if (typeof opt.onselect === 'string') { | ||||
|                 var shortcut = RED.keyboard.getShortcut(opt.onselect); | ||||
|             if (typeof opt.onselect === 'string' || opt.shortcut) { | ||||
|                 var shortcut = opt.shortcut || RED.keyboard.getShortcut(opt.onselect); | ||||
|                 if (shortcut && shortcut.key) { | ||||
|                     opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label")); | ||||
|                 } | ||||
|   | ||||
| @@ -141,7 +141,29 @@ RED.tabs = (function() { | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|  | ||||
|         if (options.contextmenu) { | ||||
|             wrapper.on('contextmenu', function(evt) { | ||||
|                 let clickedTab | ||||
|                 let target = evt.target | ||||
|                 while(target.nodeName !== 'A' && target.nodeName !== 'UL' && target.nodeName !== 'BODY') { | ||||
|                     target = target.parentNode | ||||
|                 } | ||||
|                 if (target.nodeName === 'A') { | ||||
|                     const href = target.getAttribute('href') | ||||
|                     if (href) { | ||||
|                         clickedTab = tabs[href.slice(1)] | ||||
|                     } | ||||
|                 } | ||||
|                 evt.preventDefault() | ||||
|                 evt.stopPropagation() | ||||
|                 RED.contextMenu.show({ | ||||
|                     x:evt.clientX-5, | ||||
|                     y:evt.clientY-5, | ||||
|                     options: options.contextmenu(clickedTab) | ||||
|                 }) | ||||
|                 return false | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|         var scrollLeft; | ||||
|         var scrollRight; | ||||
| @@ -809,17 +831,17 @@ RED.tabs = (function() { | ||||
|                     }); | ||||
|                     RED.popover.tooltip(closeLink,RED._("workspace.hideFlow")); | ||||
|                 } | ||||
|                 if (tab.hideable) { | ||||
|                     li.addClass("red-ui-tabs-closeable") | ||||
|                     var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li); | ||||
|                     closeLink.append('<i class="fa fa-eye" />'); | ||||
|                     closeLink.append('<i class="fa fa-eye-slash" />'); | ||||
|                     closeLink.on("click",function(event) { | ||||
|                         event.preventDefault(); | ||||
|                         hideTab(tab.id); | ||||
|                     }); | ||||
|                     RED.popover.tooltip(closeLink,RED._("workspace.hideFlow")); | ||||
|                 } | ||||
|                 // if (tab.hideable) { | ||||
|                 //     li.addClass("red-ui-tabs-closeable") | ||||
|                 //     var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li); | ||||
|                 //     closeLink.append('<i class="fa fa-eye" />'); | ||||
|                 //     closeLink.append('<i class="fa fa-eye-slash" />'); | ||||
|                 //     closeLink.on("click",function(event) { | ||||
|                 //         event.preventDefault(); | ||||
|                 //         hideTab(tab.id); | ||||
|                 //     }); | ||||
|                 //     RED.popover.tooltip(closeLink,RED._("workspace.hideFlow")); | ||||
|                 // } | ||||
|  | ||||
|                 var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li); | ||||
|                 if (options.onselect) { | ||||
| @@ -938,6 +960,9 @@ RED.tabs = (function() { | ||||
|             activeIndex: function() { | ||||
|                 return ul.find("li.active").index() | ||||
|             }, | ||||
|             getTabIndex: function (id) { | ||||
|                 return ul.find("a[href='#"+id+"']").parent().index() | ||||
|             }, | ||||
|             contains: function(id) { | ||||
|                 return ul.find("a[href='#"+id+"']").length > 0; | ||||
|             }, | ||||
|   | ||||
| @@ -1,21 +1,6 @@ | ||||
| RED.contextMenu = (function () { | ||||
|  | ||||
|     let menu; | ||||
|     function createMenu() { | ||||
|         // menu = RED.popover.menu({ | ||||
|         //     options: [ | ||||
|         //         { | ||||
|         //             label: 'delete selection', | ||||
|         //             onselect: function() { | ||||
|         //                 RED.actions.invoke('core:delete-selection') | ||||
|         //                 RED.view.focus() | ||||
|         //             } | ||||
|         //         }, | ||||
|         //         { label: 'world' } | ||||
|         //     ], | ||||
|         //     width: 200, | ||||
|         // }) | ||||
|     } | ||||
|  | ||||
|     function disposeMenu() { | ||||
|         $(document).off("mousedown.red-ui-workspace-context-menu"); | ||||
| @@ -28,114 +13,118 @@ RED.contextMenu = (function () { | ||||
|         if (menu) { | ||||
|             menu.remove() | ||||
|         } | ||||
|         let menuItems = [] | ||||
|         if (options.options) { | ||||
|             menuItems = options.options | ||||
|         } else if (options.type === 'workspace') { | ||||
|             const selection = RED.view.selection() | ||||
|             const noSelection = !selection || Object.keys(selection).length === 0 | ||||
|             const hasSelection = (selection.nodes && selection.nodes.length > 0); | ||||
|             const hasMultipleSelection = hasSelection && selection.nodes.length > 1; | ||||
|             const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || []; | ||||
|             const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || []; | ||||
|             const hasLinks = wireLinks.length > 0; | ||||
|             const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1 | ||||
|             const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1 | ||||
|             const canDelete = hasSelection || hasLinks | ||||
|             const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' | ||||
|  | ||||
|         const selection = RED.view.selection() | ||||
|         const noSelection = !selection || Object.keys(selection).length === 0 | ||||
|         const hasSelection = (selection.nodes && selection.nodes.length > 0); | ||||
|         const hasMultipleSelection = hasSelection && selection.nodes.length > 1; | ||||
|         const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || []; | ||||
|         const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || []; | ||||
|         const hasLinks = wireLinks.length > 0; | ||||
|         const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1 | ||||
|         const isMultipleLinks = !hasSelection && hasLinks && wireLinks.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 offset = $("#red-ui-workspace-chart").offset() | ||||
|  | ||||
|         let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() | ||||
|         let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() | ||||
|  | ||||
|         if (RED.view.snapGrid) { | ||||
|             const gridSize = RED.view.gridSize() | ||||
|             addX = gridSize * Math.floor(addX / gridSize) | ||||
|             addY = gridSize * Math.floor(addY / gridSize) | ||||
|         } | ||||
|  | ||||
|         const menuItems = [ | ||||
|             { onselect: 'core:show-action-list', onpostselect: function () { } }, | ||||
|             { | ||||
|                 label: RED._("contextMenu.insert"), | ||||
|                 options: [ | ||||
|                     { | ||||
|                         label: RED._("contextMenu.node"), | ||||
|                         onselect: function () { | ||||
|                             RED.view.showQuickAddDialog({ | ||||
|                                 position: [addX, addY], | ||||
|                                 touchTrigger: true, | ||||
|                                 splice: isSingleLink ? selection.links[0] : undefined, | ||||
|                                 // spliceMultiple: isMultipleLinks | ||||
|                             }) | ||||
|                         } | ||||
|                     }, | ||||
|                     (hasLinks) ? { // has least 1 wire selected | ||||
|                         label: RED._("contextMenu.junction"), | ||||
|                         onselect: 'core:split-wires-with-junctions', | ||||
|                         disabled: !hasLinks | ||||
|                     } : { | ||||
|                         label: RED._("contextMenu.junction"), | ||||
|                         onselect: function () { | ||||
|                             const nn = { | ||||
|                                 _def: { defaults: {} }, | ||||
|                                 type: 'junction', | ||||
|                                 z: RED.workspaces.active(), | ||||
|                                 id: RED.nodes.id(), | ||||
|                                 x: addX, | ||||
|                                 y: addY, | ||||
|                                 w: 0, h: 0, | ||||
|                                 outputs: 1, | ||||
|                                 inputs: 1, | ||||
|                                 dirty: true | ||||
|                             } | ||||
|                             const historyEvent = { | ||||
|                                 dirty: RED.nodes.dirty(), | ||||
|                                 t: 'add', | ||||
|                                 junctions: [nn] | ||||
|                             } | ||||
|                             RED.nodes.addJunction(nn); | ||||
|                             RED.history.push(historyEvent); | ||||
|                             RED.nodes.dirty(true); | ||||
|                             RED.view.select({nodes: [nn] }); | ||||
|                             RED.view.redraw(true) | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         label: RED._("contextMenu.linkNodes"), | ||||
|                         onselect: 'core:split-wire-with-link-nodes', | ||||
|                         disabled: !hasLinks | ||||
|                     } | ||||
|                 ] | ||||
|  | ||||
|             const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g | ||||
|             const offset = $("#red-ui-workspace-chart").offset() | ||||
|  | ||||
|             let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() | ||||
|             let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() | ||||
|  | ||||
|             if (RED.view.snapGrid) { | ||||
|                 const gridSize = RED.view.gridSize() | ||||
|                 addX = gridSize * Math.floor(addX / gridSize) | ||||
|                 addY = gridSize * Math.floor(addY / gridSize) | ||||
|             } | ||||
|         ] | ||||
|  | ||||
|         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' } | ||||
|         ) | ||||
|             menuItems.push( | ||||
|                 { onselect: 'core:show-action-list', onpostselect: function () { } }, | ||||
|                 { | ||||
|                     label: RED._("contextMenu.insert"), | ||||
|                     options: [ | ||||
|                         { | ||||
|                             label: RED._("contextMenu.node"), | ||||
|                             onselect: function () { | ||||
|                                 RED.view.showQuickAddDialog({ | ||||
|                                     position: [addX, addY], | ||||
|                                     touchTrigger: true, | ||||
|                                     splice: isSingleLink ? selection.links[0] : undefined, | ||||
|                                     // spliceMultiple: isMultipleLinks | ||||
|                                 }) | ||||
|                             } | ||||
|                         }, | ||||
|                         (hasLinks) ? { // has least 1 wire selected | ||||
|                             label: RED._("contextMenu.junction"), | ||||
|                             onselect: 'core:split-wires-with-junctions', | ||||
|                             disabled: !hasLinks | ||||
|                         } : { | ||||
|                             label: RED._("contextMenu.junction"), | ||||
|                             onselect: function () { | ||||
|                                 const nn = { | ||||
|                                     _def: { defaults: {} }, | ||||
|                                     type: 'junction', | ||||
|                                     z: RED.workspaces.active(), | ||||
|                                     id: RED.nodes.id(), | ||||
|                                     x: addX, | ||||
|                                     y: addY, | ||||
|                                     w: 0, h: 0, | ||||
|                                     outputs: 1, | ||||
|                                     inputs: 1, | ||||
|                                     dirty: true | ||||
|                                 } | ||||
|                                 const historyEvent = { | ||||
|                                     dirty: RED.nodes.dirty(), | ||||
|                                     t: 'add', | ||||
|                                     junctions: [nn] | ||||
|                                 } | ||||
|                                 RED.nodes.addJunction(nn); | ||||
|                                 RED.history.push(historyEvent); | ||||
|                                 RED.nodes.dirty(true); | ||||
|                                 RED.view.select({nodes: [nn] }); | ||||
|                                 RED.view.redraw(true) | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             label: RED._("contextMenu.linkNodes"), | ||||
|                             onselect: 'core:split-wire-with-link-nodes', | ||||
|                             disabled: !hasLinks | ||||
|                         } | ||||
|                     ] | ||||
|  | ||||
|  | ||||
|  | ||||
|                 } | ||||
|             ) | ||||
|  | ||||
|         if (hasSelection) { | ||||
|             menuItems.push( | ||||
|                 null, | ||||
|                 isGroup ? | ||||
|                     { onselect: 'core:ungroup-selection', disabled: !isGroup } | ||||
|                     : { onselect: 'core:group-selection', disabled: !hasSelection } | ||||
|                 { 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 (canRemoveFromGroup) { | ||||
|                 menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }) | ||||
|             } | ||||
|  | ||||
|             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") }) | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var direction = "right"; | ||||
| @@ -144,7 +133,7 @@ RED.contextMenu = (function () { | ||||
|             ($(window).width() -MENU_WIDTH)) { | ||||
|             direction = "left"; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         menu = RED.menu.init({ | ||||
|             direction: direction, | ||||
|             onpreselect: function() { | ||||
|   | ||||
| @@ -431,44 +431,7 @@ RED.subflow = (function() { | ||||
|  | ||||
|         $("#red-ui-subflow-delete").on("click", function(event) { | ||||
|             event.preventDefault(); | ||||
|             var subflow = RED.nodes.subflow(RED.workspaces.active()); | ||||
|             if (subflow.instances.length > 0) { | ||||
|                 var msg = $('<div>') | ||||
|                 $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); | ||||
|                 $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg); | ||||
|                 var confirmDeleteNotification = RED.notify(msg, { | ||||
|                     modal: true, | ||||
|                     fixed: true, | ||||
|                     buttons: [ | ||||
|                         { | ||||
|                             text: RED._('common.label.cancel'), | ||||
|                             click: function() { | ||||
|                                 confirmDeleteNotification.close(); | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             text: RED._('workspace.confirmDelete'), | ||||
|                             class: "primary", | ||||
|                             click: function() { | ||||
|                                 confirmDeleteNotification.close(); | ||||
|                                 completeDelete(); | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 }); | ||||
|  | ||||
|                 return; | ||||
|             } else { | ||||
|                 completeDelete(); | ||||
|             } | ||||
|             function completeDelete() { | ||||
|                 var startDirty = RED.nodes.dirty(); | ||||
|                 var historyEvent = removeSubflow(RED.workspaces.active()); | ||||
|                 historyEvent.t = 'delete'; | ||||
|                 historyEvent.dirty = startDirty; | ||||
|                 RED.history.push(historyEvent); | ||||
|             } | ||||
|  | ||||
|             RED.subflow.delete(RED.workspaces.active()) | ||||
|         }); | ||||
|  | ||||
|         refreshToolbar(activeSubflow); | ||||
| @@ -481,7 +444,48 @@ RED.subflow = (function() { | ||||
|         $("#red-ui-workspace-toolbar").hide().empty(); | ||||
|         $("#red-ui-workspace-chart").css({"margin-top": "0"}); | ||||
|     } | ||||
|     function deleteSubflow(id) { | ||||
|         const subflow = RED.nodes.subflow(id || RED.workspaces.active()); | ||||
|         if (!subflow) { | ||||
|             return | ||||
|         } | ||||
|         if (subflow.instances.length > 0) { | ||||
|             const msg = $('<div>') | ||||
|             $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); | ||||
|             $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg); | ||||
|             const confirmDeleteNotification = RED.notify(msg, { | ||||
|                 modal: true, | ||||
|                 fixed: true, | ||||
|                 buttons: [ | ||||
|                     { | ||||
|                         text: RED._('common.label.cancel'), | ||||
|                         click: function() { | ||||
|                             confirmDeleteNotification.close(); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         text: RED._('workspace.confirmDelete'), | ||||
|                         class: "primary", | ||||
|                         click: function() { | ||||
|                             confirmDeleteNotification.close(); | ||||
|                             completeDelete(); | ||||
|                         } | ||||
|                     } | ||||
|                 ] | ||||
|             }); | ||||
|  | ||||
|             return; | ||||
|         } else { | ||||
|             completeDelete(); | ||||
|         } | ||||
|         function completeDelete() { | ||||
|             const startDirty = RED.nodes.dirty(); | ||||
|             const historyEvent = removeSubflow(subflow.id); | ||||
|             historyEvent.t = 'delete'; | ||||
|             historyEvent.dirty = startDirty; | ||||
|             RED.history.push(historyEvent); | ||||
|         } | ||||
|     } | ||||
|     function removeSubflow(id, keepInstanceNodes) { | ||||
|         // TODO:  A lot of this logic is common with RED.nodes.removeWorkspace | ||||
|         var removedNodes = []; | ||||
| @@ -1323,7 +1327,10 @@ RED.subflow = (function() { | ||||
|         init: init, | ||||
|         createSubflow: createSubflow, | ||||
|         convertToSubflow: convertToSubflow, | ||||
|         // removeSubflow: Internal function to remove subflow | ||||
|         removeSubflow: removeSubflow, | ||||
|         // delete: Prompt user for confirmation | ||||
|         delete: deleteSubflow, | ||||
|         refresh: refresh, | ||||
|         removeInput: removeSubflowInput, | ||||
|         removeOutput: removeSubflowOutput, | ||||
|   | ||||
| @@ -211,6 +211,7 @@ RED.view = (function() { | ||||
|             evt.preventDefault() | ||||
|             evt.stopPropagation() | ||||
|             RED.contextMenu.show({ | ||||
|                 type: 'workspace', | ||||
|                 x:evt.clientX-5, | ||||
|                 y:evt.clientY-5 | ||||
|             }) | ||||
|   | ||||
| @@ -126,6 +126,166 @@ RED.workspaces = (function() { | ||||
|  | ||||
|     var workspace_tabs; | ||||
|     var workspaceTabCount = 0; | ||||
|  | ||||
|     function getMenuItems(isMenuButton, tab) { | ||||
|         let hiddenFlows = new Set() | ||||
|         for (let i = 0; i < hideStack.length; i++) { | ||||
|             let ids = hideStack[i] | ||||
|             if (!Array.isArray(ids)) { | ||||
|                 ids = [ids] | ||||
|             } | ||||
|             ids.forEach(id => { | ||||
|                 if (RED.nodes.workspace(id)) { | ||||
|                     hiddenFlows.add(id) | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         const hiddenflowCount = hiddenFlows.size; | ||||
|         let activeWorkspace = tab || RED.nodes.workspace(RED.workspaces.active()) || RED.nodes.subflow(RED.workspaces.active()) | ||||
|         let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false | ||||
|  | ||||
|         var menuItems = [] | ||||
|         if (isMenuButton) { | ||||
|             menuItems.push({ | ||||
|                 id:"red-ui-tabs-menu-option-search-flows", | ||||
|                 label: RED._("workspace.listFlows"), | ||||
|                 onselect: "core:list-flows" | ||||
|             }, | ||||
|             { | ||||
|                 id:"red-ui-tabs-menu-option-search-subflows", | ||||
|                 label: RED._("workspace.listSubflows"), | ||||
|                 onselect: "core:list-subflows" | ||||
|             }, | ||||
|             null) | ||||
|         } | ||||
|         menuItems.push( | ||||
|             { | ||||
|                 id:"red-ui-tabs-menu-option-add-flow", | ||||
|                 label: RED._("workspace.addFlow"), | ||||
|                 onselect: "core:add-flow" | ||||
|             } | ||||
|         ) | ||||
|         if (isMenuButton || !!tab) { | ||||
|             menuItems.push( | ||||
|                 { | ||||
|                     id:"red-ui-tabs-menu-option-add-flow-right", | ||||
|                     label: RED._("workspace.addFlowToRight"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"), | ||||
|                     onselect: function() { | ||||
|                         RED.actions.invoke("core:add-flow-to-right", tab) | ||||
|                     } | ||||
|                 }, | ||||
|                 null | ||||
|             ) | ||||
|             if (activeWorkspace && activeWorkspace.type === 'tab') { | ||||
|                 menuItems.push( | ||||
|                     isFlowDisabled ? { | ||||
|                         label: RED._("workspace.enableFlow"), | ||||
|                         shortcut: RED.keyboard.getShortcut("core:enable-flow"), | ||||
|                         onselect: function() { | ||||
|                             RED.actions.invoke("core:enable-flow", tab?tab.id:undefined) | ||||
|                         } | ||||
|                     } : { | ||||
|                         label: RED._("workspace.disableFlow"), | ||||
|                         shortcut: RED.keyboard.getShortcut("core:disable-flow"), | ||||
|                         onselect: function() { | ||||
|                             RED.actions.invoke("core:disable-flow", tab?tab.id:undefined) | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|             const currentTabs = workspace_tabs.listTabs() | ||||
|             const activeIndex = currentTabs.findIndex(id => id === activeWorkspace.id) | ||||
|             menuItems.push( | ||||
|                 { | ||||
|                     label: RED._("workspace.moveToStart"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:move-flow-to-start"), | ||||
|                     onselect: function() { | ||||
|                         RED.actions.invoke("core:move-flow-to-start", tab?tab.id:undefined) | ||||
|                     }, | ||||
|                     disabled: activeIndex === 0 | ||||
|                 }, | ||||
|                 { | ||||
|                     label: RED._("workspace.moveToEnd"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:move-flow-to-end"), | ||||
|                     onselect: function() { | ||||
|                         RED.actions.invoke("core:move-flow-to-end", tab?tab.id:undefined) | ||||
|                     }, | ||||
|                     disabled: activeIndex === currentTabs.length - 1 | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|         menuItems.push(null) | ||||
|         if (isMenuButton || !!tab) { | ||||
|             menuItems.push( | ||||
|                 { | ||||
|                     id:"red-ui-tabs-menu-option-add-hide-flows", | ||||
|                     label: RED._("workspace.hideFlow"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:hide-flow"), | ||||
|                     onselect: function() { | ||||
|                         RED.actions.invoke("core:hide-flow", tab) | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     id:"red-ui-tabs-menu-option-add-hide-other-flows", | ||||
|                     label: RED._("workspace.hideOtherFlows"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:hide-other-flows"), | ||||
|                     onselect: function() { | ||||
|                         RED.actions.invoke("core:hide-other-flows", tab) | ||||
|                     } | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|         menuItems.push( | ||||
|             { | ||||
|                 id:"red-ui-tabs-menu-option-add-hide-all-flows", | ||||
|                 label: RED._("workspace.hideAllFlows"), | ||||
|                 onselect: "core:hide-all-flows" | ||||
|             }, | ||||
|             { | ||||
|                 id:"red-ui-tabs-menu-option-add-show-all-flows", | ||||
|                 disabled: hiddenflowCount === 0, | ||||
|                 label: RED._("workspace.showAllFlows", { count: hiddenflowCount }), | ||||
|                 onselect: "core:show-all-flows" | ||||
|             }, | ||||
|             { | ||||
|                 id:"red-ui-tabs-menu-option-add-show-last-flow", | ||||
|                 disabled: hideStack.length === 0, | ||||
|                 label: RED._("workspace.showLastHiddenFlow"), | ||||
|                 onselect: "core:show-last-hidden-flow" | ||||
|             } | ||||
|         ) | ||||
|         if (tab) { | ||||
|             menuItems.push( | ||||
|                 null, | ||||
|                 { | ||||
|                     label: RED._("common.label.delete"), | ||||
|                     onselect: function() { | ||||
|                         if (tab.type === 'tab') { | ||||
|                             RED.workspaces.delete(tab) | ||||
|                         } else if (tab.type === 'subflow') { | ||||
|                             RED.subflow.delete(tab.id) | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     label: RED._("menu.label.export"), | ||||
|                     shortcut: RED.keyboard.getShortcut("core:show-export-dialog"), | ||||
|                     onselect: function() { | ||||
|                         RED.workspaces.show(tab.id) | ||||
|                         RED.actions.invoke('core:show-export-dialog', null, 'flow') | ||||
|                     } | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|         // if (isMenuButton && hiddenflowCount > 0) { | ||||
|         //     menuItems.unshift({ | ||||
|         //         label: RED._("workspace.hiddenFlows",{count: hiddenflowCount}), | ||||
|         //         onselect: "core:list-hidden-flows" | ||||
|         //     }) | ||||
|         // } | ||||
|         return menuItems; | ||||
|     } | ||||
|     function createWorkspaceTabs() { | ||||
|         workspace_tabs = RED.tabs.create({ | ||||
|             id: "red-ui-workspace-tabs", | ||||
| @@ -189,13 +349,19 @@ RED.workspaces = (function() { | ||||
|                 RED.history.push({ | ||||
|                     t:'reorder', | ||||
|                     workspaces: { | ||||
|                         from:oldOrder, | ||||
|                         to:newOrder | ||||
|                         from: oldOrder, | ||||
|                         to: newOrder | ||||
|                     }, | ||||
|                     dirty:RED.nodes.dirty() | ||||
|                 }); | ||||
|                 RED.nodes.dirty(true); | ||||
|                 setWorkspaceOrder(newOrder); | ||||
|                 // Only mark flows dirty if flow-order has changed (excluding subflows) | ||||
|                 const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id)) | ||||
|                 const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id)) | ||||
|  | ||||
|                 if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) { | ||||
|                     RED.nodes.dirty(true); | ||||
|                     setWorkspaceOrder(newOrder); | ||||
|                 } | ||||
|             }, | ||||
|             onselect: function(selectedTabs) { | ||||
|                 RED.view.select(false) | ||||
| @@ -214,12 +380,12 @@ RED.workspaces = (function() { | ||||
|             }, | ||||
|             onhide: function(tab) { | ||||
|                 hideStack.push(tab.id); | ||||
|  | ||||
|                 var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}"); | ||||
|                 hiddenTabs[tab.id] = true; | ||||
|                 RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs)); | ||||
|  | ||||
|                 RED.events.emit("workspace:hide",{workspace: tab.id}) | ||||
|                 if (tab.type === "tab") { | ||||
|                     var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}"); | ||||
|                     hiddenTabs[tab.id] = true; | ||||
|                     RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs)); | ||||
|                     RED.events.emit("workspace:hide",{workspace: tab.id}) | ||||
|                 } | ||||
|             }, | ||||
|             onshow: function(tab) { | ||||
|                 removeFromHideStack(tab.id); | ||||
| @@ -234,77 +400,8 @@ RED.workspaces = (function() { | ||||
|             scrollable: true, | ||||
|             addButton: "core:add-flow", | ||||
|             addButtonCaption: RED._("workspace.addFlow"), | ||||
|             menu: function() { | ||||
|                 var menuItems = [ | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-search-flows", | ||||
|                         label: RED._("workspace.listFlows"), | ||||
|                         onselect: "core:list-flows" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-search-subflows", | ||||
|                         label: RED._("workspace.listSubflows"), | ||||
|                         onselect: "core:list-subflows" | ||||
|                     }, | ||||
|                     null, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-flow", | ||||
|                         label: RED._("workspace.addFlow"), | ||||
|                         onselect: "core:add-flow" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-flow-right", | ||||
|                         label: RED._("workspace.addFlowToRight"), | ||||
|                         onselect: "core:add-flow-to-right" | ||||
|                     }, | ||||
|                     null, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-hide-flows", | ||||
|                         label: RED._("workspace.hideFlow"), | ||||
|                         onselect: "core:hide-flow" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-hide-other-flows", | ||||
|                         label: RED._("workspace.hideOtherFlows"), | ||||
|                         onselect: "core:hide-other-flows" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-show-all-flows", | ||||
|                         label: RED._("workspace.showAllFlows"), | ||||
|                         onselect: "core:show-all-flows" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-hide-all-flows", | ||||
|                         label: RED._("workspace.hideAllFlows"), | ||||
|                         onselect: "core:hide-all-flows" | ||||
|                     }, | ||||
|                     { | ||||
|                         id:"red-ui-tabs-menu-option-add-show-last-flow", | ||||
|                         label: RED._("workspace.showLastHiddenFlow"), | ||||
|                         onselect: "core:show-last-hidden-flow" | ||||
|                     } | ||||
|                 ] | ||||
|                 let hiddenFlows = new Set() | ||||
|                 for (let i = 0; i < hideStack.length; i++) { | ||||
|                     let ids = hideStack[i] | ||||
|                     if (!Array.isArray(ids)) { | ||||
|                         ids = [ids] | ||||
|                     } | ||||
|                     ids.forEach(id => { | ||||
|                         if (RED.nodes.workspace(id)) { | ||||
|                             hiddenFlows.add(id) | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
|                 const flowCount = hiddenFlows.size; | ||||
|                 if (flowCount > 0) { | ||||
|                     menuItems.unshift({ | ||||
|                         label: RED._("workspace.hiddenFlows",{count: flowCount}), | ||||
|                         onselect: "core:list-hidden-flows" | ||||
|                     }) | ||||
|                 } | ||||
|                 return menuItems; | ||||
|             } | ||||
|             menu: function() { return getMenuItems(true) }, | ||||
|             contextmenu: function(tab) { return getMenuItems(false, tab) } | ||||
|         }); | ||||
|         workspaceTabCount = 0; | ||||
|     } | ||||
| @@ -355,16 +452,31 @@ RED.workspaces = (function() { | ||||
|         }); | ||||
|  | ||||
|         RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)}); | ||||
|         RED.actions.add("core:add-flow-to-right",function(opts) { addWorkspace(undefined,undefined,workspace_tabs.activeIndex()+1)}); | ||||
|         RED.actions.add("core:add-flow-to-right",function(workspace) { | ||||
|             let index | ||||
|             if (workspace) { | ||||
|                 index = workspace_tabs.getTabIndex(workspace.id)+1 | ||||
|             } else { | ||||
|                 index = workspace_tabs.activeIndex()+1 | ||||
|             } | ||||
|             addWorkspace(undefined,undefined,index) | ||||
|         }); | ||||
|         RED.actions.add("core:edit-flow",editWorkspace); | ||||
|         RED.actions.add("core:remove-flow",removeWorkspace); | ||||
|         RED.actions.add("core:enable-flow",enableWorkspace); | ||||
|         RED.actions.add("core:disable-flow",disableWorkspace); | ||||
|         RED.actions.add("core:move-flow-to-start", function(id) { moveWorkspace(id, 'start') }); | ||||
|         RED.actions.add("core:move-flow-to-end", function(id) { moveWorkspace(id, 'end') }); | ||||
|  | ||||
|         RED.actions.add("core:hide-flow", function() { | ||||
|             var selection = workspace_tabs.selection(); | ||||
|             if (selection.length === 0) { | ||||
|                 selection = [{id:activeWorkspace}] | ||||
|         RED.actions.add("core:hide-flow", function(workspace) { | ||||
|             let selection | ||||
|             if (workspace) { | ||||
|                 selection = [workspace] | ||||
|             } else { | ||||
|                 selection = workspace_tabs.selection(); | ||||
|                 if (selection.length === 0) { | ||||
|                     selection = [{id:activeWorkspace}] | ||||
|                 } | ||||
|             } | ||||
|             var hiddenTabs = []; | ||||
|             selection.forEach(function(ws) { | ||||
| @@ -378,10 +490,15 @@ RED.workspaces = (function() { | ||||
|             workspace_tabs.clearSelection(); | ||||
|         }) | ||||
|  | ||||
|         RED.actions.add("core:hide-other-flows", function() { | ||||
|             var selection = workspace_tabs.selection(); | ||||
|             if (selection.length === 0) { | ||||
|                 selection = [{id:activeWorkspace}] | ||||
|         RED.actions.add("core:hide-other-flows", function(workspace) { | ||||
|             let selection | ||||
|             if (workspace) { | ||||
|                 selection = [workspace] | ||||
|             } else { | ||||
|                 selection = workspace_tabs.selection(); | ||||
|                 if (selection.length === 0) { | ||||
|                     selection = [{id:activeWorkspace}] | ||||
|                 } | ||||
|             } | ||||
|             var selected = new Set(selection.map(function(ws) { return ws.id })) | ||||
|  | ||||
| @@ -535,16 +652,46 @@ RED.workspaces = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function moveWorkspace(id, direction) { | ||||
|         const workspace = RED.nodes.workspace(id||activeWorkspace) || RED.nodes.subflow(id||activeWorkspace); | ||||
|         if (!workspace) { | ||||
|             return; | ||||
|         } | ||||
|         const currentOrder = workspace_tabs.listTabs() | ||||
|         const oldOrder = [...currentOrder] | ||||
|         const currentIndex = currentOrder.findIndex(id => id === workspace.id) | ||||
|         currentOrder.splice(currentIndex, 1) | ||||
|         if (direction === 'start') { | ||||
|             currentOrder.unshift(workspace.id) | ||||
|         } else if (direction === 'end') { | ||||
|             currentOrder.push(workspace.id) | ||||
|         } | ||||
|         const newOrder = setWorkspaceOrder(currentOrder) | ||||
|         if (JSON.stringify(newOrder) !== JSON.stringify(oldOrder)) { | ||||
|             RED.history.push({ | ||||
|                 t:'reorder', | ||||
|                 workspaces: { | ||||
|                     from:oldOrder, | ||||
|                     to:newOrder | ||||
|                 }, | ||||
|                 dirty:RED.nodes.dirty() | ||||
|             }); | ||||
|             const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id)) | ||||
|             const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id)) | ||||
|             if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) { | ||||
|                 RED.nodes.dirty(true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     function setWorkspaceOrder(order) { | ||||
|         var newOrder = order.filter(function(id) { | ||||
|             return RED.nodes.workspace(id) !== undefined; | ||||
|         }) | ||||
|         var newOrder = order.filter(id => !!RED.nodes.workspace(id)) | ||||
|         var currentOrder = RED.nodes.getWorkspaceOrder(); | ||||
|         if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) { | ||||
|             RED.nodes.setWorkspaceOrder(newOrder); | ||||
|             RED.events.emit("flows:reorder",newOrder); | ||||
|         } | ||||
|         workspace_tabs.order(order); | ||||
|         return newOrder | ||||
|     } | ||||
|  | ||||
|     function flashTab(tabId) { | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|             <option value="scale" data-i18n="range.scale.payload"></option> | ||||
|             <option value="clamp" data-i18n="range.scale.limit"></option> | ||||
|             <option value="roll" data-i18n="range.scale.wrap"></option> | ||||
|             <option value="drop" data-i18n="range.scale.drop"></option> | ||||
|         </select> | ||||
|     </div> | ||||
|     <br/> | ||||
|   | ||||
| @@ -32,11 +32,15 @@ module.exports = function(RED) { | ||||
|             if (value !== undefined) { | ||||
|                 var n = Number(value); | ||||
|                 if (!isNaN(n)) { | ||||
|                     if (node.action == "clamp") { | ||||
|                     if (node.action === "drop") { | ||||
|                         if (n < node.minin) { done(); return; } | ||||
|                         if (n > node.maxin) { done(); return; } | ||||
|                     } | ||||
|                     if (node.action === "clamp") { | ||||
|                         if (n < node.minin) { n = node.minin; } | ||||
|                         if (n > node.maxin) { n = node.maxin; } | ||||
|                     } | ||||
|                     if (node.action == "roll") { | ||||
|                     if (node.action === "roll") { | ||||
|                         var divisor = node.maxin - node.minin; | ||||
|                         n = ((n - node.minin) % divisor + divisor) % divisor + node.minin; | ||||
|                     } | ||||
|   | ||||
| @@ -34,11 +34,14 @@ | ||||
|     the range specified within the target range.</p> | ||||
|     <p><i>Scale and wrap within the target range</i> means that the result will | ||||
|     be wrapped within the target range.</p> | ||||
|     <p><i>Scale, but drop if outside input range</i> means that the result will | ||||
|     be scaled, but any inputs outside of the inout range will be dropped.</p> | ||||
|     <p>For example an input 0 - 10 mapped to 0 - 100.</p> | ||||
|     <table style="outline-width:#888 solid thin"> | ||||
|         <tr><th width="80px">mode</th><th width="80px">input</th><th width="80px">output</th></tr> | ||||
|         <tr><td><center>scale</center></td><td><center>12</center></td><td><center>120</center></td></tr> | ||||
|         <tr><td><center>limit</center></td><td><center>12</center></td><td><center>100</center></td></tr> | ||||
|         <tr><td><center>wrap</center></td><td><center>12</center></td><td><center>20</center></td></tr> | ||||
|         <tr><td><center>drop</center></td><td><center>12</center></td><td><center><i>(no output)</i></center></td></tr> | ||||
|     </table> | ||||
| </script> | ||||
|   | ||||
| @@ -813,7 +813,8 @@ | ||||
|         "scale": { | ||||
|             "payload": "Scale the message property", | ||||
|             "limit": "Scale and limit to the target range", | ||||
|             "wrap": "Scale and wrap within the target range" | ||||
|             "wrap": "Scale and wrap within the target range", | ||||
|             "drop": "Scale, but drop msg if outside input range" | ||||
|         }, | ||||
|         "tip": "Tip: This node ONLY works with numbers.", | ||||
|         "errors": { | ||||
|   | ||||
| @@ -15,22 +15,22 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "acorn": "8.7.1", | ||||
|         "acorn": "8.8.1", | ||||
|         "acorn-walk": "8.2.0", | ||||
|         "ajv": "8.11.0", | ||||
|         "body-parser": "1.20.0", | ||||
|         "ajv": "8.11.2", | ||||
|         "body-parser": "1.20.1", | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "content-type": "1.0.4", | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cookie": "0.5.0", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
|         "denque": "2.0.1", | ||||
|         "denque": "2.1.0", | ||||
|         "form-data": "4.0.0", | ||||
|         "fs-extra": "10.1.0", | ||||
|         "got": "11.8.5", | ||||
|         "hash-sum": "2.0.0", | ||||
|         "hpagent": "1.0.0", | ||||
|         "hpagent": "1.2.0", | ||||
|         "https-proxy-agent": "5.0.1", | ||||
|         "is-utf8": "0.2.1", | ||||
|         "js-yaml": "4.1.0", | ||||
| @@ -41,7 +41,7 @@ | ||||
|         "node-watch": "0.7.3", | ||||
|         "on-headers": "1.0.2", | ||||
|         "raw-body": "2.5.1", | ||||
|         "tough-cookie": "4.0.0", | ||||
|         "tough-cookie": "4.1.2", | ||||
|         "uuid": "8.3.2", | ||||
|         "ws": "7.5.6", | ||||
|         "xml2js": "0.4.23", | ||||
|   | ||||
| @@ -19,8 +19,8 @@ | ||||
|         "@node-red/util": "3.1.0-beta.0", | ||||
|         "clone": "2.1.2", | ||||
|         "fs-extra": "10.1.0", | ||||
|         "semver": "7.3.7", | ||||
|         "tar": "6.1.11", | ||||
|         "uglify-js": "3.16.2" | ||||
|         "semver": "7.3.8", | ||||
|         "tar": "6.1.12", | ||||
|         "uglify-js": "3.17.4" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -18,9 +18,9 @@ | ||||
|     "dependencies": { | ||||
|         "@node-red/registry": "3.1.0-beta.0", | ||||
|         "@node-red/util": "3.1.0-beta.0", | ||||
|         "async-mutex": "0.3.2", | ||||
|         "async-mutex": "0.4.0", | ||||
|         "clone": "2.1.2", | ||||
|         "express": "4.18.1", | ||||
|         "express": "4.18.2", | ||||
|         "fs-extra": "10.1.0", | ||||
|         "json-stringify-safe": "5.0.1" | ||||
|     } | ||||
|   | ||||
| @@ -16,11 +16,11 @@ | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "fs-extra": "10.1.0", | ||||
|         "i18next": "21.8.14", | ||||
|         "i18next": "21.10.0", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "jsonata": "1.8.6", | ||||
|         "lodash.clonedeep": "^4.5.0", | ||||
|         "moment": "2.29.4", | ||||
|         "moment-timezone": "0.5.34" | ||||
|         "moment-timezone": "0.5.39" | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -37,14 +37,14 @@ | ||||
|         "@node-red/nodes": "3.1.0-beta.0", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "express": "4.18.1", | ||||
|         "express": "4.18.2", | ||||
|         "fs-extra": "10.1.0", | ||||
|         "node-red-admin": "^3.0.0", | ||||
|         "nopt": "5.0.0", | ||||
|         "semver": "7.3.7" | ||||
|         "semver": "7.3.8" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "bcrypt": "5.0.1" | ||||
|         "bcrypt": "5.1.0" | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=14" | ||||
|   | ||||
| @@ -106,6 +106,27 @@ describe('range Node', function() { | ||||
|         genericRangeTest("clamp", 0, 10, 0, 1000, false, -1, 0, done); | ||||
|     }); | ||||
|  | ||||
|     it('drops msg if in drop mode and input outside range', function(done) { | ||||
|         var flow = [{"id":"rangeNode1","type":"range","minin":2,"maxin":8,"minout":20,"maxout":80,"action":"drop","round":true,"name":"rangeNode","wires":[["helperNode1"]]}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[]}]; | ||||
|         helper.load(rangeNode, flow, function() { | ||||
|             var rangeNode1 = helper.getNode("rangeNode1"); | ||||
|             var helperNode1 = helper.getNode("helperNode1"); | ||||
|             helperNode1.on("input", function(msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property('payload'); | ||||
|                     msg.payload.should.equal(50); | ||||
|                     done(); | ||||
|                 } catch(err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             rangeNode1.receive({payload:1}); | ||||
|             rangeNode1.receive({payload:9}); | ||||
|             rangeNode1.receive({payload:5}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('just passes on msg if payload not present', function(done) { | ||||
|         var flow = [{"id":"rangeNode1","type":"range","minin":0,"maxin":100,"minout":0,"maxout":100,"action":"scale","round":true,"name":"rangeNode","wires":[["helperNode1"]]}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[]}]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user