diff --git a/package.json b/package.json index b596e10b6..ad65edd90 100644 --- a/package.json +++ b/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", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index dd7020e75..01fcb0ec8 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -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" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index b97b9815a..8de24c3c9 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -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": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 9da5aad05..6dd500581 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -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, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index f547203d4..61435f6ad 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -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(); 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 2d95f894a..8d0f1dbd3 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 @@ -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 = $(''+RED.keyboard.formatKey(shortcut.key, true)+'').appendTo(link.find(".red-ui-menu-label")); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index 8901cf11f..a8e7ea727 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -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 = $("",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li); - closeLink.append(''); - closeLink.append(''); - 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 = $("",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li); + // closeLink.append(''); + // closeLink.append(''); + // closeLink.on("click",function(event) { + // event.preventDefault(); + // hideTab(tab.id); + // }); + // RED.popover.tooltip(closeLink,RED._("workspace.hideFlow")); + // } var badges = $('').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; }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 66ae2b943..8052d561f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -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() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index ca4f651ab..e979adf65 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -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 = $('
').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); - $('
').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 = $('
').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); + $('
').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, 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 c6c49c942..83215afe4 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 @@ -211,6 +211,7 @@ RED.view = (function() { evt.preventDefault() evt.stopPropagation() RED.contextMenu.show({ + type: 'workspace', x:evt.clientX-5, y:evt.clientY-5 }) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index ae38f2c4d..d274ba519 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -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) { diff --git a/packages/node_modules/@node-red/nodes/core/function/16-range.html b/packages/node_modules/@node-red/nodes/core/function/16-range.html index 07bb1f080..1652a91db 100644 --- a/packages/node_modules/@node-red/nodes/core/function/16-range.html +++ b/packages/node_modules/@node-red/nodes/core/function/16-range.html @@ -10,6 +10,7 @@ +
Scale and wrap within the target range means that the result will be wrapped within the target range.
+Scale, but drop if outside input range means that the result will + be scaled, but any inputs outside of the inout range will be dropped.
For example an input 0 - 10 mapped to 0 - 100.
mode | input | output |
---|---|---|