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 f226cb79e..44d370aac 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 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/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/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 5df1ca69c..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 @@ -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) + 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( - 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"), @@ -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) {