From c4e277853cf2ce5b2fcb032d7be80848793d5b61 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 12 Oct 2022 23:19:11 +0900 Subject: [PATCH 01/15] Add Japanese translation for button of node URL --- .../@node-red/editor-client/locales/ja/editor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index fb3458eed3..ce5fdd696f 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -683,7 +683,8 @@ "empty": "空", "globalConfig": "グローバル設定ノード", "triggerAction": "アクションを実行", - "find": "ワークスペース内を検索" + "find": "ワークスペース内を検索", + "copyItemUrl": "要素のURLをコピー" }, "help": { "name": "ヘルプ", @@ -1168,8 +1169,7 @@ "takeATour": "ツアーを開始", "start": "開始", "next": "次へ", - "welcomeTours": "ウェルカムツアー", - "tours": "ツアー" + "welcomeTours": "ウェルカムツアー" }, "diagnostics": { "title": "システム情報" From fb499be9795f667d2c0810dbd657d61dae6ed4a2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 25 Oct 2022 23:44:59 +0100 Subject: [PATCH 02/15] Add context menu to tab bar --- .../editor-client/locales/en-US/editor.json | 4 +- .../editor-client/src/js/ui/clipboard.js | 21 +- .../editor-client/src/js/ui/common/menu.js | 4 +- .../editor-client/src/js/ui/common/tabs.js | 49 +++- .../editor-client/src/js/ui/contextMenu.js | 215 ++++++++--------- .../@node-red/editor-client/src/js/ui/view.js | 1 + .../editor-client/src/js/ui/workspaces.js | 228 +++++++++++------- 7 files changed, 299 insertions(+), 223 deletions(-) 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 1cc571200a..f226cb79ed 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,11 +57,11 @@ "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", 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 f547203d46..61435f6ad3 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 2d95f894aa..8d0f1dbd33 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 8901cf11fa..a8e7ea727a 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 66ae2b9432..8052d561fa 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 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 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 + 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 + }) } - const historyEvent = { - dirty: RED.nodes.dirty(), - t: 'add', - junctions: [nn] + }, + (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) } - 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 } - }, - { - label: RED._("contextMenu.linkNodes"), - onselect: 'core:split-wire-with-link-nodes', - disabled: !hasLinks - } - ] + ] - } - ] - - menuItems.push( - null, - { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, - { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, - null, - { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection }, - { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, - { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() }, - { onselect: 'core:delete-selection', disabled: !canDelete }, - { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, - { onselect: 'core:select-all-nodes' } - ) - - if (hasSelection) { + } + ) + menuItems.push( null, - isGroup ? - { onselect: 'core:ungroup-selection', disabled: !isGroup } - : { onselect: 'core:group-selection', disabled: !hasSelection } + { 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/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index c6c49c9424..83215afe40 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 ae38f2c4db..a34f45b835 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,113 @@ 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; + + 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" + }, + { + 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, + { + 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) + } + }, + { + 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"), + disabled: tab.type !== 'tab', + onselect: function() { + RED.workspaces.delete(tab) + } + }, + { + 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", @@ -214,12 +321,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 +341,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 +393,29 @@ 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: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 +429,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 })) From 4cc18c25fe678b17c76aa5707a9143e4cc79aa7a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sat, 29 Oct 2022 17:34:29 +0100 Subject: [PATCH 03/15] Add drop mode to range node and include tests --- .../nodes/core/function/16-range.html | 1 + .../@node-red/nodes/core/function/16-range.js | 8 +++++-- .../locales/en-US/function/16-range.html | 3 +++ .../nodes/locales/en-US/messages.json | 3 ++- test/nodes/core/function/16-range_spec.js | 21 +++++++++++++++++++ 5 files changed, 33 insertions(+), 3 deletions(-) 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 07bb1f0803..1652a91db1 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 @@ +
diff --git a/packages/node_modules/@node-red/nodes/core/function/16-range.js b/packages/node_modules/@node-red/nodes/core/function/16-range.js index a5dede4ea1..61ffd53fb1 100644 --- a/packages/node_modules/@node-red/nodes/core/function/16-range.js +++ b/packages/node_modules/@node-red/nodes/core/function/16-range.js @@ -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; } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/16-range.html index b391f5c048..f253635655 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/16-range.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/16-range.html @@ -34,11 +34,14 @@

Details

the range specified within the target range.

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.

+
modeinputoutput
scale
12
120
limit
12
100
wrap
12
20
drop
12
(no output)
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 62d5f351f4..8b66bf5e93 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -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": { diff --git a/test/nodes/core/function/16-range_spec.js b/test/nodes/core/function/16-range_spec.js index a0dcd00784..620d21b129 100644 --- a/test/nodes/core/function/16-range_spec.js +++ b/test/nodes/core/function/16-range_spec.js @@ -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:[]}]; From dd76840568c7435bd6568f488180f81b8d56f1c5 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 1 Nov 2022 01:09:06 +0900 Subject: [PATCH 04/15] Fix uncleared translations in change node --- .../node_modules/@node-red/nodes/locales/ja/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 6e16daa6f0..124dd4f096 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -777,8 +777,8 @@ "change": "値の置換", "delete": "値の削除", "move": "値の移動", - "toValue": "対象の値", - "to": "対象の値", + "toValue": "代入する値", + "to": "移動先", "search": "検索する文字列", "replace": "置換後の文字列" }, From d8e01584f33a3f8d205180d60ec80cf91a72399a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 31 Oct 2022 20:20:05 +0000 Subject: [PATCH 05/15] Remove add-flow-to-right option if clicked in tab bar --- .../editor-client/src/js/ui/workspaces.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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 a34f45b835..5df1ca69ca 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 @@ -161,15 +161,21 @@ RED.workspaces = (function() { 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"), - shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"), - onselect: function() { - RED.actions.invoke("core:add-flow-to-right", tab) + } + ) + 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) + } } - }, + ) + } + menuItems.push( null, { id:"red-ui-tabs-menu-option-add-hide-flows", From a351cd9d9f58556e06f979867ba88ed5edb22d68 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 1 Nov 2022 10:35:57 +0000 Subject: [PATCH 06/15] Add move-to-start/end and better subflow menu options --- .../editor-client/locales/en-US/editor.json | 6 +- .../@node-red/editor-client/src/js/nodes.js | 2 +- .../editor-client/src/js/ui/subflow.js | 83 ++++++----- .../editor-client/src/js/ui/workspaces.js | 137 ++++++++++++++---- 4 files changed, 162 insertions(+), 66 deletions(-) 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 f226cb79ed..44d370aac5 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 @@ -68,7 +68,11 @@ "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 9da5aad053..6dd500581f 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/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index ca4f651abb..e979adf650 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/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 5df1ca69ca..d274ba5199 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 @@ -141,6 +141,8 @@ RED.workspaces = (function() { }) } 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) { @@ -172,27 +174,69 @@ RED.workspaces = (function() { 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, - { - 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(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"), @@ -216,9 +260,12 @@ RED.workspaces = (function() { null, { label: RED._("common.label.delete"), - disabled: tab.type !== 'tab', onselect: function() { - RED.workspaces.delete(tab) + if (tab.type === 'tab') { + RED.workspaces.delete(tab) + } else if (tab.type === 'subflow') { + RED.subflow.delete(tab.id) + } } }, { @@ -302,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) @@ -412,6 +465,8 @@ RED.workspaces = (function() { 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(workspace) { let selection @@ -597,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) { From b95df6d8832909b91d5e34fba69113aeb65ff23f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 7 Nov 2022 15:47:19 +0900 Subject: [PATCH 07/15] i18n item URL copy notification & add Japanese message --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- .../@node-red/editor-client/src/js/ui/view-tools.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) 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 1cc571200a..b97b9815a3 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 @@ -684,7 +684,8 @@ "globalConfig": "Global Configuration Nodes", "triggerAction": "Trigger action", "find": "Find in workspace", - "copyItemUrl": "Copy item url" + "copyItemUrl": "Copy item url", + "copyURL2Clipboard": "Copied url to clipboard" }, "help": { "name": "Help", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index fb3458eed3..fe2625556f 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -683,7 +683,8 @@ "empty": "空", "globalConfig": "グローバル設定ノード", "triggerAction": "アクションを実行", - "find": "ワークスペース内を検索" + "find": "ワークスペース内を検索", + "copyURL2Clipboard": "URLをクリップボードにコピーしました" }, "help": { "name": "ヘルプ", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 1ed5791a62..0f02077d8a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1211,7 +1211,7 @@ RED.view.tools = (function() { url += '/edit' } if (RED.clipboard.copyText(url)) { - RED.notify('Copied url to clipboard', { timeout: 2000 }) + RED.notify(RED._("sidebar.info.copyURL2Clipboard"), { timeout: 2000 }) } } } From e3892dc26d9c1388762467696297c6759cfdaf90 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 7 Nov 2022 16:07:46 +0900 Subject: [PATCH 08/15] add Japanese message for item url copy actions --- .../@node-red/editor-client/locales/ja/editor.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index fb3458eed3..1420fbe532 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1348,6 +1348,8 @@ "show-project-settings": "プロジェクト設定を表示", "show-version-control-tab": "バージョンコントロールタブを表示", "start-flows": "フローを開始", - "stop-flows": "フローを停止" + "stop-flows": "フローを停止", + "copy-item-url": "要素のURLをコピー", + "copy-item-edit-url": "要素の編集URLをコピー" } } From b3f761776d79dbb730714486a799ce30be9780a4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sat, 3 Dec 2022 22:43:03 +0000 Subject: [PATCH 09/15] Update dependencies --- package.json | 36 +++++++++---------- .../@node-red/editor-api/package.json | 6 ++-- .../node_modules/@node-red/nodes/package.json | 12 +++---- .../@node-red/registry/package.json | 6 ++-- .../@node-red/runtime/package.json | 4 +-- .../node_modules/@node-red/util/package.json | 4 +-- packages/node_modules/node-red/package.json | 6 ++-- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index b596e10b61..ad65edd907 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 dd7020e758..01fcb0ec8c 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/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 9db16efc07..c4b7023dd3 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -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", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 088bba36ad..8dbe03d5b2 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -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" } } diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 14171f1d63..f3c58ee711 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -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" } diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index aa82273f6c..a02b997115 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -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" } } diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index b4c4e53f01..e87a14c971 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -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" From c24b123917188f142038b7c4d5431f9f1c89edd4 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 5 Dec 2022 23:11:56 +0900 Subject: [PATCH 10/15] Add Japanese translation for editor actions --- .../@node-red/editor-client/locales/ja/editor.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 53979a965e..5ea5a28dd8 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -68,7 +68,11 @@ "enabled": "有効", "disabled": "無効", "info": "詳細", - "selectNodes": "ノードをクリックして選択" + "selectNodes": "ノードをクリックして選択", + "enableFlow": "フローを有効化", + "disableFlow": "フローを無効化", + "moveToStart": "フローを先頭へ移動", + "moveToEnd": "フローを最後へ移動" }, "menu": { "label": { @@ -1350,7 +1354,7 @@ "show-version-control-tab": "バージョンコントロールタブを表示", "start-flows": "フローを開始", "stop-flows": "フローを停止", - "copy-item-url": "要素のURLをコピー", - "copy-item-edit-url": "要素の編集URLをコピー" + "copy-item-url": "要素のURLをコピー", + "copy-item-edit-url": "要素の編集URLをコピー" } } From 7117472e73b95beb0c06cb2fa6313fa45466c524 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 5 Dec 2022 23:13:50 +0900 Subject: [PATCH 11/15] Add Japanese translation for range node --- packages/node_modules/@node-red/nodes/locales/ja/messages.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 124dd4f096..cc4a07b4e0 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -813,7 +813,8 @@ "scale": { "payload": "msg.payloadの値を拡大/縮小", "limit": "入力値の範囲外の値を最小値/最大値とし拡大/縮小", - "wrap": "入力値の範囲外の値を範囲幅で割った余りとし拡大/縮小" + "wrap": "入力値の範囲外の値を範囲幅で割った余りとし拡大/縮小", + "drop": "値を拡大/縮小(入力範囲外の時はメッセージを削除)" }, "tip": "注釈: 本ノードは、数値のみ扱うことができます。", "errors": { From 7b52ef34beec0175ed6808794be86ccad9ea2b3b Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 5 Dec 2022 22:17:05 +0000 Subject: [PATCH 12/15] Remember compact/pretty flow export user choice closes #3849 --- .../@node-red/editor-client/src/js/ui/clipboard.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 61435f6ad3..53a908c676 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 @@ -656,7 +656,12 @@ RED.clipboard = (function() { $("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select(); dialogContainer.i18n(); + var format = RED.settings.flowFilePretty ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini"; + const userFormat = RED.settings.get("editor.dialog.export.pretty") + if (userFormat === false || userFormat === true) { + format = userFormat ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini"; + } $("#red-ui-clipboard-dialog-export-fmt-group > a").on("click", function(evt) { evt.preventDefault(); @@ -672,7 +677,8 @@ RED.clipboard = (function() { var nodes = JSON.parse(flow); format = $(this).attr('id'); - if (format === 'red-ui-clipboard-dialog-export-fmt-full') { + const pretty = format === "red-ui-clipboard-dialog-export-fmt-full"; + if (pretty) { flow = JSON.stringify(nodes,null,4); } else { flow = JSON.stringify(nodes); @@ -681,6 +687,7 @@ RED.clipboard = (function() { setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50); $("#red-ui-clipboard-dialog-export-text").trigger("focus"); + RED.settings.set("editor.dialog.export.pretty", pretty) } }); From f6901cd19f774d26cb3ad7187302e4acce304a31 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Dec 2022 09:50:29 +0000 Subject: [PATCH 13/15] CSV node replace replace with replaceAll just in case mentioned in Issue #3989 --- packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 9c55fa2b66..504e184d26 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -19,9 +19,9 @@ module.exports = function(RED) { function CSVNode(n) { RED.nodes.createNode(this,n); this.template = (n.temp || ""); - this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r"); + this.sep = (n.sep || ',').replaceAll("\\t","\t").replaceAll("\\n","\n").replaceAll("\\r","\r"); this.quo = '"'; - this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r"); + this.ret = (n.ret || "\n").replaceAll("\\n","\n").replaceAll("\\r","\r"); this.winflag = (this.ret === "\r\n"); this.lineend = "\n"; this.multi = n.multi || "one"; From 24291918386c9fbef309a6ff8caeffb99ecb6ec8 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Dec 2022 13:48:21 +0000 Subject: [PATCH 14/15] CSV - swap to regex replace for node14 support --- packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 504e184d26..04f2bb71fc 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -19,9 +19,9 @@ module.exports = function(RED) { function CSVNode(n) { RED.nodes.createNode(this,n); this.template = (n.temp || ""); - this.sep = (n.sep || ',').replaceAll("\\t","\t").replaceAll("\\n","\n").replaceAll("\\r","\r"); + this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r"); this.quo = '"'; - this.ret = (n.ret || "\n").replaceAll("\\n","\n").replaceAll("\\r","\r"); + this.ret = (n.ret || "\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r"); this.winflag = (this.ret === "\r\n"); this.lineend = "\n"; this.multi = n.multi || "one"; From 93a191123210251b104adac35d1ec38c5aab941a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Dec 2022 21:26:20 +0000 Subject: [PATCH 15/15] CSV - Add note about msg.reset to info page to close #3976 --- .../@node-red/nodes/locales/en-US/parsers/70-CSV.html | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html index dc4dd98fff..baa3b036b7 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html @@ -47,5 +47,6 @@

Details

If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.

The node can accept a multi-part input as long as the parts property is set correctly, for example from a file-in node or split node.

If outputting multiple messages they will have their parts property set and form a complete message sequence.

+

If the node is set to only send column headers once, then setting msg.reset to any value will cause the node to resend the headers.

Note: the column template must be comma separated - even if a different separator is chosen for the data.