/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.workspaces = (function() { var activeWorkspace = 0; var workspaceIndex = 0; var viewStack = []; var hideStack = []; var viewStackPos = 0; let flashingTab; let flashingTabTimer; function addToViewStack(id) { if (viewStackPos !== viewStack.length) { viewStack.splice(viewStackPos); } viewStack.push(id); viewStackPos = viewStack.length; } function removeFromHideStack(id) { hideStack = hideStack.filter(function(v) { if (v === id) { return false; } else if (Array.isArray(v)) { var i = v.indexOf(id); if (i > -1) { v.splice(i,1); } if (v.length === 0) { return false; } return true } return true; }) } function addWorkspace(ws,skipHistoryEntry,targetIndex) { if (ws) { if (!ws.closeable) { ws.hideable = true; } if (!ws.hasOwnProperty('locked')) { ws.locked = false } workspace_tabs.addTab(ws,targetIndex); var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}"); if (hiddenTabs[ws.id]) { workspace_tabs.hideTab(ws.id); } workspace_tabs.resize(); } else { var tabId = RED.nodes.id(); do { workspaceIndex += 1; } while ($("#red-ui-workspace-tabs li[flowname='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0); ws = { type: "tab", id: tabId, disabled: false, locked: false, info: "", label: RED._('workspace.defaultName',{number:workspaceIndex}), env: [], hideable: true, }; if (!skipHistoryEntry) { ws.added = true } RED.nodes.addWorkspace(ws,targetIndex); workspace_tabs.addTab(ws,targetIndex); workspace_tabs.activateTab(tabId); if (!skipHistoryEntry) { RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); } } $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added)); RED.view.focus(); return ws; } function deleteWorkspace(ws) { if (workspaceTabCount === 1) { return; } if (ws.locked) { return } var workspaceOrder = RED.nodes.getWorkspaceOrder(); ws._index = workspaceOrder.indexOf(ws.id); removeWorkspace(ws); var historyEvent = RED.nodes.removeWorkspace(ws.id); historyEvent.t = 'delete'; historyEvent.dirty = RED.nodes.dirty(); historyEvent.workspaces = [ws]; RED.history.push(historyEvent); RED.nodes.dirty(true); RED.sidebar.config.refresh(); } function showEditWorkspaceDialog(id) { var workspace = RED.nodes.workspace(id); if (!workspace) { var subflow = RED.nodes.subflow(id); if (subflow) { RED.editor.editSubflow(subflow); } } else { if (!workspace.locked) { RED.editor.editFlow(workspace); } } } 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 const currentTabs = workspace_tabs.listTabs(); let flowCount = 0; currentTabs.forEach(tab => { if (RED.nodes.workspace(tab)) { flowCount++; } }); let isCurrentLocked = RED.workspaces.isLocked() if (tab) { isCurrentLocked = tab.locked } 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) }, disabled: isCurrentLocked } : { label: RED._("workspace.disableFlow"), shortcut: RED.keyboard.getShortcut("core:disable-flow"), onselect: function() { RED.actions.invoke("core:disable-flow", tab?tab.id:undefined) }, disabled: isCurrentLocked }, isCurrentLocked? { label: RED._("workspace.unlockFlow"), shortcut: RED.keyboard.getShortcut("core:unlock-flow"), onselect: function() { RED.actions.invoke('core:unlock-flow', tab?tab.id:undefined) } } : { label: RED._("workspace.lockFlow"), shortcut: RED.keyboard.getShortcut("core:lock-flow"), onselect: function() { RED.actions.invoke('core:lock-flow', tab?tab.id:undefined) } }, null ) } const activeIndex = currentTabs.findIndex(id => (activeWorkspace && (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", disabled: (hiddenflowCount === flowCount) }, { 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) } }, disabled: isCurrentLocked || (workspaceTabCount === 1) }, { 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", onchange: function(tab) { var event = { old: activeWorkspace } if (tab) { $("#red-ui-workspace-chart").show(); activeWorkspace = tab.id; window.location.hash = 'flow/'+tab.id; $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled); $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked); } else { $("#red-ui-workspace-chart").hide(); activeWorkspace = 0; window.location.hash = ''; } event.workspace = activeWorkspace; RED.events.emit("workspace:change",event); RED.sidebar.config.refresh(); RED.view.focus(); }, onclick: function(tab) { if (tab.id !== activeWorkspace) { addToViewStack(activeWorkspace); } RED.view.focus(); }, ondblclick: function(tab) { if (tab.type != "subflow") { showEditWorkspaceDialog(tab.id); } else { RED.editor.editSubflow(RED.nodes.subflow(tab.id)); } }, onadd: function(tab) { if (tab.type === "tab") { workspaceTabCount++; } $('
').prependTo("#red-ui-tab-"+(tab.id.replace(".","-"))+" .red-ui-tab-label"); if (tab.disabled) { $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled'); } $(' ').prependTo("#red-ui-tab-"+(tab.id.replace(".","-"))+" .red-ui-tab-label"); if (tab.locked) { $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked'); } const changeBadgeContainer = $('').appendTo("#red-ui-tab-"+(tab.id.replace(".","-"))) const changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); changeBadge.setAttribute("cx",5); changeBadge.setAttribute("cy",5); changeBadge.setAttribute("r",5); changeBadgeContainer.append(changeBadge) RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); if (workspaceTabCount === 1) { showWorkspace(); } }, onremove: function(tab) { if (tab.type === "tab") { workspaceTabCount--; } else { hideStack.push(tab.id); } RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); if (workspaceTabCount === 0) { hideWorkspace(); } }, onreorder: function(oldOrder, newOrder) { RED.history.push({ t:'reorder', workspaces: { from: oldOrder, to: newOrder }, dirty:RED.nodes.dirty() }); // 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) if (selectedTabs.length === 0) { $("#red-ui-workspace-chart svg").css({"pointer-events":"auto",filter:"none"}) $("#red-ui-workspace-toolbar").css({"pointer-events":"auto",filter:"none"}) $("#red-ui-palette-container").css({"pointer-events":"auto",filter:"none"}) $(".red-ui-sidebar-shade").hide(); } else { RED.view.select(false) $("#red-ui-workspace-chart svg").css({"pointer-events":"none",filter:"opacity(60%)"}) $("#red-ui-workspace-toolbar").css({"pointer-events":"none",filter:"opacity(60%)"}) $("#red-ui-palette-container").css({"pointer-events":"none",filter:"opacity(60%)"}) $(".red-ui-sidebar-shade").show(); } }, onhide: function(tab) { hideStack.push(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); var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}"); delete hiddenTabs[tab.id]; RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs)); RED.events.emit("workspace:show",{workspace: tab.id}) }, minimumActiveTabWidth: 150, scrollable: true, addButton: "core:add-flow", addButtonCaption: RED._("workspace.addFlow"), menu: function() { return getMenuItems(true) }, contextmenu: function(tab) { return getMenuItems(false, tab) } }); workspaceTabCount = 0; } function showWorkspace() { $("#red-ui-workspace .red-ui-tabs").show() $("#red-ui-workspace-chart").show() $("#red-ui-workspace-footer").children().show() } function hideWorkspace() { $("#red-ui-workspace .red-ui-tabs").hide() $("#red-ui-workspace-chart").hide() $("#red-ui-workspace-footer").children().hide() } function init() { $('