RED.sidebar.info.outliner = (function() { var treeList; var searchInput; var activeSearch; var projectInfo; var projectInfoLabel; var flowList; var subflowList; var globalConfigNodes; var objects = {}; var missingParents = {}; var configNodeTypes; function getFlowData() { var flowData = [ { label: RED._("menu.label.flows"), expanded: true, children: [] }, { id: "__subflow__", label: RED._("menu.label.subflows"), children: [ getEmptyItem("__subflow__") ] }, { id: "__global__", flow: "__global__", label: RED._("sidebar.info.globalConfig"), types: {}, children: [ getEmptyItem("__global__") ] } ] flowList = flowData[0]; subflowList = flowData[1]; globalConfigNodes = flowData[2]; configNodeTypes = { __global__: globalConfigNodes}; return flowData; } function getProjectLabel(p) { var div = $('
',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); div.css("width", "calc(100% - 40px)"); var contentDiv = $('
',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); contentDiv.text(p.name); var controls = $('
',{class:"red-ui-info-outline-item-controls"}).appendTo(div); var editProjectButton = $('') .appendTo(controls) .on("click", function(evt) { evt.preventDefault(); RED.projects.editProject(); }); RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings')); return div; } var empties = {}; function getEmptyItem(id) { var item = { empty: true, element: $('
').text(RED._("sidebar.info.empty")), } empties[id] = item; return item; } function getNodeLabel(n) { var div = $('
',{class:"red-ui-node-list-item red-ui-info-outline-item"}); RED.utils.createNodeIcon(n, true).appendTo(div); div.find(".red-ui-node-label").addClass("red-ui-info-outline-item-label") addControls(n, div); return div; } function getFlowLabel(n) { var div = $('
',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); var contentDiv = $('
',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); var label = (typeof n === "string")? n : n.label; var newlineIndex = label.indexOf("\\n"); if (newlineIndex > -1) { label = label.substring(0,newlineIndex)+"..."; } contentDiv.text(label); addControls(n, div); return div; } function addControls(n,div) { var controls = $('
',{class:"red-ui-info-outline-item-controls red-ui-info-outline-item-hover-controls"}).appendTo(div); if (n.type === "subflow") { var subflowInstanceBadge = $('').text(n.instances.length).appendTo(controls).on("click",function(evt) { evt.preventDefault(); evt.stopPropagation(); RED.search.show("type:subflow:"+n.id); }) // RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})}); } if (n._def.category === "config" && n.type !== "group") { var userCountBadge = $('').text(n.users.length).appendTo(controls).on("click",function(evt) { evt.preventDefault(); evt.stopPropagation(); RED.search.show("uses:"+n.id); }) RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})}); } if (n._def.button) { var triggerButton = $('').appendTo(controls).on("click",function(evt) { evt.preventDefault(); evt.stopPropagation(); RED.view.clickNodeButton(n); }) RED.popover.tooltip(triggerButton,RED._("sidebar.info.triggerAction")); } // $('').appendTo(controls).on("click",function(evt) { // evt.preventDefault(); // evt.stopPropagation(); // RED.view.reveal(n.id); // }) if (n.type !== 'subflow') { var toggleButton = $('').appendTo(controls).on("click",function(evt) { evt.preventDefault(); evt.stopPropagation(); if (n.type === 'tab') { if (n.disabled) { RED.workspaces.enable(n.id) } else { RED.workspaces.disable(n.id) } } else if (n.type === 'group') { var groupNodes = RED.group.getNodes(n,true); var groupHistoryEvent = { t:'multi', events:[], dirty: RED.nodes.dirty() } var targetState; groupNodes.forEach(function(n) { if (n.type !== 'group') { if (targetState === undefined) { targetState = !n.d; } var state = !!n.d; if (state !== targetState) { var historyEvent = { t: "edit", node: n, changed: n.changed, changes: { d: n.d } } if (n.d) { delete n.d; } else { n.d = true; } n.dirty = true; n.changed = true; RED.events.emit("nodes:change",n); groupHistoryEvent.events.push(historyEvent); } } if (groupHistoryEvent.events.length > 0) { RED.history.push(groupHistoryEvent); RED.nodes.dirty(true) RED.view.redraw(); } }) } else { // TODO: this ought to be a utility function in RED.nodes var historyEvent = { t: "edit", node: n, changed: n.changed, changes: { d: n.d }, dirty:RED.nodes.dirty() } if (n.d) { delete n.d; } else { n.d = true; } n.dirty = true; n.changed = true; RED.events.emit("nodes:change",n); RED.history.push(historyEvent); RED.nodes.dirty(true) RED.view.redraw(); } }); RED.popover.tooltip(toggleButton,function() { if (n.type === "group") { return RED._("common.label.enable")+" / "+RED._("common.label.disable") } return RED._("common.label."+(((n.type==='tab' && n.disabled) || (n.type!=='tab' && n.d))?"enable":"disable")); }); } else { $('
').appendTo(controls) } controls.find("button").on("dblclick", function(evt) { evt.preventDefault(); evt.stopPropagation(); }) } function onProjectLoad(activeProject) { objects = {}; var newFlowData = getFlowData(); projectInfoLabel.empty(); getProjectLabel(activeProject).appendTo(projectInfoLabel); projectInfo.show(); treeList.treeList('data',newFlowData); } function build() { var container = $("
", {class:"red-ui-info-outline"}).css({'height': '100%'}); var toolbar = $("
", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(container); searchInput = $('').appendTo(toolbar).searchBox({ style: "compact", delay: 500, change: function() { var val = $(this).val(); var searchResults = RED.search.search(val); if (val) { activeSearch = val; var resultMap = {}; for (var i=0,l=searchResults.length;i
').hide().appendTo(container) projectInfoLabel = $('').appendTo(projectInfo); //
Space Monkey
').appendTo(container) treeList = $("
").css({width: "100%"}).appendTo(container).treeList({ data:getFlowData() }) treeList.on('treelistselect', function(e,item) { var node = RED.nodes.node(item.id) || RED.nodes.group(item.id); if (node) { if (node.type === 'group' || node._def.category !== "config") { RED.view.select({nodes:[node]}) } else if (node._def.category === "config") { RED.sidebar.info.refresh(node); } else { RED.view.select({nodes:[]}) } } }) treeList.on('treelistconfirm', function(e,item) { var node = RED.nodes.node(item.id); if (node) { if (node._def.category === "config") { RED.editor.editConfig("", node.type, node.id); } else { RED.editor.edit(node); } } }) RED.events.on("projects:load", onProjectLoad) RED.events.on("flows:add", onFlowAdd) RED.events.on("flows:remove", onObjectRemove) RED.events.on("flows:change", onFlowChange) RED.events.on("flows:reorder", onFlowsReorder) RED.events.on("subflows:add", onSubflowAdd) RED.events.on("subflows:remove", onObjectRemove) RED.events.on("subflows:change", onSubflowChange) RED.events.on("nodes:add",onNodeAdd); RED.events.on("nodes:remove",onObjectRemove); RED.events.on("nodes:change",onNodeChange); RED.events.on("groups:add",onNodeAdd); RED.events.on("groups:remove",onObjectRemove); RED.events.on("groups:change",onNodeChange); RED.events.on("workspace:clear", onWorkspaceClear) return container; } function onWorkspaceClear() { treeList.treeList('data',getFlowData()); } function onFlowAdd(ws) { objects[ws.id] = { id: ws.id, element: getFlowLabel(ws), children:[], deferBuild: true, icon: "red-ui-icons red-ui-icons-flow", gutter: getGutter(ws) } if (missingParents[ws.id]) { objects[ws.id].children = missingParents[ws.id]; delete missingParents[ws.id] } else { objects[ws.id].children.push(getEmptyItem(ws.id)); } flowList.treeList.addChild(objects[ws.id]) objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) updateSearch(); } function onFlowChange(n) { var existingObject = objects[n.id]; var label = n.label || n.id; var newlineIndex = label.indexOf("\\n"); if (newlineIndex > -1) { label = label.substring(0,newlineIndex)+"..."; } existingObject.element.find(".red-ui-info-outline-item-label").text(label); existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) updateSearch(); } function onFlowsReorder(order) { var indexMap = {}; order.forEach(function(id,index) { indexMap[id] = index; }) flowList.treeList.sortChildren(function(A,B) { if (A.id === "__global__") { return -1 } if (B.id === "__global__") { return 1 } return indexMap[A.id] - indexMap[B.id] }) } function onSubflowAdd(sf) { objects[sf.id] = { id: sf.id, element: getNodeLabel(sf), children:[], deferBuild: true, gutter: getGutter(sf) } if (missingParents[sf.id]) { objects[sf.id].children = missingParents[sf.id]; delete missingParents[sf.id] } else { objects[sf.id].children.push(getEmptyItem(sf.id)); } if (empties["__subflow__"]) { empties["__subflow__"].treeList.remove(); delete empties["__subflow__"]; } subflowList.treeList.addChild(objects[sf.id]) updateSearch(); } function onSubflowChange(sf) { var existingObject = objects[sf.id]; existingObject.treeList.replaceElement(getNodeLabel(sf)); // existingObject.element.find(".red-ui-info-outline-item-label").text(n.name || n.id); RED.nodes.eachNode(function(n) { if (n.type == "subflow:"+sf.id) { var sfInstance = objects[n.id]; sfInstance.treeList.replaceElement(getNodeLabel(n)); } }); updateSearch(); } function onNodeChange(n) { var existingObject = objects[n.id]; var parent = n.g||n.z||"__global__"; var nodeLabelText = RED.utils.getNodeLabel(n,n.name || (n.type+": "+n.id)); if (nodeLabelText) { existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText); } else { existingObject.element.find(".red-ui-info-outline-item-label").html(" "); } var existingParent = existingObject.parent.id; if (!existingParent) { existingParent = existingObject.parent.parent.flow } if (parent !== existingParent) { var parentItem = existingObject.parent; existingObject.treeList.remove(true); if (parentItem.children.length === 0) { if (parentItem.config) { // this is a config parentItem.treeList.remove(); // console.log("Removing",n.type,"from",parentItem.parent.id||parentItem.parent.parent.id) delete configNodeTypes[parentItem.parent.id||parentItem.parent.parent.id].types[n.type]; if (parentItem.parent.children.length === 0) { if (parentItem.parent.id === "__global__") { parentItem.parent.treeList.addChild(getEmptyItem(parentItem.parent.id)); } else { delete configNodeTypes[parentItem.parent.parent.id]; parentItem.parent.treeList.remove(); if (parentItem.parent.parent.children.length === 0) { parentItem.parent.parent.treeList.addChild(getEmptyItem(parentItem.parent.parent.id)); } } } } else { parentItem.treeList.addChild(getEmptyItem(parentItem.id)); } } if (n._def.category === 'config' && n.type !== 'group') { // This must be a config node that has been rescoped createFlowConfigNode(parent,n.type); configNodeTypes[parent].types[n.type].treeList.addChild(objects[n.id]); } else { // This is a node that has moved groups if (empties[parent]) { empties[parent].treeList.remove(); delete empties[parent]; } objects[parent].treeList.addChild(existingObject) } // if (parent === "__global__") { // // Global always exists here // if (!configNodeTypes[parent][n.type]) { // configNodeTypes[parent][n.type] = { // config: true, // label: n.type, // children: [] // } // globalConfigNodes.treeList.addChild(configNodeTypes[parent][n.type]) // } // configNodeTypes[parent][n.type].treeList.addChild(existingObject); // } else { // if (empties[parent]) { // empties[parent].treeList.remove(); // delete empties[parent]; // } // objects[parent].treeList.addChild(existingObject) // } } existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) if (n._def.category === "config" && n.type !== 'group') { existingObject.element.find(".red-ui-info-outline-item-control-users").text(n.users.length); } updateSearch(); } function onObjectRemove(n) { var existingObject = objects[n.id]; existingObject.treeList.remove(); delete objects[n.id] if (/^subflow:/.test(n.type)) { var sfType = n.type.substring(8); if (objects[sfType]) { objects[sfType].element.find(".red-ui-info-outline-item-control-users").text(RED.nodes.subflow(sfType).instances.length); } } // If this is a group being removed, it may have an empty item if (empties[n.id]) { delete empties[n.id]; } var parent = existingObject.parent; if (parent.children.length === 0) { if (parent.config) { // this is a config parent.treeList.remove(); delete configNodeTypes[parent.parent.id||n.z].types[n.type]; if (parent.parent.children.length === 0) { if (parent.parent.id === "__global__") { parent.parent.treeList.addChild(getEmptyItem(parent.parent.id)); } else { delete configNodeTypes[n.z]; parent.parent.treeList.remove(); if (parent.parent.parent.children.length === 0) { parent.parent.parent.treeList.addChild(getEmptyItem(parent.parent.parent.id)); } } } } else { parent.treeList.addChild(getEmptyItem(parent.id)); } } } function getGutter(n) { var span = $("",{class:"red-ui-info-outline-gutter"}); var revealButton = $('').appendTo(span).on("click",function(evt) { evt.preventDefault(); evt.stopPropagation(); RED.view.reveal(n.id); }) RED.popover.tooltip(revealButton,RED._("sidebar.info.find")); return span; } function createFlowConfigNode(parent,type) { // console.log("createFlowConfig",parent,type,configNodeTypes[parent]); if (empties[parent]) { empties[parent].treeList.remove(); delete empties[parent]; } if (!configNodeTypes[parent]) { // There is no 'config nodes' item in the parent flow configNodeTypes[parent] = { config: true, flow: parent, types: {}, label: RED._("menu.label.displayConfig"), children: [] } objects[parent].treeList.insertChildAt(configNodeTypes[parent],0); // console.log("CREATED", parent) } if (!configNodeTypes[parent].types[type]) { configNodeTypes[parent].types[type] = { config: true, label: type, children: [] } configNodeTypes[parent].treeList.addChild(configNodeTypes[parent].types[type]); // console.log("CREATED", parent,type) } } function onNodeAdd(n) { objects[n.id] = { id: n.id, element: getNodeLabel(n), gutter: getGutter(n) } if (n.type === "group") { objects[n.id].children = []; objects[n.id].deferBuild = true; if (missingParents[n.id]) { objects[n.id].children = missingParents[n.id]; delete missingParents[n.id] } } var parent = n.g||n.z||"__global__"; if (n._def.category !== "config" || n.type === 'group') { if (objects[parent]) { if (empties[parent]) { empties[parent].treeList.remove(); delete empties[parent]; } if (objects[parent].treeList) { objects[parent].treeList.addChild(objects[n.id]); } else { objects[parent].children.push(objects[n.id]) } } else { missingParents[parent] = missingParents[parent]||[]; missingParents[parent].push(objects[n.id]) } } else { createFlowConfigNode(parent,n.type); configNodeTypes[parent].types[n.type].treeList.addChild(objects[n.id]); } objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) if (/^subflow:/.test(n.type)) { var sfType = n.type.substring(8); if (objects[sfType]) { objects[sfType].element.find(".red-ui-info-outline-item-control-users").text(RED.nodes.subflow(sfType).instances.length); } } updateSearch(); } var updateSearchTimer; function updateSearch() { if (updateSearchTimer) { clearTimeout(updateSearchTimer) } if (activeSearch) { updateSearchTimer = setTimeout(function() { searchInput.searchBox("change"); },100); } } function onSelectionChanged(selection) { // treeList.treeList('clearSelection'); } return { build: build, search: function(val) { searchInput.searchBox('value',val) }, select: function(node) { if (node) { if (Array.isArray(node)) { treeList.treeList('select', node.map(function(n) { return objects[n.id] }), false) } else { treeList.treeList('select', objects[node.id], false) } } else { treeList.treeList('clearSelection') } }, reveal: function(node) { treeList.treeList('show', objects[node.id]) } } })();