mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	| @@ -15,5 +15,5 @@ | ||||
|     "shadow": true,     // allow variable shadowing (re-use of names...) | ||||
|     "sub": true,        // don't warn that foo['bar'] should be written as foo.bar | ||||
|     "proto": true,      // allow setting of __proto__ in node < v0.12, | ||||
|     "esversion": 6      // allow es6 | ||||
|     "esversion": 11      // allow es11(ES2020) | ||||
| } | ||||
|   | ||||
| @@ -71,6 +71,8 @@ | ||||
|         "selectNodes": "Click nodes to select", | ||||
|         "enableFlow": "Enable flow", | ||||
|         "disableFlow": "Disable flow", | ||||
|         "lockFlow": "Lock flow", | ||||
|         "unlockFlow": "Unlock flow", | ||||
|         "moveToStart": "Move flow to start", | ||||
|         "moveToEnd": "Move flow to end" | ||||
|     }, | ||||
| @@ -105,6 +107,7 @@ | ||||
|             "displayStatus": "Show node status", | ||||
|             "displayConfig": "Configuration nodes", | ||||
|             "import": "Import", | ||||
|             "importExample": "Import Example Flow", | ||||
|             "export": "Export", | ||||
|             "search": "Search flows", | ||||
|             "searchInput": "search your flows", | ||||
| @@ -501,6 +504,7 @@ | ||||
|         "addRemoveNode": "Add/remove node from selection", | ||||
|         "editSelected": "Edit selected node", | ||||
|         "deleteSelected": "Delete selected nodes or link", | ||||
|         "deleteReconnect": "Delete and Reconnect", | ||||
|         "importNode": "Import nodes", | ||||
|         "exportNode": "Export nodes", | ||||
|         "nudgeNode": "Move selected nodes (1px)", | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * An API for undo / redo history buffer | ||||
|  * @namespace RED.history | ||||
| */ | ||||
| @@ -434,7 +434,9 @@ RED.history = (function() { | ||||
|  | ||||
|                 if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('disabled')) { | ||||
|                     $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!ev.node.disabled); | ||||
|                     $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!ev.node.disabled); | ||||
|                 } | ||||
|                 if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('locked')) { | ||||
|                     $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!ev.node.locked); | ||||
|                 } | ||||
|                 if (ev.subflow) { | ||||
|                     inverseEv.subflow = {}; | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|  * @namespace RED.nodes | ||||
| */ | ||||
| RED.nodes = (function() { | ||||
|  | ||||
|     var PORT_TYPE_INPUT = 1; | ||||
|     var PORT_TYPE_OUTPUT = 0; | ||||
|  | ||||
| @@ -63,6 +62,7 @@ RED.nodes = (function() { | ||||
|             defaults: { | ||||
|                 label: {value:""}, | ||||
|                 disabled: {value: false}, | ||||
|                 locked: {value: false}, | ||||
|                 info: {value: ""}, | ||||
|                 env: {value: []} | ||||
|             } | ||||
| @@ -575,15 +575,48 @@ RED.nodes = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const nodeProxyHandler = { | ||||
|         get(node, prop) { | ||||
|             if (prop === '__isProxy__') { | ||||
|                 return true | ||||
|             } else if (prop == '__node__') { | ||||
|                 return node | ||||
|             } | ||||
|             return node[prop] | ||||
|         }, | ||||
|         set(node, prop, value) { | ||||
|             if (node.z && (RED.nodes.workspace(node.z)?.locked || RED.nodes.subflow(node.z)?.locked)) { | ||||
|                 if ( | ||||
|                     node._def.defaults[prop] || | ||||
|                     prop === 'z' || | ||||
|                     prop === 'l' || | ||||
|                     prop === 'd' || | ||||
|                     (prop === 'changed' && (!!node.changed) !== (!!value)) || // jshint ignore:line | ||||
|                     ((prop === 'x' || prop === 'y') && !node.resize && node.type !== 'group') | ||||
|                 ) { | ||||
|                     throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`) | ||||
|                 } | ||||
|             } | ||||
|             node[prop] = value; | ||||
|             return true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function addNode(n) { | ||||
|         let newNode | ||||
|         if (!n.__isProxy__) { | ||||
|             newNode = new Proxy(n, nodeProxyHandler) | ||||
|         } else { | ||||
|             newNode = n | ||||
|         } | ||||
|  | ||||
|         if (n.type.indexOf("subflow") !== 0) { | ||||
|             n["_"] = n._def._; | ||||
|         } else { | ||||
|             var subflowId = n.type.substring(8); | ||||
|             var sf = RED.nodes.subflow(subflowId); | ||||
|             if (sf) { | ||||
|                 sf.instances.push(sf); | ||||
|                 sf.instances.push(newNode); | ||||
|             } | ||||
|             n["_"] = RED._; | ||||
|         } | ||||
| @@ -600,12 +633,13 @@ RED.nodes = (function() { | ||||
|                 }); | ||||
|                 n.i = nextId+1; | ||||
|             } | ||||
|             allNodes.addNode(n); | ||||
|             allNodes.addNode(newNode); | ||||
|             if (!nodeLinks[n.id]) { | ||||
|                 nodeLinks[n.id] = {in:[],out:[]}; | ||||
|             } | ||||
|         } | ||||
|         RED.events.emit('nodes:add',n); | ||||
|         RED.events.emit('nodes:add',newNode); | ||||
|         return newNode | ||||
|     } | ||||
|     function addLink(l) { | ||||
|         if (nodeLinks[l.source.id]) { | ||||
| @@ -1052,6 +1086,9 @@ RED.nodes = (function() { | ||||
|         node.type = n.type; | ||||
|         for (var d in n._def.defaults) { | ||||
|             if (n._def.defaults.hasOwnProperty(d)) { | ||||
|                 if (d === 'locked' && !n.locked) { | ||||
|                     continue | ||||
|                 } | ||||
|                 node[d] = n[d]; | ||||
|             } | ||||
|         } | ||||
| @@ -1331,7 +1368,6 @@ RED.nodes = (function() { | ||||
|         } else { | ||||
|             nodeSet = [sf]; | ||||
|         } | ||||
|         console.log(nodeSet); | ||||
|         return createExportableNodeSet(nodeSet); | ||||
|     } | ||||
|     /** | ||||
| @@ -2318,19 +2354,6 @@ RED.nodes = (function() { | ||||
|             if (n.g && !new_group_set.has(n.g)) { | ||||
|                 delete n.g; | ||||
|             } | ||||
|             n.nodes = n.nodes.map(function(id) { | ||||
|                 return node_map[id]; | ||||
|             }) | ||||
|             // Just in case the group references a node that doesn't exist for some reason | ||||
|             n.nodes = n.nodes.filter(function(v) { | ||||
|                 if (v) { | ||||
|                     // Repair any nodes that have forgotten they are in this group | ||||
|                     if (v.g !== n.id) { | ||||
|                         v.g = n.id; | ||||
|                     } | ||||
|                 } | ||||
|                 return !!v | ||||
|             }); | ||||
|             if (!n.g) { | ||||
|                 groupDepthMap[n.id] = 0; | ||||
|             } | ||||
| @@ -2353,21 +2376,22 @@ RED.nodes = (function() { | ||||
|             return groupDepthMap[A.id] - groupDepthMap[B.id]; | ||||
|         }); | ||||
|         for (i=0;i<new_groups.length;i++) { | ||||
|             n = new_groups[i]; | ||||
|             addGroup(n); | ||||
|             new_groups[i] = addGroup(new_groups[i]); | ||||
|             node_map[new_groups[i].id] = new_groups[i] | ||||
|         } | ||||
|  | ||||
|         for (i=0;i<new_junctions.length;i++) { | ||||
|             var junction = new_junctions[i]; | ||||
|             addJunction(junction); | ||||
|             new_junctions[i] = addJunction(new_junctions[i]); | ||||
|             node_map[new_junctions[i].id] = new_junctions[i] | ||||
|         } | ||||
|  | ||||
|  | ||||
|         // Now the nodes have been fully updated, add them. | ||||
|         for (i=0;i<new_nodes.length;i++) { | ||||
|             var node = new_nodes[i]; | ||||
|             addNode(node); | ||||
|             new_nodes[i] = addNode(new_nodes[i]) | ||||
|             node_map[new_nodes[i].id] = new_nodes[i] | ||||
|         } | ||||
|  | ||||
|         // Finally validate them all. | ||||
|         // This has to be done after everything is added so that any checks for | ||||
|         // dependent config nodes will pass | ||||
| @@ -2375,6 +2399,39 @@ RED.nodes = (function() { | ||||
|             var node = new_nodes[i]; | ||||
|             RED.editor.validateNode(node); | ||||
|         } | ||||
|         const lookupNode = (id) => { | ||||
|             const mappedNode = node_map[id] | ||||
|             if (!mappedNode) { | ||||
|                 return null | ||||
|             } | ||||
|             if (mappedNode.__isProxy__) { | ||||
|                 return mappedNode | ||||
|             } else { | ||||
|                 return node_map[mappedNode.id] | ||||
|             } | ||||
|         } | ||||
|         // Update groups to reference proxy node objects | ||||
|         for (i=0;i<new_groups.length;i++) { | ||||
|             n = new_groups[i]; | ||||
|             // bypass the proxy in case the flow is locked | ||||
|             n.__node__.nodes = n.nodes.map(lookupNode) | ||||
|             // Just in case the group references a node that doesn't exist for some reason | ||||
|             n.__node__.nodes = n.nodes.filter(function(v) { | ||||
|                 if (v) { | ||||
|                     // Repair any nodes that have forgotten they are in this group | ||||
|                     if (v.g !== n.id) { | ||||
|                         v.g = n.id; | ||||
|                     } | ||||
|                 } | ||||
|                 return !!v | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Update links to use proxy node objects | ||||
|         for (i=0;i<new_links.length;i++) { | ||||
|             new_links[i].source = lookupNode(new_links[i].source.id) || new_links[i].source | ||||
|             new_links[i].target = lookupNode(new_links[i].target.id) || new_links[i].target | ||||
|         } | ||||
|  | ||||
|         RED.workspaces.refresh(); | ||||
|  | ||||
| @@ -2503,11 +2560,17 @@ RED.nodes = (function() { | ||||
|         junctions = {}; | ||||
|         junctionsByZ = {}; | ||||
|  | ||||
|         var workspaceIds = Object.keys(workspaces); | ||||
|         // Ensure all workspaces are unlocked so we don't get any edit-protection | ||||
|         // preventing removal | ||||
|         workspaceIds.forEach(function(id) { | ||||
|             workspaces[id].locked = false | ||||
|         }); | ||||
|  | ||||
|         var subflowIds = Object.keys(subflows); | ||||
|         subflowIds.forEach(function(id) { | ||||
|             RED.subflow.removeSubflow(id) | ||||
|         }); | ||||
|         var workspaceIds = Object.keys(workspaces); | ||||
|         workspaceIds.forEach(function(id) { | ||||
|             RED.workspaces.remove(workspaces[id]); | ||||
|         }); | ||||
| @@ -2528,10 +2591,14 @@ RED.nodes = (function() { | ||||
|     } | ||||
|  | ||||
|     function addGroup(group) { | ||||
|         if (!group.__isProxy__) { | ||||
|             group = new Proxy(group, nodeProxyHandler) | ||||
|         } | ||||
|         groupsByZ[group.z] = groupsByZ[group.z] || []; | ||||
|         groupsByZ[group.z].push(group); | ||||
|         groups[group.id] = group; | ||||
|         RED.events.emit("groups:add",group); | ||||
|         return group | ||||
|     } | ||||
|     function removeGroup(group) { | ||||
|         var i = groupsByZ[group.z].indexOf(group); | ||||
| @@ -2552,6 +2619,9 @@ RED.nodes = (function() { | ||||
|     } | ||||
|  | ||||
|     function addJunction(junction) { | ||||
|         if (!junction.__isProxy__) { | ||||
|             junction = new Proxy(junction, nodeProxyHandler) | ||||
|         } | ||||
|         junctionsByZ[junction.z] = junctionsByZ[junction.z] || [] | ||||
|         junctionsByZ[junction.z].push(junction) | ||||
|         junctions[junction.id] = junction; | ||||
| @@ -2559,6 +2629,7 @@ RED.nodes = (function() { | ||||
|             nodeLinks[junction.id] = {in:[],out:[]}; | ||||
|         } | ||||
|         RED.events.emit("junctions:add", junction) | ||||
|         return junction | ||||
|     } | ||||
|     function removeJunction(junction) { | ||||
|         var i = junctionsByZ[junction.z].indexOf(junction) | ||||
|   | ||||
| @@ -668,11 +668,6 @@ var RED = (function() { | ||||
|         ]}); | ||||
|  | ||||
|         menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [ | ||||
|             {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"}, | ||||
|             {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"}, | ||||
|             {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"}, | ||||
|             {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"}, | ||||
|             null, | ||||
|             {id: "menu-item-view-tools-align-left", label:RED._("menu.label.alignLeft"), disabled: true, onselect: "core:align-selection-to-left"}, | ||||
|             {id: "menu-item-view-tools-align-center", label:RED._("menu.label.alignCenter"), disabled: true, onselect: "core:align-selection-to-center"}, | ||||
|             {id: "menu-item-view-tools-align-right", label:RED._("menu.label.alignRight"), disabled: true, onselect: "core:align-selection-to-right"}, | ||||
| @@ -682,7 +677,12 @@ var RED = (function() { | ||||
|             {id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"}, | ||||
|             null, | ||||
|             {id: "menu-item-view-tools-distribute-horizontally", label:RED._("menu.label.distributeHorizontally"), disabled: true, onselect: "core:distribute-selection-horizontally"}, | ||||
|             {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"} | ||||
|             {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"}, | ||||
|             null, | ||||
|             {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"}, | ||||
|             {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"}, | ||||
|             {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"}, | ||||
|             {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"} | ||||
|         ]}); | ||||
|  | ||||
|         menuOptions.push(null); | ||||
|   | ||||
| @@ -503,7 +503,7 @@ RED.clipboard = (function() { | ||||
|         $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport); | ||||
|         $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)}); | ||||
|  | ||||
|         if (RED.workspaces.active() === 0) { | ||||
|         if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) { | ||||
|             $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected"); | ||||
|             $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected"); | ||||
|         } else { | ||||
| @@ -1271,15 +1271,17 @@ RED.clipboard = (function() { | ||||
|             RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget); | ||||
|  | ||||
|             $('#red-ui-workspace-chart').on("dragenter",function(event) { | ||||
|                 if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || | ||||
|                      $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                 if (!RED.workspaces.isActiveLocked() && ( | ||||
|                     $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || | ||||
|                      $.inArray("Files",event.originalEvent.dataTransfer.types) != -1)) { | ||||
|                     $("#red-ui-drop-target").css({display:'table'}).focus(); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             $('#red-ui-drop-target').on("dragover",function(event) { | ||||
|                 if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || | ||||
|                      $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                      $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 || | ||||
|                         RED.workspaces.isActiveLocked()) { | ||||
|                     event.preventDefault(); | ||||
|                 } | ||||
|             }) | ||||
| @@ -1287,27 +1289,29 @@ RED.clipboard = (function() { | ||||
|                 hideDropTarget(); | ||||
|             }) | ||||
|             .on("drop",function(event) { | ||||
|                 try { | ||||
|                     if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                         var data = event.originalEvent.dataTransfer.getData("text/plain"); | ||||
|                         data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); | ||||
|                         importNodes(data); | ||||
|                     } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                         var files = event.originalEvent.dataTransfer.files; | ||||
|                         if (files.length === 1) { | ||||
|                             var file = files[0]; | ||||
|                             var reader = new FileReader(); | ||||
|                             reader.onload = (function(theFile) { | ||||
|                                 return function(e) { | ||||
|                                     importNodes(e.target.result); | ||||
|                                 }; | ||||
|                             })(file); | ||||
|                             reader.readAsText(file); | ||||
|                 if (!RED.workspaces.isActiveLocked()) { | ||||
|                     try { | ||||
|                         if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                             var data = event.originalEvent.dataTransfer.getData("text/plain"); | ||||
|                             data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); | ||||
|                             importNodes(data); | ||||
|                         } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { | ||||
|                             var files = event.originalEvent.dataTransfer.files; | ||||
|                             if (files.length === 1) { | ||||
|                                 var file = files[0]; | ||||
|                                 var reader = new FileReader(); | ||||
|                                 reader.onload = (function(theFile) { | ||||
|                                     return function(e) { | ||||
|                                         importNodes(e.target.result); | ||||
|                                     }; | ||||
|                                 })(file); | ||||
|                                 reader.readAsText(file); | ||||
|                             } | ||||
|                         } | ||||
|                     } catch(err) { | ||||
|                         // Ensure any errors throw above doesn't stop the drop target from | ||||
|                         // being hidden. | ||||
|                     } | ||||
|                 } catch(err) { | ||||
|                     // Ensure any errors throw above doesn't stop the drop target from | ||||
|                     // being hidden. | ||||
|                 } | ||||
|                 hideDropTarget(); | ||||
|                 event.preventDefault(); | ||||
|   | ||||
| @@ -28,8 +28,10 @@ RED.contextMenu = (function () { | ||||
|             const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1 | ||||
|             const canDelete = hasSelection || hasLinks | ||||
|             const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' | ||||
|  | ||||
|             const canEdit = !RED.workspaces.isActiveLocked() | ||||
|             const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g | ||||
|             const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0 | ||||
|             const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0 | ||||
|             const offset = $("#red-ui-workspace-chart").offset() | ||||
|  | ||||
|             let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() | ||||
| @@ -42,89 +44,133 @@ RED.contextMenu = (function () { | ||||
|             } | ||||
|  | ||||
|             menuItems.push( | ||||
|                 { onselect: 'core:show-action-list', onpostselect: function () { } }, | ||||
|                 { | ||||
|                     label: RED._("contextMenu.insert"), | ||||
|                     options: [ | ||||
|                         { | ||||
|                             label: RED._("contextMenu.node"), | ||||
|                             onselect: function () { | ||||
|                                 RED.view.showQuickAddDialog({ | ||||
|                                     position: [addX, addY], | ||||
|                                     touchTrigger: true, | ||||
|                                     splice: isSingleLink ? selection.links[0] : undefined, | ||||
|                                     // spliceMultiple: isMultipleLinks | ||||
|                                 }) | ||||
|                             } | ||||
|                         }, | ||||
|                         (hasLinks) ? { // has least 1 wire selected | ||||
|                             label: RED._("contextMenu.junction"), | ||||
|                             onselect: 'core:split-wires-with-junctions', | ||||
|                             disabled: !hasLinks | ||||
|                         } : { | ||||
|                             label: RED._("contextMenu.junction"), | ||||
|                             onselect: function () { | ||||
|                                 const nn = { | ||||
|                                     _def: { defaults: {} }, | ||||
|                                     type: 'junction', | ||||
|                                     z: RED.workspaces.active(), | ||||
|                                     id: RED.nodes.id(), | ||||
|                                     x: addX, | ||||
|                                     y: addY, | ||||
|                                     w: 0, h: 0, | ||||
|                                     outputs: 1, | ||||
|                                     inputs: 1, | ||||
|                                     dirty: true | ||||
|                                 } | ||||
|                                 const historyEvent = { | ||||
|                                     dirty: RED.nodes.dirty(), | ||||
|                                     t: 'add', | ||||
|                                     junctions: [nn] | ||||
|                                 } | ||||
|                                 RED.nodes.addJunction(nn); | ||||
|                                 RED.history.push(historyEvent); | ||||
|                                 RED.nodes.dirty(true); | ||||
|                                 RED.view.select({nodes: [nn] }); | ||||
|                                 RED.view.redraw(true) | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             label: RED._("contextMenu.linkNodes"), | ||||
|                             onselect: 'core:split-wire-with-link-nodes', | ||||
|                             disabled: !hasLinks | ||||
|                         } | ||||
|                     ] | ||||
|  | ||||
|  | ||||
|  | ||||
|                 } | ||||
|                 { onselect: 'core:show-action-list', onpostselect: function () { } } | ||||
|             ) | ||||
|  | ||||
|             const insertOptions = [] | ||||
|             menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions }) | ||||
|             insertOptions.push( | ||||
|                 { | ||||
|                     label: RED._("contextMenu.node"), | ||||
|                     onselect: function () { | ||||
|                         RED.view.showQuickAddDialog({ | ||||
|                             position: [addX, addY], | ||||
|                             touchTrigger: true, | ||||
|                             splice: isSingleLink ? selection.links[0] : undefined, | ||||
|                             // spliceMultiple: isMultipleLinks | ||||
|                         }) | ||||
|                     }, | ||||
|                     disabled: !canEdit | ||||
|                 }, | ||||
|                 (hasLinks) ? { // has least 1 wire selected | ||||
|                     label: RED._("contextMenu.junction"), | ||||
|                     onselect: 'core:split-wires-with-junctions', | ||||
|                     disabled: !canEdit || !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) | ||||
|                     }, | ||||
|                     disabled: !canEdit | ||||
|                 }, | ||||
|                 { | ||||
|                     label: RED._("contextMenu.linkNodes"), | ||||
|                     onselect: 'core:split-wire-with-link-nodes', | ||||
|                     disabled: !canEdit || !hasLinks | ||||
|                 }, | ||||
|                 null, | ||||
|                 { onselect: 'core:show-import-dialog', label: RED._('common.label.import')}, | ||||
|                 { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') } | ||||
|             ) | ||||
|             if (hasSelection && canEdit) { | ||||
|                 const nodeOptions = [] | ||||
|                 if (!hasMultipleSelection && !isGroup) { | ||||
|                     nodeOptions.push( | ||||
|                         { onselect: 'core:show-node-help' }, | ||||
|                         null | ||||
|                     ) | ||||
|                 } | ||||
|                 nodeOptions.push( | ||||
|                     { onselect: 'core:enable-selected-nodes' }, | ||||
|                     { onselect: 'core:disable-selected-nodes' }, | ||||
|                     null, | ||||
|                     { onselect: 'core:show-selected-node-labels' }, | ||||
|                     { onselect: 'core:hide-selected-node-labels' } | ||||
|                 ) | ||||
|                 menuItems.push({ | ||||
|                     label: RED._('sidebar.info.node'), | ||||
|                     options: nodeOptions | ||||
|                 }) | ||||
|                 menuItems.push({ | ||||
|                     label: RED._('sidebar.info.group'), | ||||
|                     options: [ | ||||
|                         { onselect: 'core:group-selection' }, | ||||
|                         { onselect: 'core:ungroup-selection', disabled: !hasGroup }, | ||||
|                         null, | ||||
|                         { onselect: 'core:copy-group-style', disabled: !hasGroup }, | ||||
|                         { onselect: 'core:paste-group-style', disabled: !hasGroup} | ||||
|                     ] | ||||
|                 }) | ||||
|                 if (canRemoveFromGroup) { | ||||
|                     menuItems[menuItems.length - 1].options.push( | ||||
|                         null, | ||||
|                         { onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") } | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|             if (canEdit && hasMultipleSelection) { | ||||
|                 menuItems.push({ | ||||
|                     label: RED._('menu.label.arrange'), | ||||
|                     options: [ | ||||
|                         { label:RED._("menu.label.alignLeft"), onselect: "core:align-selection-to-left"}, | ||||
|                         { label:RED._("menu.label.alignCenter"), onselect: "core:align-selection-to-center"}, | ||||
|                         { label:RED._("menu.label.alignRight"), onselect: "core:align-selection-to-right"}, | ||||
|                         null, | ||||
|                         { label:RED._("menu.label.alignTop"), onselect: "core:align-selection-to-top"}, | ||||
|                         { label:RED._("menu.label.alignMiddle"), onselect: "core:align-selection-to-middle"}, | ||||
|                         { label:RED._("menu.label.alignBottom"), onselect: "core:align-selection-to-bottom"}, | ||||
|                         null, | ||||
|                         { label:RED._("menu.label.distributeHorizontally"), onselect: "core:distribute-selection-horizontally"}, | ||||
|                         { label:RED._("menu.label.distributeVertically"), onselect: "core:distribute-selection-vertically"} | ||||
|                     ] | ||||
|                 }) | ||||
|             } | ||||
|  | ||||
|  | ||||
|             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:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !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:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() }, | ||||
|                 { onselect: 'core:delete-selection', disabled: !canEdit || !canDelete }, | ||||
|                 { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete }, | ||||
|                 { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, | ||||
|                 { onselect: 'core:select-all-nodes' } | ||||
|                 { onselect: 'core:select-all-nodes' }, | ||||
|             ) | ||||
|  | ||||
|             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"; | ||||
|   | ||||
| @@ -558,6 +558,11 @@ RED.deploy = (function() { | ||||
|                 RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success"); | ||||
|             } | ||||
|             RED.nodes.eachNode(function (node) { | ||||
|                 const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null); | ||||
|                 const isLocked = flow ? flow.locked : false; | ||||
|                 if (flow && isLocked) { | ||||
|                     flow.locked = false; | ||||
|                 } | ||||
|                 if (node.changed) { | ||||
|                     node.dirty = true; | ||||
|                     node.changed = false; | ||||
| @@ -569,6 +574,9 @@ RED.deploy = (function() { | ||||
|                 if (node.credentials) { | ||||
|                     delete node.credentials; | ||||
|                 } | ||||
|                 if (flow && isLocked) { | ||||
|                     flow.locked = isLocked; | ||||
|                 } | ||||
|             }); | ||||
|             RED.nodes.eachConfig(function (confNode) { | ||||
|                 confNode.changed = false; | ||||
|   | ||||
| @@ -1847,11 +1847,15 @@ RED.editor = (function() { | ||||
|                             workspace.disabled = disabled; | ||||
|  | ||||
|                             $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); | ||||
|                             if (workspace.id === RED.workspaces.active()) { | ||||
|                                 $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         var locked = $("#node-input-locked").prop("checked"); | ||||
|                         if (workspace.locked !== locked) { | ||||
|                             editState.changes.locked = workspace.locked; | ||||
|                             editState.changed = true; | ||||
|                             workspace.locked = locked; | ||||
|                             $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked); | ||||
|                         } | ||||
|                         if (editState.changed) { | ||||
|                             var historyEvent = { | ||||
|                                 t: "edit", | ||||
| @@ -1892,6 +1896,7 @@ RED.editor = (function() { | ||||
|                 var trayBody = tray.find('.red-ui-tray-body'); | ||||
|                 trayBody.parent().css('overflow','hidden'); | ||||
|                 var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter) | ||||
|                 var trayFooterRight = $('<div class="red-ui-tray-footer-right"></div>').appendTo(trayFooter) | ||||
|  | ||||
|                 var nodeEditPanes = [ | ||||
|                     'editor-tab-flow-properties', | ||||
| @@ -1906,6 +1911,18 @@ RED.editor = (function() { | ||||
|                     disabledIcon: "fa-ban", | ||||
|                     invertState: true | ||||
|                 }) | ||||
|  | ||||
|                 if (!workspace.hasOwnProperty("locked")) { | ||||
|                     workspace.locked = false; | ||||
|                 } | ||||
|                 $('<input id="node-input-locked" type="checkbox">').prop("checked",workspace.locked).appendTo(trayFooterRight).toggleButton({ | ||||
|                     enabledLabel: 'Unlocked', | ||||
|                     enabledIcon: "fa-unlock-alt", | ||||
|                     disabledLabel: 'Locked', | ||||
|                     disabledIcon: "fa-lock", | ||||
|                     invertState: true | ||||
|                 }) | ||||
|  | ||||
|                 prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", defaultTab, function(_activeEditPanes) { | ||||
|                     activeEditPanes = _activeEditPanes; | ||||
|                     trayBody.i18n(); | ||||
|   | ||||
| @@ -52,8 +52,6 @@ | ||||
|                     node.info = info; | ||||
|                 } | ||||
|                 $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled); | ||||
|                 $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|   | ||||
| @@ -185,6 +185,8 @@ RED.group = (function() { | ||||
|             var activateMerge = false; | ||||
|             var activateRemove = false; | ||||
|             var singleGroupSelected = false; | ||||
|             var locked = RED.workspaces.isActiveLocked() | ||||
|  | ||||
|             if (activateGroup) { | ||||
|                 singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group'; | ||||
|                 selection.nodes.forEach(function (n) { | ||||
| @@ -199,12 +201,12 @@ RED.group = (function() { | ||||
|                     activateMerge = (selection.nodes.length > 1); | ||||
|                 } | ||||
|             } | ||||
|             RED.menu.setDisabled("menu-item-group-group", !activateGroup); | ||||
|             RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup); | ||||
|             RED.menu.setDisabled("menu-item-group-merge", !activateMerge); | ||||
|             RED.menu.setDisabled("menu-item-group-remove", !activateRemove); | ||||
|             RED.menu.setDisabled("menu-item-group-group", locked || !activateGroup); | ||||
|             RED.menu.setDisabled("menu-item-group-ungroup", locked || !activateUngroup); | ||||
|             RED.menu.setDisabled("menu-item-group-merge", locked || !activateMerge); | ||||
|             RED.menu.setDisabled("menu-item-group-remove", locked || !activateRemove); | ||||
|             RED.menu.setDisabled("menu-item-edit-copy-group-style", !singleGroupSelected); | ||||
|             RED.menu.setDisabled("menu-item-edit-paste-group-style", !activateUngroup); | ||||
|             RED.menu.setDisabled("menu-item-edit-paste-group-style", locked || !activateUngroup); | ||||
|         }); | ||||
|  | ||||
|         RED.actions.add("core:group-selection", function() { groupSelection() }) | ||||
| @@ -261,6 +263,7 @@ RED.group = (function() { | ||||
|         } | ||||
|     } | ||||
|     function pasteGroupStyle() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (RED.view.state() !== RED.state.DEFAULT) { return } | ||||
|         if (groupStyleClipboard) { | ||||
|             var selection = RED.view.selection(); | ||||
| @@ -295,6 +298,7 @@ RED.group = (function() { | ||||
|     } | ||||
|  | ||||
|     function groupSelection() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (RED.view.state() !== RED.state.DEFAULT) { return } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
| @@ -313,6 +317,7 @@ RED.group = (function() { | ||||
|         } | ||||
|     } | ||||
|     function ungroupSelection() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (RED.view.state() !== RED.state.DEFAULT) { return } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
| @@ -336,6 +341,7 @@ RED.group = (function() { | ||||
|     } | ||||
|  | ||||
|     function ungroup(g) { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         var nodes = []; | ||||
|         var parentGroup = RED.nodes.group(g.g); | ||||
|         g.nodes.forEach(function(n) { | ||||
| @@ -362,6 +368,7 @@ RED.group = (function() { | ||||
|     } | ||||
|  | ||||
|     function mergeSelection() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (RED.view.state() !== RED.state.DEFAULT) { return } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
| @@ -431,6 +438,7 @@ RED.group = (function() { | ||||
|     } | ||||
|  | ||||
|     function removeSelection() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (RED.view.state() !== RED.state.DEFAULT) { return } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
| @@ -458,6 +466,7 @@ RED.group = (function() { | ||||
|         } | ||||
|     } | ||||
|     function createGroup(nodes) { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (nodes.length === 0) { | ||||
|             return; | ||||
|         } | ||||
| @@ -480,7 +489,7 @@ RED.group = (function() { | ||||
|         } | ||||
|  | ||||
|         group.z = nodes[0].z; | ||||
|         RED.nodes.addGroup(group); | ||||
|         group = RED.nodes.addGroup(group); | ||||
|  | ||||
|         try { | ||||
|             addToGroup(group,nodes); | ||||
| @@ -563,6 +572,7 @@ RED.group = (function() { | ||||
|         markDirty(group); | ||||
|     } | ||||
|     function removeFromGroup(group, nodes, reparent) { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (!Array.isArray(nodes)) { | ||||
|             nodes = [nodes]; | ||||
|         } | ||||
|   | ||||
| @@ -282,6 +282,7 @@ RED.palette = (function() { | ||||
|             var hoverGroup; | ||||
|             var paletteWidth; | ||||
|             var paletteTop; | ||||
|             var dropEnabled; | ||||
|             $(d).draggable({ | ||||
|                 helper: 'clone', | ||||
|                 appendTo: '#red-ui-editor', | ||||
| @@ -289,6 +290,7 @@ RED.palette = (function() { | ||||
|                 revertDuration: 200, | ||||
|                 containment:'#red-ui-main-container', | ||||
|                 start: function() { | ||||
|                     dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked); | ||||
|                     paletteWidth = $("#red-ui-palette").width(); | ||||
|                     paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; | ||||
|                     hoverGroup = null; | ||||
| @@ -299,96 +301,100 @@ RED.palette = (function() { | ||||
|                     RED.view.focus(); | ||||
|                 }, | ||||
|                 stop: function() { | ||||
|                     d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); | ||||
|                     if (hoverGroup) { | ||||
|                         document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); | ||||
|                     if (dropEnabled) { | ||||
|                         d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); | ||||
|                         if (hoverGroup) { | ||||
|                             document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); | ||||
|                         } | ||||
|                         if (activeGroup) { | ||||
|                             document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); | ||||
|                         } | ||||
|                         if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } | ||||
|                         if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } | ||||
|                     } | ||||
|                     if (activeGroup) { | ||||
|                         document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); | ||||
|                     } | ||||
|                     if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } | ||||
|                     if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } | ||||
|                 }, | ||||
|                 drag: function(e,ui) { | ||||
|                     var paletteNode = getPaletteNode(nt); | ||||
|                     ui.originalPosition.left = paletteNode.offset().left; | ||||
|                     mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); | ||||
|                     mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10; | ||||
|                     if (!groupTimer) { | ||||
|                         groupTimer = setTimeout(function() { | ||||
|                             var mx = mouseX / RED.view.scale(); | ||||
|                             var my = mouseY / RED.view.scale(); | ||||
|                             var group = RED.view.getGroupAtPoint(mx,my); | ||||
|                             if (group !== hoverGroup) { | ||||
|                                 if (hoverGroup) { | ||||
|                                     document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); | ||||
|                                 } | ||||
|                                 if (group) { | ||||
|                                     document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); | ||||
|                                 } | ||||
|                                 hoverGroup = group; | ||||
|                                 if (hoverGroup) { | ||||
|                                     $(ui.helper).data('group',hoverGroup); | ||||
|                                 } else { | ||||
|                                     $(ui.helper).removeData('group'); | ||||
|                                 } | ||||
|                             } | ||||
|                             groupTimer = null; | ||||
|  | ||||
|                         },200) | ||||
|                     } | ||||
|                     if (def.inputs > 0 && def.outputs > 0) { | ||||
|                         if (!spliceTimer) { | ||||
|                             spliceTimer = setTimeout(function() { | ||||
|                                 var nodes = []; | ||||
|                                 var bestDistance = Infinity; | ||||
|                                 var bestLink = null; | ||||
|                                 if (chartSVG.getIntersectionList) { | ||||
|                                     var svgRect = chartSVG.createSVGRect(); | ||||
|                                     svgRect.x = mouseX; | ||||
|                                     svgRect.y = mouseY; | ||||
|                                     svgRect.width = 1; | ||||
|                                     svgRect.height = 1; | ||||
|                                     nodes = chartSVG.getIntersectionList(svgRect,chartSVG); | ||||
|                                 } else { | ||||
|                                     // Firefox doesn't do getIntersectionList and that | ||||
|                                     // makes us sad | ||||
|                                     nodes = RED.view.getLinksAtPoint(mouseX,mouseY); | ||||
|                                 } | ||||
|                     if (dropEnabled) { | ||||
|                         mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); | ||||
|                         mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10; | ||||
|                         if (!groupTimer) { | ||||
|                             groupTimer = setTimeout(function() { | ||||
|                                 var mx = mouseX / RED.view.scale(); | ||||
|                                 var my = mouseY / RED.view.scale(); | ||||
|                                 for (var i=0;i<nodes.length;i++) { | ||||
|                                     var node = d3.select(nodes[i]); | ||||
|                                     if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) { | ||||
|                                         var length = nodes[i].getTotalLength(); | ||||
|                                         for (var j=0;j<length;j+=10) { | ||||
|                                             var p = nodes[i].getPointAtLength(j); | ||||
|                                             var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my)); | ||||
|                                             if (d2 < 200 && d2 < bestDistance) { | ||||
|                                                 bestDistance = d2; | ||||
|                                                 bestLink = nodes[i]; | ||||
|                                 var group = RED.view.getGroupAtPoint(mx,my); | ||||
|                                 if (group !== hoverGroup) { | ||||
|                                     if (hoverGroup) { | ||||
|                                         document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); | ||||
|                                     } | ||||
|                                     if (group) { | ||||
|                                         document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); | ||||
|                                     } | ||||
|                                     hoverGroup = group; | ||||
|                                     if (hoverGroup) { | ||||
|                                         $(ui.helper).data('group',hoverGroup); | ||||
|                                     } else { | ||||
|                                         $(ui.helper).removeData('group'); | ||||
|                                     } | ||||
|                                 } | ||||
|                                 groupTimer = null; | ||||
|  | ||||
|                             },200) | ||||
|                         } | ||||
|                         if (def.inputs > 0 && def.outputs > 0) { | ||||
|                             if (!spliceTimer) { | ||||
|                                 spliceTimer = setTimeout(function() { | ||||
|                                     var nodes = []; | ||||
|                                     var bestDistance = Infinity; | ||||
|                                     var bestLink = null; | ||||
|                                     if (chartSVG.getIntersectionList) { | ||||
|                                         var svgRect = chartSVG.createSVGRect(); | ||||
|                                         svgRect.x = mouseX; | ||||
|                                         svgRect.y = mouseY; | ||||
|                                         svgRect.width = 1; | ||||
|                                         svgRect.height = 1; | ||||
|                                         nodes = chartSVG.getIntersectionList(svgRect,chartSVG); | ||||
|                                     } else { | ||||
|                                         // Firefox doesn't do getIntersectionList and that | ||||
|                                         // makes us sad | ||||
|                                         nodes = RED.view.getLinksAtPoint(mouseX,mouseY); | ||||
|                                     } | ||||
|                                     var mx = mouseX / RED.view.scale(); | ||||
|                                     var my = mouseY / RED.view.scale(); | ||||
|                                     for (var i=0;i<nodes.length;i++) { | ||||
|                                         var node = d3.select(nodes[i]); | ||||
|                                         if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) { | ||||
|                                             var length = nodes[i].getTotalLength(); | ||||
|                                             for (var j=0;j<length;j+=10) { | ||||
|                                                 var p = nodes[i].getPointAtLength(j); | ||||
|                                                 var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my)); | ||||
|                                                 if (d2 < 200 && d2 < bestDistance) { | ||||
|                                                     bestDistance = d2; | ||||
|                                                     bestLink = nodes[i]; | ||||
|                                                 } | ||||
|                                             } | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                                 if (activeSpliceLink && activeSpliceLink !== bestLink) { | ||||
|                                     d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false); | ||||
|                                 } | ||||
|                                 if (bestLink) { | ||||
|                                     d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true) | ||||
|                                 } else { | ||||
|                                     d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); | ||||
|                                 } | ||||
|                                 if (activeSpliceLink !== bestLink) { | ||||
|                                     if (bestLink) { | ||||
|                                         $(ui.helper).data('splice',d3.select(bestLink).data()[0]); | ||||
|                                     } else { | ||||
|                                         $(ui.helper).removeData('splice'); | ||||
|                                     if (activeSpliceLink && activeSpliceLink !== bestLink) { | ||||
|                                         d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false); | ||||
|                                     } | ||||
|                                 } | ||||
|                                 activeSpliceLink = bestLink; | ||||
|                                 spliceTimer = null; | ||||
|                             },200); | ||||
|                                     if (bestLink) { | ||||
|                                         d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true) | ||||
|                                     } else { | ||||
|                                         d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); | ||||
|                                     } | ||||
|                                     if (activeSpliceLink !== bestLink) { | ||||
|                                         if (bestLink) { | ||||
|                                             $(ui.helper).data('splice',d3.select(bestLink).data()[0]); | ||||
|                                         } else { | ||||
|                                             $(ui.helper).removeData('splice'); | ||||
|                                         } | ||||
|                                     } | ||||
|                                     activeSpliceLink = bestLink; | ||||
|                                     spliceTimer = null; | ||||
|                                 },200); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -273,6 +273,11 @@ RED.subflow = (function() { | ||||
|         var subflowInstances = []; | ||||
|         if (activeSubflow) { | ||||
|             RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) { | ||||
|                 const parentFlow = RED.nodes.workspace(n.z) | ||||
|                 const wasLocked = parentFlow && parentFlow.locked | ||||
|                 if (wasLocked) { | ||||
|                     parentFlow.locked = false | ||||
|                 } | ||||
|                 subflowInstances.push({ | ||||
|                     id: n.id, | ||||
|                     changed: n.changed | ||||
| @@ -285,6 +290,9 @@ RED.subflow = (function() { | ||||
|                 n.resize = true; | ||||
|                 n.dirty = true; | ||||
|                 RED.editor.updateNodeProperties(n); | ||||
|                 if (wasLocked) { | ||||
|                     parentFlow.locked = true | ||||
|                 } | ||||
|             }); | ||||
|             RED.editor.validateNode(activeSubflow); | ||||
|             return { | ||||
| @@ -450,6 +458,9 @@ RED.subflow = (function() { | ||||
|             return | ||||
|         } | ||||
|         if (subflow.instances.length > 0) { | ||||
|             if (subflow.instances.some(sf => { const ws = RED.nodes.workspace(sf.z); return ws?ws.locked:false })) { | ||||
|                 return | ||||
|             } | ||||
|             const msg = $('<div>') | ||||
|             $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); | ||||
|             $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg); | ||||
| @@ -554,7 +565,7 @@ RED.subflow = (function() { | ||||
|             } | ||||
|         }); | ||||
|         RED.events.on("view:selection-changed",function(selection) { | ||||
|             if (!selection.nodes) { | ||||
|             if (!selection.nodes || RED.workspaces.isActiveLocked()) { | ||||
|                 RED.menu.setDisabled("menu-item-subflow-convert",true); | ||||
|             } else { | ||||
|                 RED.menu.setDisabled("menu-item-subflow-convert",false); | ||||
| @@ -617,6 +628,9 @@ RED.subflow = (function() { | ||||
|     } | ||||
|  | ||||
|     function convertToSubflow() { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (!selection.nodes) { | ||||
|             RED.notify(RED._("subflow.errors.noNodesSelected"),"error"); | ||||
| @@ -772,7 +786,7 @@ RED.subflow = (function() { | ||||
|         } | ||||
|         subflowInstance._def = RED.nodes.getType(subflowInstance.type); | ||||
|         RED.editor.validateNode(subflowInstance); | ||||
|         RED.nodes.add(subflowInstance); | ||||
|         subflowInstance = RED.nodes.add(subflowInstance); | ||||
|  | ||||
|         if (containingGroup) { | ||||
|             RED.group.addToGroup(containingGroup, subflowInstance); | ||||
|   | ||||
| @@ -43,12 +43,15 @@ RED.sidebar.config = (function() { | ||||
|  | ||||
|     var categories = {}; | ||||
|  | ||||
|     function getOrCreateCategory(name,parent,label) { | ||||
|     function getOrCreateCategory(name,parent,label,isLocked) { | ||||
|         name = name.replace(/\./i,"-"); | ||||
|         if (!categories[name]) { | ||||
|             var container = $('<div class="red-ui-palette-category red-ui-sidebar-config-category" id="red-ui-sidebar-config-category-'+name+'"></div>').appendTo(parent); | ||||
|             var header = $('<div class="red-ui-sidebar-config-tray-header red-ui-palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container); | ||||
|             let lockIcon | ||||
|             if (label) { | ||||
|                 lockIcon = $('<span style="margin-right: 5px"><i class="fa fa-lock"/></span>').appendTo(header) | ||||
|                 lockIcon.toggle(!!isLocked) | ||||
|                 $('<span class="red-ui-palette-node-config-label"/>').text(label).appendTo(header); | ||||
|             } else { | ||||
|                 $('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header); | ||||
| @@ -62,6 +65,7 @@ RED.sidebar.config = (function() { | ||||
|             var icon = header.find("i"); | ||||
|             var result = { | ||||
|                 label: label, | ||||
|                 lockIcon, | ||||
|                 list: category, | ||||
|                 size: function() { | ||||
|                     return result.list.find("li:not(.red-ui-palette-node-config-none)").length | ||||
| @@ -100,6 +104,9 @@ RED.sidebar.config = (function() { | ||||
|             }); | ||||
|             categories[name] = result; | ||||
|         } else { | ||||
|             if (isLocked !== undefined && categories[name].lockIcon) { | ||||
|                 categories[name].lockIcon.toggle(!!isLocked) | ||||
|             } | ||||
|             if (categories[name].label !== label) { | ||||
|                 categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label); | ||||
|                 categories[name].label = label; | ||||
| @@ -216,7 +223,7 @@ RED.sidebar.config = (function() { | ||||
|  | ||||
|         RED.nodes.eachWorkspace(function(ws) { | ||||
|             validList[ws.id.replace(/\./g,"-")] = true; | ||||
|             getOrCreateCategory(ws.id,flowCategories,ws.label); | ||||
|             getOrCreateCategory(ws.id,flowCategories,ws.label, ws.locked); | ||||
|         }) | ||||
|         RED.nodes.eachSubflow(function(sf) { | ||||
|             validList[sf.id.replace(/\./g,"-")] = true; | ||||
| @@ -274,6 +281,15 @@ RED.sidebar.config = (function() { | ||||
|                     changes: {}, | ||||
|                     dirty: RED.nodes.dirty() | ||||
|                 } | ||||
|                 for (let i = 0; i < selectedNodes.length; i++) { | ||||
|                     let node = RED.nodes.node(selectedNodes[i]) | ||||
|                     if (node.z) { | ||||
|                         let ws = RED.nodes.workspace(node.z) | ||||
|                         if (ws && ws.locked) { | ||||
|                             return | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 selectedNodes.forEach(function(id) { | ||||
|                     var node = RED.nodes.node(id); | ||||
|                     try { | ||||
|   | ||||
| @@ -141,7 +141,8 @@ RED.sidebar.help = (function() { | ||||
|         RED.events.on('registry:node-type-removed', queueRefresh); | ||||
|         RED.events.on('subflows:change', refreshSubflow); | ||||
|  | ||||
|         RED.actions.add("core:show-help-tab",show); | ||||
|         RED.actions.add("core:show-help-tab", show); | ||||
|         RED.actions.add("core:show-node-help", showNodeHelp) | ||||
|  | ||||
|     } | ||||
|  | ||||
| @@ -338,6 +339,19 @@ RED.sidebar.help = (function() { | ||||
|         resizeStack(); | ||||
|     } | ||||
|  | ||||
|     function showNodeHelp(node) { | ||||
|         if (!node) { | ||||
|             const selection = RED.view.selection() | ||||
|             if (selection.nodes && selection.nodes.length > 0) { | ||||
|                 node = selection.nodes.find(n => n.type !== 'group' && n.type !== 'junction') | ||||
|             } | ||||
|         } | ||||
|         if (node) { | ||||
|             show(node.type, true) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // TODO: DRY - projects.js | ||||
|     function addTargetToExternalLinks(el) { | ||||
|         $(el).find("a").each(function(el) { | ||||
|   | ||||
| @@ -221,6 +221,22 @@ RED.sidebar.info.outliner = (function() { | ||||
|         } else { | ||||
|             $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls) | ||||
|         } | ||||
|         if (n.type === 'tab') { | ||||
|             var lockToggleButton = $('<button type="button" class="red-ui-info-outline-item-control-lock red-ui-button red-ui-button-small"><i class="fa fa-unlock-alt"></i><i class="fa fa-lock"></i></button>').appendTo(controls).on("click",function(evt) { | ||||
|                 evt.preventDefault(); | ||||
|                 evt.stopPropagation(); | ||||
|                 if (n.locked) { | ||||
|                     RED.workspaces.unlock(n.id) | ||||
|                 } else { | ||||
|                     RED.workspaces.lock(n.id) | ||||
|                 } | ||||
|             }) | ||||
|             RED.popover.tooltip(lockToggleButton,function() { | ||||
|                 return RED._("common.label."+(n.locked?"unlock":"lock")); | ||||
|             }); | ||||
|         } else { | ||||
|             $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls) | ||||
|         } | ||||
|         controls.find("button").on("dblclick", function(evt) { | ||||
|             evt.preventDefault(); | ||||
|             evt.stopPropagation(); | ||||
| @@ -364,6 +380,8 @@ RED.sidebar.info.outliner = (function() { | ||||
|         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) | ||||
|         objects[ws.id].element.toggleClass("red-ui-info-outline-item-locked", !!ws.locked) | ||||
|         objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-locked", !!ws.locked) | ||||
|         updateSearch(); | ||||
|  | ||||
|     } | ||||
| @@ -378,6 +396,8 @@ RED.sidebar.info.outliner = (function() { | ||||
|         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) | ||||
|         existingObject.element.toggleClass("red-ui-info-outline-item-locked", !!n.locked) | ||||
|         existingObject.treeList.container.toggleClass("red-ui-info-outline-item-locked", !!n.locked) | ||||
|         updateSearch(); | ||||
|     } | ||||
|     function onFlowsReorder(order) { | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|  **/ | ||||
|  | ||||
| RED.view.tools = (function() { | ||||
|  | ||||
|     'use strict'; | ||||
|     function selectConnected(type) { | ||||
|         var selection = RED.view.selection(); | ||||
|         var visited = new Set(); | ||||
| @@ -39,6 +39,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function alignToGrid() { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             var changedNodes = []; | ||||
| @@ -87,6 +90,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function moveSelection(dx,dy) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         if (moving_set === null) { | ||||
|             moving_set = []; | ||||
|             var selection = RED.view.selection(); | ||||
| @@ -153,6 +159,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function setSelectedNodeLabelState(labelShown) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         var historyEvents = []; | ||||
|         var nodes = []; | ||||
| @@ -439,6 +448,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function alignSelectionToEdge(direction) { | ||||
|         // if (RED.workspaces.isActiveLocked()) { | ||||
|         //     return | ||||
|         // } | ||||
|         var selection = RED.view.selection(); | ||||
|  | ||||
|         if (selection.nodes && selection.nodes.length > 1) { | ||||
| @@ -539,8 +551,10 @@ RED.view.tools = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function distributeSelection(direction) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|  | ||||
|         if (selection.nodes && selection.nodes.length > 2) { | ||||
| @@ -699,6 +713,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function reorderSelection(dir) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             var nodesToMove = []; | ||||
| @@ -734,8 +751,10 @@ RED.view.tools = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function wireSeriesOfNodes() { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             if (selection.nodes.length > 1) { | ||||
| @@ -776,6 +795,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function wireNodeToMultiple() { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             if (selection.nodes.length > 1) { | ||||
| @@ -823,6 +845,9 @@ RED.view.tools = (function() { | ||||
|      * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes. | ||||
|      */ | ||||
|     function splitWiresWithLinkNodes(wires) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); | ||||
|         if (!wiresToSplit) { | ||||
|             return | ||||
| @@ -877,7 +902,6 @@ RED.view.tools = (function() { | ||||
|             if(!nnLinkOut) { | ||||
|                 const nLinkOut = RED.view.createNode("link out"); //create link node | ||||
|                 nnLinkOut = nLinkOut.node; | ||||
|                 nodeSrcMap[linkOutMapId] = nnLinkOut; | ||||
|                 let yOffset = 0; | ||||
|                 if(nSrc.outputs > 1) { | ||||
|  | ||||
| @@ -892,7 +916,8 @@ RED.view.tools = (function() { | ||||
|                     updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset); | ||||
|                 } | ||||
|                 //add created node | ||||
|                 RED.nodes.add(nnLinkOut); | ||||
|                 nnLinkOut = RED.nodes.add(nnLinkOut); | ||||
|                 nodeSrcMap[linkOutMapId] = nnLinkOut; | ||||
|                 RED.editor.validateNode(nnLinkOut); | ||||
|                 history.events.push(nLinkOut.historyEvent); | ||||
|                 //connect node to link node | ||||
| @@ -913,10 +938,10 @@ RED.view.tools = (function() { | ||||
|             if(!nnLinkIn) { | ||||
|                 const nLinkIn = RED.view.createNode("link in"); //create link node | ||||
|                 nnLinkIn = nLinkIn.node; | ||||
|                 nodeTrgMap[nTrg.id] = nnLinkIn; | ||||
|                 updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0); | ||||
|                 //add created node | ||||
|                 RED.nodes.add(nnLinkIn); | ||||
|                 nnLinkIn = RED.nodes.add(nnLinkIn); | ||||
|                 nodeTrgMap[nTrg.id] = nnLinkIn; | ||||
|                 RED.editor.validateNode(nnLinkIn); | ||||
|                 history.events.push(nLinkIn.historyEvent); | ||||
|                 //connect node to link node | ||||
| @@ -991,6 +1016,9 @@ RED.view.tools = (function() { | ||||
|      * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory` | ||||
|      */ | ||||
|     function generateNodeNames(node, options) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         options = Object.assign({ | ||||
|             renameBlank: true, | ||||
|             renameClash: true, | ||||
| @@ -1061,6 +1089,9 @@ RED.view.tools = (function() { | ||||
|     } | ||||
|  | ||||
|     function addJunctionsToWires(wires) { | ||||
|         if (RED.workspaces.isActiveLocked()) { | ||||
|             return | ||||
|         } | ||||
|         let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); | ||||
|         if (!wiresToSplit) { | ||||
|             return | ||||
| @@ -1131,7 +1162,7 @@ RED.view.tools = (function() { | ||||
|  | ||||
|             var nodeGroups = new Set() | ||||
|  | ||||
|             RED.nodes.addJunction(junction) | ||||
|             junction = RED.nodes.addJunction(junction) | ||||
|             addedJunctions.push(junction) | ||||
|             let newLink | ||||
|             if (gid === links[0].source.id+":"+links[0].sourcePort) { | ||||
|   | ||||
| @@ -54,6 +54,7 @@ RED.view = (function() { | ||||
|     var spliceTimer; | ||||
|     var groupHoverTimer; | ||||
|  | ||||
|     var activeFlowLocked = false; | ||||
|     var activeSubflow = null; | ||||
|     var activeNodes = []; | ||||
|     var activeLinks = []; | ||||
| @@ -411,8 +412,19 @@ RED.view = (function() { | ||||
|  | ||||
|             activeSubflow = RED.nodes.subflow(event.workspace); | ||||
|  | ||||
|             RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); | ||||
|             RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); | ||||
|             if (activeSubflow) { | ||||
|                 activeFlowLocked = activeSubflow.locked | ||||
|             } else { | ||||
|                 var activeWorkspace = RED.nodes.workspace(event.workspace) | ||||
|                 if (activeWorkspace) { | ||||
|                     activeFlowLocked = activeWorkspace.locked | ||||
|                 } else { | ||||
|                     activeFlowLocked = true | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             RED.menu.setDisabled("menu-item-workspace-edit", activeFlowLocked || activeSubflow || event.workspace === 0); | ||||
|             RED.menu.setDisabled("menu-item-workspace-delete",activeFlowLocked || event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); | ||||
|  | ||||
|             if (workspaceScrollPositions[event.workspace]) { | ||||
|                 chart.scrollLeft(workspaceScrollPositions[event.workspace].left); | ||||
| @@ -439,6 +451,15 @@ RED.view = (function() { | ||||
|             redraw(); | ||||
|         }); | ||||
|  | ||||
|         RED.events.on("flows:change", function(workspace) { | ||||
|             if (workspace.id === RED.workspaces.active()) { | ||||
|                 activeFlowLocked = !!workspace.locked | ||||
|                 $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); | ||||
|                 $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked); | ||||
|  | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         RED.statusBar.add({ | ||||
|             id: "view-zoom-controls", | ||||
|             align: "right", | ||||
| @@ -496,6 +517,9 @@ RED.view = (function() { | ||||
|         chart.droppable({ | ||||
|             accept:".red-ui-palette-node", | ||||
|             drop: function( event, ui ) { | ||||
|                 if (activeFlowLocked) { | ||||
|                     return | ||||
|                 } | ||||
|                 d3.event = event; | ||||
|                 var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); | ||||
|                 var result = createNode(selected_tool); | ||||
| @@ -503,9 +527,7 @@ RED.view = (function() { | ||||
|                     return; | ||||
|                 } | ||||
|                 var historyEvent = result.historyEvent; | ||||
|                 var nn = result.node; | ||||
|  | ||||
|                 RED.nodes.add(nn); | ||||
|                 var nn = RED.nodes.add(result.node); | ||||
|  | ||||
|                 var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); | ||||
|                 if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { | ||||
| @@ -632,6 +654,9 @@ RED.view = (function() { | ||||
|         RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); | ||||
|         RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); | ||||
|         RED.actions.add("core:paste-from-internal-clipboard",function(){ | ||||
|             if (RED.workspaces.isActiveLocked()) { | ||||
|                 return | ||||
|             } | ||||
|             importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'}); | ||||
|         }); | ||||
|  | ||||
| @@ -640,22 +665,27 @@ RED.view = (function() { | ||||
|         RED.events.on("view:selection-changed", function(selection) { | ||||
|             var hasSelection = (selection.nodes && selection.nodes.length > 0); | ||||
|             var hasMultipleSelection = hasSelection && selection.nodes.length > 1; | ||||
|             RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection); | ||||
|             var hasLinkSelected = selection.links && selection.links.length > 0; | ||||
|             var canEdit = !activeFlowLocked && hasSelection | ||||
|             var canEditMultiple = !activeFlowLocked && hasMultipleSelection | ||||
|             RED.menu.setDisabled("menu-item-edit-cut", !canEdit); | ||||
|             RED.menu.setDisabled("menu-item-edit-copy", !hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-edit-select-connected", !hasSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-to-back", !canEdit); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-to-front", !canEdit); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-backwards", !canEdit); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-move-forwards", !canEdit); | ||||
|  | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-left", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-center", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-right", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-top", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-middle", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-align-bottom", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally", !canEditMultiple); | ||||
|             RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally", !canEditMultiple); | ||||
|  | ||||
|             RED.menu.setDisabled("menu-item-edit-split-wire-with-links", activeFlowLocked || !hasLinkSelected); | ||||
|         }) | ||||
|  | ||||
|         RED.actions.add("core:delete-selection",deleteSelection); | ||||
| @@ -1045,7 +1075,7 @@ RED.view = (function() { | ||||
|                             .attr("class", "nr-ui-view-lasso"); | ||||
|                         d3.event.preventDefault(); | ||||
|                     } | ||||
|                 } else if (d3.event.altKey) { | ||||
|                 } else if (d3.event.altKey && !activeFlowLocked) { | ||||
|                     //Alt [+shift] held - Begin slicing | ||||
|                     clearSelection(); | ||||
|                     mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; | ||||
| @@ -1059,6 +1089,9 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function showQuickAddDialog(options) { | ||||
|         if (activeFlowLocked) { | ||||
|             return | ||||
|         } | ||||
|         options = options || {}; | ||||
|         var point = options.position || lastClickPosition; | ||||
|         var spliceLink = options.splice; | ||||
| @@ -1238,6 +1271,11 @@ RED.view = (function() { | ||||
|                 if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { | ||||
|                     nn.l = showLabel; | ||||
|                 } | ||||
|                 if (nn.type === 'junction') { | ||||
|                     nn = RED.nodes.addJunction(nn); | ||||
|                 } else { | ||||
|                     nn = RED.nodes.add(nn); | ||||
|                 } | ||||
|                 if (quickAddLink) { | ||||
|                     var drag_line = quickAddLink; | ||||
|                     var src = null,dst,src_port; | ||||
| @@ -1340,11 +1378,7 @@ RED.view = (function() { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 if (nn.type === 'junction') { | ||||
|                     RED.nodes.addJunction(nn); | ||||
|                 } else { | ||||
|                     RED.nodes.add(nn); | ||||
|                 } | ||||
|  | ||||
|                 RED.editor.validateNode(nn); | ||||
|  | ||||
|                 if (targetGroup) { | ||||
| @@ -1602,16 +1636,18 @@ RED.view = (function() { | ||||
|             } | ||||
|             var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); | ||||
|             if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { | ||||
|                 mouse_mode = RED.state.MOVING_ACTIVE; | ||||
|                 clickElapsed = 0; | ||||
|                 spliceActive = false; | ||||
|                 if (movingSet.length() === 1) { | ||||
|                     node = movingSet.get(0); | ||||
|                     spliceActive = node.n.hasOwnProperty("_def") && | ||||
|                                    ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && | ||||
|                                    ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && | ||||
|                                    RED.nodes.filterLinks({ source: node.n }).length === 0 && | ||||
|                                    RED.nodes.filterLinks({ target: node.n }).length === 0; | ||||
|                 if (!activeFlowLocked) { | ||||
|                     mouse_mode = RED.state.MOVING_ACTIVE; | ||||
|                     spliceActive = false; | ||||
|                     if (movingSet.length() === 1) { | ||||
|                         node = movingSet.get(0); | ||||
|                         spliceActive = node.n.hasOwnProperty("_def") && | ||||
|                                        ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && | ||||
|                                        ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && | ||||
|                                        RED.nodes.filterLinks({ source: node.n }).length === 0 && | ||||
|                                        RED.nodes.filterLinks({ target: node.n }).length === 0; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { | ||||
| @@ -2456,6 +2492,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function editSelection() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (movingSet.length() > 0) { | ||||
|             var node = movingSet.get(0).n; | ||||
|             if (node.type === "subflow") { | ||||
| @@ -2471,6 +2508,9 @@ RED.view = (function() { | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             return; | ||||
|         } | ||||
|         if (activeFlowLocked) { | ||||
|             return | ||||
|         } | ||||
|         if (portLabelHover) { | ||||
|             portLabelHover.remove(); | ||||
|             portLabelHover = null; | ||||
| @@ -2786,6 +2826,7 @@ RED.view = (function() { | ||||
|  | ||||
|  | ||||
|     function detachSelectedNodes() { | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); | ||||
| @@ -2927,7 +2968,7 @@ RED.view = (function() { | ||||
|         mousedown_node = d; | ||||
|         mousedown_port_type = portType; | ||||
|         mousedown_port_index = portIndex || 0; | ||||
|         if (mouse_mode !== RED.state.QUICK_JOINING) { | ||||
|         if (mouse_mode !== RED.state.QUICK_JOINING && !activeFlowLocked) { | ||||
|             mouse_mode = RED.state.JOINING; | ||||
|             document.body.style.cursor = "crosshair"; | ||||
|             if (evt.ctrlKey || evt.metaKey) { | ||||
| @@ -3367,6 +3408,11 @@ RED.view = (function() { | ||||
|         } | ||||
|         if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { | ||||
|             mouse_mode = RED.state.DEFAULT; | ||||
|             if (RED.workspaces.isActiveLocked()) { | ||||
|                 clickElapsed = 0; | ||||
|                 d3.event.stopPropagation(); | ||||
|                 return | ||||
|             } | ||||
|             if (d.type != "subflow") { | ||||
|                 if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { | ||||
|                     RED.workspaces.show(d.type.substring(8)); | ||||
| @@ -3690,7 +3736,6 @@ RED.view = (function() { | ||||
|             } | ||||
|             // selectedLinks.clear(); | ||||
|             if (d3.event.button != 2) { | ||||
|                 mouse_mode = RED.state.MOVING; | ||||
|                 var mouse = d3.touches(this)[0]||d3.mouse(this); | ||||
|                 mouse[0] += d.x-d.w/2; | ||||
|                 mouse[1] += d.y-d.h/2; | ||||
| @@ -3883,6 +3928,7 @@ RED.view = (function() { | ||||
|         if (RED.view.DEBUG) { | ||||
|             console.warn("groupMouseUp", { mouse_mode, event: d3.event }); | ||||
|         } | ||||
|         if (RED.workspaces.isActiveLocked()) { return } | ||||
|         if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { | ||||
|             mouse_mode = RED.state.DEFAULT; | ||||
|             RED.editor.editGroup(g); | ||||
| @@ -4053,7 +4099,7 @@ RED.view = (function() { | ||||
|     function isButtonEnabled(d) { | ||||
|         var buttonEnabled = true; | ||||
|         var ws = RED.nodes.workspace(RED.workspaces.active()); | ||||
|         if (ws && !ws.disabled && !d.d) { | ||||
|         if (ws && !ws.disabled && !d.d && !ws.locked) { | ||||
|             if (d._def.button.hasOwnProperty('enabled')) { | ||||
|                 if (typeof d._def.button.enabled === "function") { | ||||
|                     buttonEnabled = d._def.button.enabled.call(d); | ||||
| @@ -4076,7 +4122,7 @@ RED.view = (function() { | ||||
|         } | ||||
|         var activeWorkspace = RED.workspaces.active(); | ||||
|         var ws = RED.nodes.workspace(activeWorkspace); | ||||
|         if (ws && !ws.disabled && !d.d) { | ||||
|         if (ws && !ws.disabled && !d.d && !ws.locked) { | ||||
|             if (d._def.button.toggle) { | ||||
|                 d[d._def.button.toggle] = !d[d._def.button.toggle]; | ||||
|                 d.dirty = true; | ||||
| @@ -4091,7 +4137,7 @@ RED.view = (function() { | ||||
|             if (d.dirty) { | ||||
|                 redraw(); | ||||
|             } | ||||
|         } else { | ||||
|         } else if (!ws || !ws.locked){ | ||||
|             if (activeSubflow) { | ||||
|                 RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); | ||||
|             } else { | ||||
| @@ -4106,14 +4152,15 @@ RED.view = (function() { | ||||
|     function showTouchMenu(obj,pos) { | ||||
|         var mdn = mousedown_node; | ||||
|         var options = []; | ||||
|         options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); | ||||
|         options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); | ||||
|         options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); | ||||
|         options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); | ||||
|         options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); | ||||
|         const isActiveLocked = RED.workspaces.isActiveLocked() | ||||
|         options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); | ||||
|         options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); | ||||
|         options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}}); | ||||
|         options.push({name:"paste",disabled:(isActiveLocked || clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); | ||||
|         options.push({name:"edit",disabled:(isActiveLocked || movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); | ||||
|         options.push({name:"select",onselect:function() {selectAll();}}); | ||||
|         options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); | ||||
|         options.push({name:"add",onselect:function() { | ||||
|         options.push({name:"add",disabled:isActiveLocked, onselect:function() { | ||||
|             chartPos = chart.offset(); | ||||
|             showQuickAddDialog({ | ||||
|                 position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], | ||||
| @@ -5811,6 +5858,9 @@ RED.view = (function() { | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             return; | ||||
|         } | ||||
|         if (activeFlowLocked) { | ||||
|             return | ||||
|         } | ||||
|         var workspaceSelection = RED.workspaces.selection(); | ||||
|         var changed = false; | ||||
|         if (workspaceSelection.length > 0) { | ||||
|   | ||||
| @@ -58,6 +58,9 @@ RED.workspaces = (function() { | ||||
|             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")||"{}"); | ||||
| @@ -75,6 +78,7 @@ RED.workspaces = (function() { | ||||
|                 type: "tab", | ||||
|                 id: tabId, | ||||
|                 disabled: false, | ||||
|                 locked: false, | ||||
|                 info: "", | ||||
|                 label: RED._('workspace.defaultName',{number:workspaceIndex}), | ||||
|                 env: [], | ||||
| @@ -99,6 +103,9 @@ RED.workspaces = (function() { | ||||
|         if (workspaceTabCount === 1) { | ||||
|             return; | ||||
|         } | ||||
|         if (ws.locked) { | ||||
|             return | ||||
|         } | ||||
|         var workspaceOrder = RED.nodes.getWorkspaceOrder(); | ||||
|         ws._index = workspaceOrder.indexOf(ws.id); | ||||
|         removeWorkspace(ws); | ||||
| @@ -119,7 +126,9 @@ RED.workspaces = (function() { | ||||
|                 RED.editor.editSubflow(subflow); | ||||
|             } | ||||
|         } else { | ||||
|             RED.editor.editFlow(workspace); | ||||
|             if (!workspace.locked) { | ||||
|                 RED.editor.editFlow(workspace); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -144,6 +153,11 @@ RED.workspaces = (function() { | ||||
|         let activeWorkspace = tab || RED.nodes.workspace(RED.workspaces.active()) || RED.nodes.subflow(RED.workspaces.active()) | ||||
|         let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false | ||||
|  | ||||
|         let isCurrentLocked = RED.workspaces.isActiveLocked() | ||||
|         if (tab) { | ||||
|             isCurrentLocked = tab.locked | ||||
|         } | ||||
|  | ||||
|         var menuItems = [] | ||||
|         if (isMenuButton) { | ||||
|             menuItems.push({ | ||||
| @@ -184,14 +198,30 @@ RED.workspaces = (function() { | ||||
|                         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 currentTabs = workspace_tabs.listTabs() | ||||
| @@ -235,6 +265,7 @@ RED.workspaces = (function() { | ||||
|                     } | ||||
|                 } | ||||
|             ) | ||||
|  | ||||
|         } | ||||
|         menuItems.push( | ||||
|             { | ||||
| @@ -260,6 +291,7 @@ RED.workspaces = (function() { | ||||
|                 null, | ||||
|                 { | ||||
|                     label: RED._("common.label.delete"), | ||||
|                     disabled: isCurrentLocked, | ||||
|                     onselect: function() { | ||||
|                         if (tab.type === 'tab') { | ||||
|                             RED.workspaces.delete(tab) | ||||
| @@ -297,7 +329,8 @@ RED.workspaces = (function() { | ||||
|                     $("#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-disabled", !!tab.disabled); | ||||
|                     $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked); | ||||
|                 } else { | ||||
|                     $("#red-ui-workspace-chart").hide(); | ||||
|                     activeWorkspace = 0; | ||||
| @@ -329,6 +362,12 @@ RED.workspaces = (function() { | ||||
|                 if (tab.disabled) { | ||||
|                     $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled'); | ||||
|                 } | ||||
|                 $('<span class="red-ui-workspace-locked-icon"><i class="fa fa-lock"></i> </span>').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'); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); | ||||
|                 if (workspaceTabCount === 1) { | ||||
|                     showWorkspace(); | ||||
| @@ -465,6 +504,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:lock-flow",lockWorkspace); | ||||
|         RED.actions.add("core:unlock-flow",unlockWorkspace); | ||||
|         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') }); | ||||
|  | ||||
| @@ -603,7 +644,7 @@ RED.workspaces = (function() { | ||||
|     } | ||||
|     function setWorkspaceState(id,disabled) { | ||||
|         var workspace = RED.nodes.workspace(id||activeWorkspace); | ||||
|         if (!workspace) { | ||||
|         if (!workspace || workspace.locked) { | ||||
|             return; | ||||
|         } | ||||
|         if (workspace.disabled !== disabled) { | ||||
| @@ -638,11 +679,58 @@ RED.workspaces = (function() { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     function lockWorkspace(id) { | ||||
|         setWorkspaceLockState(id,true); | ||||
|     } | ||||
|     function unlockWorkspace(id) { | ||||
|         setWorkspaceLockState(id,false); | ||||
|     } | ||||
|     function setWorkspaceLockState(id,locked) { | ||||
|         var workspace = RED.nodes.workspace(id||activeWorkspace); | ||||
|         if (!workspace) { | ||||
|             return; | ||||
|         } | ||||
|         if (workspace.locked !== locked) { | ||||
|             var changes = { locked: workspace.locked }; | ||||
|             workspace.locked = locked; | ||||
|             $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked); | ||||
|             if (!id || (id === activeWorkspace)) { | ||||
|                 $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked); | ||||
|             } | ||||
|             var historyEvent = { | ||||
|                 t: "edit", | ||||
|                 changes:changes, | ||||
|                 node: workspace, | ||||
|                 dirty: RED.nodes.dirty() | ||||
|             } | ||||
|             workspace.changed = true; | ||||
|             RED.history.push(historyEvent); | ||||
|             RED.events.emit("flows:change",workspace); | ||||
|             RED.nodes.dirty(true); | ||||
|             // RED.sidebar.config.refresh(); | ||||
|             // var selection = RED.view.selection(); | ||||
|             // if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { | ||||
|             //     RED.sidebar.info.refresh(workspace); | ||||
|             // } | ||||
|             // if (changes.hasOwnProperty('disabled')) { | ||||
|             //     RED.nodes.eachNode(function(n) { | ||||
|             //         if (n.z === workspace.id) { | ||||
|             //             n.dirty = true; | ||||
|             //         } | ||||
|             //     }); | ||||
|             //     RED.view.redraw(); | ||||
|             // } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function removeWorkspace(ws) { | ||||
|         if (!ws) { | ||||
|             deleteWorkspace(RED.nodes.workspace(activeWorkspace)); | ||||
|             ws = RED.nodes.workspace(activeWorkspace) | ||||
|             if (ws && !ws.locked) { | ||||
|                 deleteWorkspace(RED.nodes.workspace(activeWorkspace)); | ||||
|             } | ||||
|         } else { | ||||
|             if (ws.locked) { return } | ||||
|             if (workspace_tabs.contains(ws.id)) { | ||||
|                 workspace_tabs.removeTab(ws.id); | ||||
|             } | ||||
| @@ -737,6 +825,10 @@ RED.workspaces = (function() { | ||||
|         active: function() { | ||||
|             return activeWorkspace | ||||
|         }, | ||||
|         isActiveLocked: function() { | ||||
|             var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace) | ||||
|             return ws && ws.locked | ||||
|         }, | ||||
|         selection: function() { | ||||
|             return workspace_tabs.selection(); | ||||
|         }, | ||||
| @@ -793,6 +885,8 @@ RED.workspaces = (function() { | ||||
|             workspace_tabs.resize(); | ||||
|         }, | ||||
|         enable: enableWorkspace, | ||||
|         disable: disableWorkspace | ||||
|         disable: disableWorkspace, | ||||
|         lock: lockWorkspace, | ||||
|         unlock: unlockWorkspace | ||||
|     } | ||||
| })(); | ||||
|   | ||||
| @@ -68,6 +68,9 @@ | ||||
|     stroke: var(--red-ui-node-border); | ||||
|     cursor: move; | ||||
|     stroke-width: 1; | ||||
|     .red-ui-workspace-locked & { | ||||
|         cursor: pointer; | ||||
|     } | ||||
| } | ||||
| .red-ui-workspace-select-mode { | ||||
|     g.red-ui-flow-node.red-ui-flow-node-hovered * { | ||||
| @@ -287,9 +290,11 @@ g.red-ui-flow-node-selected { | ||||
|     text-anchor:start; | ||||
| } | ||||
|  | ||||
| .red-ui-flow-port-hovered { | ||||
|     stroke: var(--red-ui-port-selected-color); | ||||
|     fill:  var(--red-ui-port-selected-color); | ||||
| #red-ui-workspace:not(.red-ui-workspace-locked) { | ||||
|     .red-ui-flow-port-hovered { | ||||
|         stroke: var(--red-ui-port-selected-color); | ||||
|         fill:  var(--red-ui-port-selected-color); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .red-ui-flow-subflow-port { | ||||
|   | ||||
| @@ -467,6 +467,9 @@ div.red-ui-info-table { | ||||
|         .fa-eye { | ||||
|             display: none; | ||||
|         } | ||||
|         .fa-unlock-alt { | ||||
|             display: none; | ||||
|         } | ||||
|     } | ||||
|     .red-ui-info-outline-item-control-reveal, | ||||
|     .red-ui-info-outline-item-control-action { | ||||
| @@ -500,6 +503,25 @@ div.red-ui-info-table { | ||||
|             display: none; | ||||
|         } | ||||
|     } | ||||
|     .fa-lock { | ||||
|         display: none; | ||||
|     } | ||||
|     .red-ui-info-outline-item.red-ui-info-outline-item-locked & { | ||||
|         .fa-lock { | ||||
|             display: inline-block; | ||||
|         } | ||||
|         .fa-unlock-alt { | ||||
|             display: none; | ||||
|         } | ||||
|     } | ||||
|     // If the parent is locked, do not show the display/action buttons when | ||||
|     // hovering in the outline | ||||
|     .red-ui-info-outline-item-locked .red-ui-info-outline-item & { | ||||
|         .red-ui-info-outline-item-control-disable, | ||||
|         .red-ui-info-outline-item-control-action { | ||||
|             display: none; | ||||
|         } | ||||
|     } | ||||
|     button { | ||||
|         margin-right: 3px | ||||
|     } | ||||
| @@ -517,8 +539,6 @@ div.red-ui-info-table { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| .red-ui-icons { | ||||
|     display: inline-block; | ||||
|     width: 18px; | ||||
|   | ||||
| @@ -106,6 +106,28 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| .red-ui-workspace-locked-icon { | ||||
|     display: none; | ||||
| } | ||||
| .red-ui-workspace-locked { | ||||
|     &.red-ui-tab { | ||||
|         // border-top-style: dashed; | ||||
|         // border-left-style: dashed; | ||||
|         // border-right-style: dashed; | ||||
|  | ||||
|         // a { | ||||
|         //     font-style: italic; | ||||
|         //     color: var(--red-ui-tab-text-color-disabled-inactive) !important; | ||||
|         // } | ||||
|         // &.active a { | ||||
|         //     font-weight: normal; | ||||
|         //     color: var(--red-ui-tab-text-color-disabled-active) !important; | ||||
|         // } | ||||
|         .red-ui-workspace-locked-icon { | ||||
|             display: inline; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #red-ui-navigator-canvas { | ||||
|     position: absolute; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user