Merge pull request #3938 from node-red/locking-flows

Locking flows
This commit is contained in:
Nick O'Leary 2022-12-03 23:01:58 +00:00 committed by GitHub
commit 04cea003b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 744 additions and 292 deletions

View File

@ -15,5 +15,5 @@
"shadow": true, // allow variable shadowing (re-use of names...) "shadow": true, // allow variable shadowing (re-use of names...)
"sub": true, // don't warn that foo['bar'] should be written as foo.bar "sub": true, // don't warn that foo['bar'] should be written as foo.bar
"proto": true, // allow setting of __proto__ in node < v0.12, "proto": true, // allow setting of __proto__ in node < v0.12,
"esversion": 6 // allow es6 "esversion": 11 // allow es11(ES2020)
} }

View File

@ -71,6 +71,8 @@
"selectNodes": "Click nodes to select", "selectNodes": "Click nodes to select",
"enableFlow": "Enable flow", "enableFlow": "Enable flow",
"disableFlow": "Disable flow", "disableFlow": "Disable flow",
"lockFlow": "Lock flow",
"unlockFlow": "Unlock flow",
"moveToStart": "Move flow to start", "moveToStart": "Move flow to start",
"moveToEnd": "Move flow to end" "moveToEnd": "Move flow to end"
}, },
@ -105,6 +107,7 @@
"displayStatus": "Show node status", "displayStatus": "Show node status",
"displayConfig": "Configuration nodes", "displayConfig": "Configuration nodes",
"import": "Import", "import": "Import",
"importExample": "Import Example Flow",
"export": "Export", "export": "Export",
"search": "Search flows", "search": "Search flows",
"searchInput": "search your flows", "searchInput": "search your flows",
@ -501,6 +504,7 @@
"addRemoveNode": "Add/remove node from selection", "addRemoveNode": "Add/remove node from selection",
"editSelected": "Edit selected node", "editSelected": "Edit selected node",
"deleteSelected": "Delete selected nodes or link", "deleteSelected": "Delete selected nodes or link",
"deleteReconnect": "Delete and Reconnect",
"importNode": "Import nodes", "importNode": "Import nodes",
"exportNode": "Export nodes", "exportNode": "Export nodes",
"nudgeNode": "Move selected nodes (1px)", "nudgeNode": "Move selected nodes (1px)",

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
**/ **/
/** /**
* An API for undo / redo history buffer * An API for undo / redo history buffer
* @namespace RED.history * @namespace RED.history
*/ */
@ -434,7 +434,9 @@ RED.history = (function() {
if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('disabled')) { 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-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) { if (ev.subflow) {
inverseEv.subflow = {}; inverseEv.subflow = {};

View File

@ -19,7 +19,6 @@
* @namespace RED.nodes * @namespace RED.nodes
*/ */
RED.nodes = (function() { RED.nodes = (function() {
var PORT_TYPE_INPUT = 1; var PORT_TYPE_INPUT = 1;
var PORT_TYPE_OUTPUT = 0; var PORT_TYPE_OUTPUT = 0;
@ -63,6 +62,7 @@ RED.nodes = (function() {
defaults: { defaults: {
label: {value:""}, label: {value:""},
disabled: {value: false}, disabled: {value: false},
locked: {value: false},
info: {value: ""}, info: {value: ""},
env: {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) { function addNode(n) {
let newNode
if (!n.__isProxy__) {
newNode = new Proxy(n, nodeProxyHandler)
} else {
newNode = n
}
if (n.type.indexOf("subflow") !== 0) { if (n.type.indexOf("subflow") !== 0) {
n["_"] = n._def._; n["_"] = n._def._;
} else { } else {
var subflowId = n.type.substring(8); var subflowId = n.type.substring(8);
var sf = RED.nodes.subflow(subflowId); var sf = RED.nodes.subflow(subflowId);
if (sf) { if (sf) {
sf.instances.push(sf); sf.instances.push(newNode);
} }
n["_"] = RED._; n["_"] = RED._;
} }
@ -600,12 +633,13 @@ RED.nodes = (function() {
}); });
n.i = nextId+1; n.i = nextId+1;
} }
allNodes.addNode(n); allNodes.addNode(newNode);
if (!nodeLinks[n.id]) { if (!nodeLinks[n.id]) {
nodeLinks[n.id] = {in:[],out:[]}; nodeLinks[n.id] = {in:[],out:[]};
} }
} }
RED.events.emit('nodes:add',n); RED.events.emit('nodes:add',newNode);
return newNode
} }
function addLink(l) { function addLink(l) {
if (nodeLinks[l.source.id]) { if (nodeLinks[l.source.id]) {
@ -1052,6 +1086,9 @@ RED.nodes = (function() {
node.type = n.type; node.type = n.type;
for (var d in n._def.defaults) { for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) { if (n._def.defaults.hasOwnProperty(d)) {
if (d === 'locked' && !n.locked) {
continue
}
node[d] = n[d]; node[d] = n[d];
} }
} }
@ -1331,7 +1368,6 @@ RED.nodes = (function() {
} else { } else {
nodeSet = [sf]; nodeSet = [sf];
} }
console.log(nodeSet);
return createExportableNodeSet(nodeSet); return createExportableNodeSet(nodeSet);
} }
/** /**
@ -2318,19 +2354,6 @@ RED.nodes = (function() {
if (n.g && !new_group_set.has(n.g)) { if (n.g && !new_group_set.has(n.g)) {
delete 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) { if (!n.g) {
groupDepthMap[n.id] = 0; groupDepthMap[n.id] = 0;
} }
@ -2353,21 +2376,22 @@ RED.nodes = (function() {
return groupDepthMap[A.id] - groupDepthMap[B.id]; return groupDepthMap[A.id] - groupDepthMap[B.id];
}); });
for (i=0;i<new_groups.length;i++) { for (i=0;i<new_groups.length;i++) {
n = new_groups[i]; new_groups[i] = addGroup(new_groups[i]);
addGroup(n); node_map[new_groups[i].id] = new_groups[i]
} }
for (i=0;i<new_junctions.length;i++) { for (i=0;i<new_junctions.length;i++) {
var junction = new_junctions[i]; new_junctions[i] = addJunction(new_junctions[i]);
addJunction(junction); node_map[new_junctions[i].id] = new_junctions[i]
} }
// Now the nodes have been fully updated, add them. // Now the nodes have been fully updated, add them.
for (i=0;i<new_nodes.length;i++) { for (i=0;i<new_nodes.length;i++) {
var node = new_nodes[i]; new_nodes[i] = addNode(new_nodes[i])
addNode(node); node_map[new_nodes[i].id] = new_nodes[i]
} }
// Finally validate them all. // Finally validate them all.
// This has to be done after everything is added so that any checks for // This has to be done after everything is added so that any checks for
// dependent config nodes will pass // dependent config nodes will pass
@ -2375,6 +2399,39 @@ RED.nodes = (function() {
var node = new_nodes[i]; var node = new_nodes[i];
RED.editor.validateNode(node); 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(); RED.workspaces.refresh();
@ -2503,11 +2560,17 @@ RED.nodes = (function() {
junctions = {}; junctions = {};
junctionsByZ = {}; 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); var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) { subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id) RED.subflow.removeSubflow(id)
}); });
var workspaceIds = Object.keys(workspaces);
workspaceIds.forEach(function(id) { workspaceIds.forEach(function(id) {
RED.workspaces.remove(workspaces[id]); RED.workspaces.remove(workspaces[id]);
}); });
@ -2528,10 +2591,14 @@ RED.nodes = (function() {
} }
function addGroup(group) { function addGroup(group) {
if (!group.__isProxy__) {
group = new Proxy(group, nodeProxyHandler)
}
groupsByZ[group.z] = groupsByZ[group.z] || []; groupsByZ[group.z] = groupsByZ[group.z] || [];
groupsByZ[group.z].push(group); groupsByZ[group.z].push(group);
groups[group.id] = group; groups[group.id] = group;
RED.events.emit("groups:add",group); RED.events.emit("groups:add",group);
return group
} }
function removeGroup(group) { function removeGroup(group) {
var i = groupsByZ[group.z].indexOf(group); var i = groupsByZ[group.z].indexOf(group);
@ -2552,6 +2619,9 @@ RED.nodes = (function() {
} }
function addJunction(junction) { function addJunction(junction) {
if (!junction.__isProxy__) {
junction = new Proxy(junction, nodeProxyHandler)
}
junctionsByZ[junction.z] = junctionsByZ[junction.z] || [] junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
junctionsByZ[junction.z].push(junction) junctionsByZ[junction.z].push(junction)
junctions[junction.id] = junction; junctions[junction.id] = junction;
@ -2559,6 +2629,7 @@ RED.nodes = (function() {
nodeLinks[junction.id] = {in:[],out:[]}; nodeLinks[junction.id] = {in:[],out:[]};
} }
RED.events.emit("junctions:add", junction) RED.events.emit("junctions:add", junction)
return junction
} }
function removeJunction(junction) { function removeJunction(junction) {
var i = junctionsByZ[junction.z].indexOf(junction) var i = junctionsByZ[junction.z].indexOf(junction)

View File

@ -668,11 +668,6 @@ var RED = (function() {
]}); ]});
menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [ 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-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-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"}, {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"}, {id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"},
null, 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-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); menuOptions.push(null);

View File

@ -503,7 +503,7 @@ RED.clipboard = (function() {
$("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport); $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
$("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)}); $("#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-current").addClass('disabled').removeClass("selected");
$("#red-ui-clipboard-dialog-import-opt-new").addClass("selected"); $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
} else { } else {
@ -1271,15 +1271,17 @@ RED.clipboard = (function() {
RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget); RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
$('#red-ui-workspace-chart').on("dragenter",function(event) { $('#red-ui-workspace-chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || if (!RED.workspaces.isActiveLocked() && (
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { $.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").css({display:'table'}).focus();
} }
}); });
$('#red-ui-drop-target').on("dragover",function(event) { $('#red-ui-drop-target').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || 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(); event.preventDefault();
} }
}) })
@ -1287,27 +1289,29 @@ RED.clipboard = (function() {
hideDropTarget(); hideDropTarget();
}) })
.on("drop",function(event) { .on("drop",function(event) {
try { if (!RED.workspaces.isActiveLocked()) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { try {
var data = event.originalEvent.dataTransfer.getData("text/plain"); if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); var data = event.originalEvent.dataTransfer.getData("text/plain");
importNodes(data); data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
} else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { importNodes(data);
var files = event.originalEvent.dataTransfer.files; } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
if (files.length === 1) { var files = event.originalEvent.dataTransfer.files;
var file = files[0]; if (files.length === 1) {
var reader = new FileReader(); var file = files[0];
reader.onload = (function(theFile) { var reader = new FileReader();
return function(e) { reader.onload = (function(theFile) {
importNodes(e.target.result); return function(e) {
}; importNodes(e.target.result);
})(file); };
reader.readAsText(file); })(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(); hideDropTarget();
event.preventDefault(); event.preventDefault();

View File

@ -28,8 +28,10 @@ RED.contextMenu = (function () {
const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1 const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
const canDelete = hasSelection || hasLinks const canDelete = hasSelection || hasLinks
const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' 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 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() const offset = $("#red-ui-workspace-chart").offset()
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
@ -42,89 +44,133 @@ RED.contextMenu = (function () {
} }
menuItems.push( menuItems.push(
{ onselect: 'core:show-action-list', onpostselect: function () { } }, { 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
}
]
}
) )
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( menuItems.push(
null, null,
{ onselect: 'core:undo', disabled: RED.history.list().length === 0 }, { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
{ onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
null, 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: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:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
{ onselect: 'core:delete-selection', disabled: !canDelete }, { 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: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"; var direction = "right";

View File

@ -558,6 +558,11 @@ RED.deploy = (function() {
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success"); RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
} }
RED.nodes.eachNode(function (node) { 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) { if (node.changed) {
node.dirty = true; node.dirty = true;
node.changed = false; node.changed = false;
@ -569,6 +574,9 @@ RED.deploy = (function() {
if (node.credentials) { if (node.credentials) {
delete node.credentials; delete node.credentials;
} }
if (flow && isLocked) {
flow.locked = isLocked;
}
}); });
RED.nodes.eachConfig(function (confNode) { RED.nodes.eachConfig(function (confNode) {
confNode.changed = false; confNode.changed = false;

View File

@ -1847,11 +1847,15 @@ RED.editor = (function() {
workspace.disabled = disabled; workspace.disabled = disabled;
$("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.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) { if (editState.changed) {
var historyEvent = { var historyEvent = {
t: "edit", t: "edit",
@ -1892,6 +1896,7 @@ RED.editor = (function() {
var trayBody = tray.find('.red-ui-tray-body'); var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden'); trayBody.parent().css('overflow','hidden');
var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter) 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 = [ var nodeEditPanes = [
'editor-tab-flow-properties', 'editor-tab-flow-properties',
@ -1906,6 +1911,18 @@ RED.editor = (function() {
disabledIcon: "fa-ban", disabledIcon: "fa-ban",
invertState: true 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) { prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", defaultTab, function(_activeEditPanes) {
activeEditPanes = _activeEditPanes; activeEditPanes = _activeEditPanes;
trayBody.i18n(); trayBody.i18n();

View File

@ -52,8 +52,6 @@
node.info = info; node.info = info;
} }
$("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled); $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
} }
} }
}); });

View File

@ -185,6 +185,8 @@ RED.group = (function() {
var activateMerge = false; var activateMerge = false;
var activateRemove = false; var activateRemove = false;
var singleGroupSelected = false; var singleGroupSelected = false;
var locked = RED.workspaces.isActiveLocked()
if (activateGroup) { if (activateGroup) {
singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group'; singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
selection.nodes.forEach(function (n) { selection.nodes.forEach(function (n) {
@ -199,12 +201,12 @@ RED.group = (function() {
activateMerge = (selection.nodes.length > 1); activateMerge = (selection.nodes.length > 1);
} }
} }
RED.menu.setDisabled("menu-item-group-group", !activateGroup); RED.menu.setDisabled("menu-item-group-group", locked || !activateGroup);
RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup); RED.menu.setDisabled("menu-item-group-ungroup", locked || !activateUngroup);
RED.menu.setDisabled("menu-item-group-merge", !activateMerge); RED.menu.setDisabled("menu-item-group-merge", locked || !activateMerge);
RED.menu.setDisabled("menu-item-group-remove", !activateRemove); 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-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() }) RED.actions.add("core:group-selection", function() { groupSelection() })
@ -261,6 +263,7 @@ RED.group = (function() {
} }
} }
function pasteGroupStyle() { function pasteGroupStyle() {
if (RED.workspaces.isActiveLocked()) { return }
if (RED.view.state() !== RED.state.DEFAULT) { return } if (RED.view.state() !== RED.state.DEFAULT) { return }
if (groupStyleClipboard) { if (groupStyleClipboard) {
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -295,6 +298,7 @@ RED.group = (function() {
} }
function groupSelection() { function groupSelection() {
if (RED.workspaces.isActiveLocked()) { return }
if (RED.view.state() !== RED.state.DEFAULT) { return } if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
@ -313,6 +317,7 @@ RED.group = (function() {
} }
} }
function ungroupSelection() { function ungroupSelection() {
if (RED.workspaces.isActiveLocked()) { return }
if (RED.view.state() !== RED.state.DEFAULT) { return } if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
@ -336,6 +341,7 @@ RED.group = (function() {
} }
function ungroup(g) { function ungroup(g) {
if (RED.workspaces.isActiveLocked()) { return }
var nodes = []; var nodes = [];
var parentGroup = RED.nodes.group(g.g); var parentGroup = RED.nodes.group(g.g);
g.nodes.forEach(function(n) { g.nodes.forEach(function(n) {
@ -362,6 +368,7 @@ RED.group = (function() {
} }
function mergeSelection() { function mergeSelection() {
if (RED.workspaces.isActiveLocked()) { return }
if (RED.view.state() !== RED.state.DEFAULT) { return } if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
@ -431,6 +438,7 @@ RED.group = (function() {
} }
function removeSelection() { function removeSelection() {
if (RED.workspaces.isActiveLocked()) { return }
if (RED.view.state() !== RED.state.DEFAULT) { return } if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
@ -458,6 +466,7 @@ RED.group = (function() {
} }
} }
function createGroup(nodes) { function createGroup(nodes) {
if (RED.workspaces.isActiveLocked()) { return }
if (nodes.length === 0) { if (nodes.length === 0) {
return; return;
} }
@ -480,7 +489,7 @@ RED.group = (function() {
} }
group.z = nodes[0].z; group.z = nodes[0].z;
RED.nodes.addGroup(group); group = RED.nodes.addGroup(group);
try { try {
addToGroup(group,nodes); addToGroup(group,nodes);
@ -563,6 +572,7 @@ RED.group = (function() {
markDirty(group); markDirty(group);
} }
function removeFromGroup(group, nodes, reparent) { function removeFromGroup(group, nodes, reparent) {
if (RED.workspaces.isActiveLocked()) { return }
if (!Array.isArray(nodes)) { if (!Array.isArray(nodes)) {
nodes = [nodes]; nodes = [nodes];
} }

View File

@ -282,6 +282,7 @@ RED.palette = (function() {
var hoverGroup; var hoverGroup;
var paletteWidth; var paletteWidth;
var paletteTop; var paletteTop;
var dropEnabled;
$(d).draggable({ $(d).draggable({
helper: 'clone', helper: 'clone',
appendTo: '#red-ui-editor', appendTo: '#red-ui-editor',
@ -289,6 +290,7 @@ RED.palette = (function() {
revertDuration: 200, revertDuration: 200,
containment:'#red-ui-main-container', containment:'#red-ui-main-container',
start: function() { start: function() {
dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
paletteWidth = $("#red-ui-palette").width(); paletteWidth = $("#red-ui-palette").width();
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
hoverGroup = null; hoverGroup = null;
@ -299,96 +301,100 @@ RED.palette = (function() {
RED.view.focus(); RED.view.focus();
}, },
stop: function() { stop: function() {
d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (dropEnabled) {
if (hoverGroup) { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); 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) { drag: function(e,ui) {
var paletteNode = getPaletteNode(nt); var paletteNode = getPaletteNode(nt);
ui.originalPosition.left = paletteNode.offset().left; ui.originalPosition.left = paletteNode.offset().left;
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); if (dropEnabled) {
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10; mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
if (!groupTimer) { mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
groupTimer = setTimeout(function() { if (!groupTimer) {
var mx = mouseX / RED.view.scale(); groupTimer = setTimeout(function() {
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);
}
var mx = mouseX / RED.view.scale(); var mx = mouseX / RED.view.scale();
var my = mouseY / RED.view.scale(); var my = mouseY / RED.view.scale();
for (var i=0;i<nodes.length;i++) { var group = RED.view.getGroupAtPoint(mx,my);
var node = d3.select(nodes[i]); if (group !== hoverGroup) {
if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) { if (hoverGroup) {
var length = nodes[i].getTotalLength(); document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
for (var j=0;j<length;j+=10) { }
var p = nodes[i].getPointAtLength(j); if (group) {
var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my)); document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
if (d2 < 200 && d2 < bestDistance) { }
bestDistance = d2; hoverGroup = group;
bestLink = nodes[i]; 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) {
if (activeSpliceLink && activeSpliceLink !== bestLink) { d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
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 (bestLink) {
activeSpliceLink = bestLink; d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
spliceTimer = null; } else {
},200); 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);
}
} }
} }
} }

View File

@ -273,6 +273,11 @@ RED.subflow = (function() {
var subflowInstances = []; var subflowInstances = [];
if (activeSubflow) { if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) { 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({ subflowInstances.push({
id: n.id, id: n.id,
changed: n.changed changed: n.changed
@ -285,6 +290,9 @@ RED.subflow = (function() {
n.resize = true; n.resize = true;
n.dirty = true; n.dirty = true;
RED.editor.updateNodeProperties(n); RED.editor.updateNodeProperties(n);
if (wasLocked) {
parentFlow.locked = true
}
}); });
RED.editor.validateNode(activeSubflow); RED.editor.validateNode(activeSubflow);
return { return {
@ -450,6 +458,9 @@ RED.subflow = (function() {
return return
} }
if (subflow.instances.length > 0) { 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>') const msg = $('<div>')
$('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg); $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
$('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg); $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
@ -554,7 +565,7 @@ RED.subflow = (function() {
} }
}); });
RED.events.on("view:selection-changed",function(selection) { 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); RED.menu.setDisabled("menu-item-subflow-convert",true);
} else { } else {
RED.menu.setDisabled("menu-item-subflow-convert",false); RED.menu.setDisabled("menu-item-subflow-convert",false);
@ -617,6 +628,9 @@ RED.subflow = (function() {
} }
function convertToSubflow() { function convertToSubflow() {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (!selection.nodes) { if (!selection.nodes) {
RED.notify(RED._("subflow.errors.noNodesSelected"),"error"); RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
@ -772,7 +786,7 @@ RED.subflow = (function() {
} }
subflowInstance._def = RED.nodes.getType(subflowInstance.type); subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance); RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance); subflowInstance = RED.nodes.add(subflowInstance);
if (containingGroup) { if (containingGroup) {
RED.group.addToGroup(containingGroup, subflowInstance); RED.group.addToGroup(containingGroup, subflowInstance);

View File

@ -43,12 +43,15 @@ RED.sidebar.config = (function() {
var categories = {}; var categories = {};
function getOrCreateCategory(name,parent,label) { function getOrCreateCategory(name,parent,label,isLocked) {
name = name.replace(/\./i,"-"); name = name.replace(/\./i,"-");
if (!categories[name]) { 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 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); 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) { 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); $('<span class="red-ui-palette-node-config-label"/>').text(label).appendTo(header);
} else { } else {
$('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header); $('<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 icon = header.find("i");
var result = { var result = {
label: label, label: label,
lockIcon,
list: category, list: category,
size: function() { size: function() {
return result.list.find("li:not(.red-ui-palette-node-config-none)").length return result.list.find("li:not(.red-ui-palette-node-config-none)").length
@ -100,6 +104,9 @@ RED.sidebar.config = (function() {
}); });
categories[name] = result; categories[name] = result;
} else { } else {
if (isLocked !== undefined && categories[name].lockIcon) {
categories[name].lockIcon.toggle(!!isLocked)
}
if (categories[name].label !== label) { if (categories[name].label !== label) {
categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label); categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label);
categories[name].label = label; categories[name].label = label;
@ -216,7 +223,7 @@ RED.sidebar.config = (function() {
RED.nodes.eachWorkspace(function(ws) { RED.nodes.eachWorkspace(function(ws) {
validList[ws.id.replace(/\./g,"-")] = true; 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) { RED.nodes.eachSubflow(function(sf) {
validList[sf.id.replace(/\./g,"-")] = true; validList[sf.id.replace(/\./g,"-")] = true;
@ -274,6 +281,15 @@ RED.sidebar.config = (function() {
changes: {}, changes: {},
dirty: RED.nodes.dirty() 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) { selectedNodes.forEach(function(id) {
var node = RED.nodes.node(id); var node = RED.nodes.node(id);
try { try {

View File

@ -141,7 +141,8 @@ RED.sidebar.help = (function() {
RED.events.on('registry:node-type-removed', queueRefresh); RED.events.on('registry:node-type-removed', queueRefresh);
RED.events.on('subflows:change', refreshSubflow); 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(); 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 // TODO: DRY - projects.js
function addTargetToExternalLinks(el) { function addTargetToExternalLinks(el) {
$(el).find("a").each(function(el) { $(el).find("a").each(function(el) {

View File

@ -221,6 +221,22 @@ RED.sidebar.info.outliner = (function() {
} else { } else {
$('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls) $('<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) { controls.find("button").on("dblclick", function(evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -364,6 +380,8 @@ RED.sidebar.info.outliner = (function() {
flowList.treeList.addChild(objects[ws.id]) flowList.treeList.addChild(objects[ws.id])
objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) 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].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(); updateSearch();
} }
@ -378,6 +396,8 @@ RED.sidebar.info.outliner = (function() {
existingObject.element.find(".red-ui-info-outline-item-label").text(label); existingObject.element.find(".red-ui-info-outline-item-label").text(label);
existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
existingObject.treeList.container.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(); updateSearch();
} }
function onFlowsReorder(order) { function onFlowsReorder(order) {

View File

@ -15,7 +15,7 @@
**/ **/
RED.view.tools = (function() { RED.view.tools = (function() {
'use strict';
function selectConnected(type) { function selectConnected(type) {
var selection = RED.view.selection(); var selection = RED.view.selection();
var visited = new Set(); var visited = new Set();
@ -39,6 +39,9 @@ RED.view.tools = (function() {
} }
function alignToGrid() { function alignToGrid() {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
var changedNodes = []; var changedNodes = [];
@ -87,6 +90,9 @@ RED.view.tools = (function() {
} }
function moveSelection(dx,dy) { function moveSelection(dx,dy) {
if (RED.workspaces.isActiveLocked()) {
return
}
if (moving_set === null) { if (moving_set === null) {
moving_set = []; moving_set = [];
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -153,6 +159,9 @@ RED.view.tools = (function() {
} }
function setSelectedNodeLabelState(labelShown) { function setSelectedNodeLabelState(labelShown) {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
var historyEvents = []; var historyEvents = [];
var nodes = []; var nodes = [];
@ -439,6 +448,9 @@ RED.view.tools = (function() {
} }
function alignSelectionToEdge(direction) { function alignSelectionToEdge(direction) {
// if (RED.workspaces.isActiveLocked()) {
// return
// }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length > 1) { if (selection.nodes && selection.nodes.length > 1) {
@ -539,8 +551,10 @@ RED.view.tools = (function() {
} }
} }
function distributeSelection(direction) { function distributeSelection(direction) {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length > 2) { if (selection.nodes && selection.nodes.length > 2) {
@ -699,6 +713,9 @@ RED.view.tools = (function() {
} }
function reorderSelection(dir) { function reorderSelection(dir) {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
var nodesToMove = []; var nodesToMove = [];
@ -734,8 +751,10 @@ RED.view.tools = (function() {
} }
} }
function wireSeriesOfNodes() { function wireSeriesOfNodes() {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
if (selection.nodes.length > 1) { if (selection.nodes.length > 1) {
@ -776,6 +795,9 @@ RED.view.tools = (function() {
} }
function wireNodeToMultiple() { function wireNodeToMultiple() {
if (RED.workspaces.isActiveLocked()) {
return
}
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
if (selection.nodes.length > 1) { 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. * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
*/ */
function splitWiresWithLinkNodes(wires) { function splitWiresWithLinkNodes(wires) {
if (RED.workspaces.isActiveLocked()) {
return
}
let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
if (!wiresToSplit) { if (!wiresToSplit) {
return return
@ -877,7 +902,6 @@ RED.view.tools = (function() {
if(!nnLinkOut) { if(!nnLinkOut) {
const nLinkOut = RED.view.createNode("link out"); //create link node const nLinkOut = RED.view.createNode("link out"); //create link node
nnLinkOut = nLinkOut.node; nnLinkOut = nLinkOut.node;
nodeSrcMap[linkOutMapId] = nnLinkOut;
let yOffset = 0; let yOffset = 0;
if(nSrc.outputs > 1) { if(nSrc.outputs > 1) {
@ -892,7 +916,8 @@ RED.view.tools = (function() {
updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset); updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
} }
//add created node //add created node
RED.nodes.add(nnLinkOut); nnLinkOut = RED.nodes.add(nnLinkOut);
nodeSrcMap[linkOutMapId] = nnLinkOut;
RED.editor.validateNode(nnLinkOut); RED.editor.validateNode(nnLinkOut);
history.events.push(nLinkOut.historyEvent); history.events.push(nLinkOut.historyEvent);
//connect node to link node //connect node to link node
@ -913,10 +938,10 @@ RED.view.tools = (function() {
if(!nnLinkIn) { if(!nnLinkIn) {
const nLinkIn = RED.view.createNode("link in"); //create link node const nLinkIn = RED.view.createNode("link in"); //create link node
nnLinkIn = nLinkIn.node; nnLinkIn = nLinkIn.node;
nodeTrgMap[nTrg.id] = nnLinkIn;
updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0); updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
//add created node //add created node
RED.nodes.add(nnLinkIn); nnLinkIn = RED.nodes.add(nnLinkIn);
nodeTrgMap[nTrg.id] = nnLinkIn;
RED.editor.validateNode(nnLinkIn); RED.editor.validateNode(nnLinkIn);
history.events.push(nLinkIn.historyEvent); history.events.push(nLinkIn.historyEvent);
//connect node to link node //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` * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
*/ */
function generateNodeNames(node, options) { function generateNodeNames(node, options) {
if (RED.workspaces.isActiveLocked()) {
return
}
options = Object.assign({ options = Object.assign({
renameBlank: true, renameBlank: true,
renameClash: true, renameClash: true,
@ -1061,6 +1089,9 @@ RED.view.tools = (function() {
} }
function addJunctionsToWires(wires) { function addJunctionsToWires(wires) {
if (RED.workspaces.isActiveLocked()) {
return
}
let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link)); let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
if (!wiresToSplit) { if (!wiresToSplit) {
return return
@ -1131,7 +1162,7 @@ RED.view.tools = (function() {
var nodeGroups = new Set() var nodeGroups = new Set()
RED.nodes.addJunction(junction) junction = RED.nodes.addJunction(junction)
addedJunctions.push(junction) addedJunctions.push(junction)
let newLink let newLink
if (gid === links[0].source.id+":"+links[0].sourcePort) { if (gid === links[0].source.id+":"+links[0].sourcePort) {

View File

@ -54,6 +54,7 @@ RED.view = (function() {
var spliceTimer; var spliceTimer;
var groupHoverTimer; var groupHoverTimer;
var activeFlowLocked = false;
var activeSubflow = null; var activeSubflow = null;
var activeNodes = []; var activeNodes = [];
var activeLinks = []; var activeLinks = [];
@ -411,8 +412,19 @@ RED.view = (function() {
activeSubflow = RED.nodes.subflow(event.workspace); activeSubflow = RED.nodes.subflow(event.workspace);
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); if (activeSubflow) {
RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || 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]) { if (workspaceScrollPositions[event.workspace]) {
chart.scrollLeft(workspaceScrollPositions[event.workspace].left); chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
@ -439,6 +451,15 @@ RED.view = (function() {
redraw(); 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({ RED.statusBar.add({
id: "view-zoom-controls", id: "view-zoom-controls",
align: "right", align: "right",
@ -496,6 +517,9 @@ RED.view = (function() {
chart.droppable({ chart.droppable({
accept:".red-ui-palette-node", accept:".red-ui-palette-node",
drop: function( event, ui ) { drop: function( event, ui ) {
if (activeFlowLocked) {
return
}
d3.event = event; d3.event = event;
var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
var result = createNode(selected_tool); var result = createNode(selected_tool);
@ -503,9 +527,7 @@ RED.view = (function() {
return; return;
} }
var historyEvent = result.historyEvent; var historyEvent = result.historyEvent;
var nn = result.node; var nn = RED.nodes.add(result.node);
RED.nodes.add(nn);
var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); 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")) { 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:copy-selection-to-internal-clipboard",copySelection);
RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
RED.actions.add("core:paste-from-internal-clipboard",function(){ RED.actions.add("core:paste-from-internal-clipboard",function(){
if (RED.workspaces.isActiveLocked()) {
return
}
importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'}); importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
}); });
@ -640,22 +665,27 @@ RED.view = (function() {
RED.events.on("view:selection-changed", function(selection) { RED.events.on("view:selection-changed", function(selection) {
var hasSelection = (selection.nodes && selection.nodes.length > 0); var hasSelection = (selection.nodes && selection.nodes.length > 0);
var hasMultipleSelection = hasSelection && selection.nodes.length > 1; var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); var hasLinkSelected = selection.links && selection.links.length > 0;
RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); var canEdit = !activeFlowLocked && hasSelection
RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); var canEditMultiple = !activeFlowLocked && hasMultipleSelection
RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); RED.menu.setDisabled("menu-item-edit-cut", !canEdit);
RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); RED.menu.setDisabled("menu-item-edit-copy", !hasSelection);
RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); RED.menu.setDisabled("menu-item-edit-select-connected", !hasSelection);
RED.menu.setDisabled("menu-item-view-tools-move-forwards",!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-left", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-align-center", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-align-right", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-align-top", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-align-middle", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-align-bottom", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally", !canEditMultiple);
RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); 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); RED.actions.add("core:delete-selection",deleteSelection);
@ -1045,7 +1075,7 @@ RED.view = (function() {
.attr("class", "nr-ui-view-lasso"); .attr("class", "nr-ui-view-lasso");
d3.event.preventDefault(); d3.event.preventDefault();
} }
} else if (d3.event.altKey) { } else if (d3.event.altKey && !activeFlowLocked) {
//Alt [+shift] held - Begin slicing //Alt [+shift] held - Begin slicing
clearSelection(); clearSelection();
mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING;
@ -1059,6 +1089,9 @@ RED.view = (function() {
} }
function showQuickAddDialog(options) { function showQuickAddDialog(options) {
if (activeFlowLocked) {
return
}
options = options || {}; options = options || {};
var point = options.position || lastClickPosition; var point = options.position || lastClickPosition;
var spliceLink = options.splice; 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")) { if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
nn.l = showLabel; nn.l = showLabel;
} }
if (nn.type === 'junction') {
nn = RED.nodes.addJunction(nn);
} else {
nn = RED.nodes.add(nn);
}
if (quickAddLink) { if (quickAddLink) {
var drag_line = quickAddLink; var drag_line = quickAddLink;
var src = null,dst,src_port; 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); RED.editor.validateNode(nn);
if (targetGroup) { 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]); 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)) { if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
mouse_mode = RED.state.MOVING_ACTIVE;
clickElapsed = 0; clickElapsed = 0;
spliceActive = false; if (!activeFlowLocked) {
if (movingSet.length() === 1) { mouse_mode = RED.state.MOVING_ACTIVE;
node = movingSet.get(0); spliceActive = false;
spliceActive = node.n.hasOwnProperty("_def") && if (movingSet.length() === 1) {
((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && node = movingSet.get(0);
((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && spliceActive = node.n.hasOwnProperty("_def") &&
RED.nodes.filterLinks({ source: node.n }).length === 0 && ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
RED.nodes.filterLinks({ target: node.n }).length === 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) { } 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() { function editSelection() {
if (RED.workspaces.isActiveLocked()) { return }
if (movingSet.length() > 0) { if (movingSet.length() > 0) {
var node = movingSet.get(0).n; var node = movingSet.get(0).n;
if (node.type === "subflow") { if (node.type === "subflow") {
@ -2471,6 +2508,9 @@ RED.view = (function() {
if (mouse_mode === RED.state.SELECTING_NODE) { if (mouse_mode === RED.state.SELECTING_NODE) {
return; return;
} }
if (activeFlowLocked) {
return
}
if (portLabelHover) { if (portLabelHover) {
portLabelHover.remove(); portLabelHover.remove();
portLabelHover = null; portLabelHover = null;
@ -2786,6 +2826,7 @@ RED.view = (function() {
function detachSelectedNodes() { function detachSelectedNodes() {
if (RED.workspaces.isActiveLocked()) { return }
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
@ -2927,7 +2968,7 @@ RED.view = (function() {
mousedown_node = d; mousedown_node = d;
mousedown_port_type = portType; mousedown_port_type = portType;
mousedown_port_index = portIndex || 0; 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; mouse_mode = RED.state.JOINING;
document.body.style.cursor = "crosshair"; document.body.style.cursor = "crosshair";
if (evt.ctrlKey || evt.metaKey) { if (evt.ctrlKey || evt.metaKey) {
@ -3367,6 +3408,11 @@ RED.view = (function() {
} }
if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
mouse_mode = RED.state.DEFAULT; mouse_mode = RED.state.DEFAULT;
if (RED.workspaces.isActiveLocked()) {
clickElapsed = 0;
d3.event.stopPropagation();
return
}
if (d.type != "subflow") { if (d.type != "subflow") {
if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
RED.workspaces.show(d.type.substring(8)); RED.workspaces.show(d.type.substring(8));
@ -3690,7 +3736,6 @@ RED.view = (function() {
} }
// selectedLinks.clear(); // selectedLinks.clear();
if (d3.event.button != 2) { if (d3.event.button != 2) {
mouse_mode = RED.state.MOVING;
var mouse = d3.touches(this)[0]||d3.mouse(this); var mouse = d3.touches(this)[0]||d3.mouse(this);
mouse[0] += d.x-d.w/2; mouse[0] += d.x-d.w/2;
mouse[1] += d.y-d.h/2; mouse[1] += d.y-d.h/2;
@ -3883,6 +3928,7 @@ RED.view = (function() {
if (RED.view.DEBUG) { if (RED.view.DEBUG) {
console.warn("groupMouseUp", { mouse_mode, event: d3.event }); console.warn("groupMouseUp", { mouse_mode, event: d3.event });
} }
if (RED.workspaces.isActiveLocked()) { return }
if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
mouse_mode = RED.state.DEFAULT; mouse_mode = RED.state.DEFAULT;
RED.editor.editGroup(g); RED.editor.editGroup(g);
@ -4053,7 +4099,7 @@ RED.view = (function() {
function isButtonEnabled(d) { function isButtonEnabled(d) {
var buttonEnabled = true; var buttonEnabled = true;
var ws = RED.nodes.workspace(RED.workspaces.active()); 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 (d._def.button.hasOwnProperty('enabled')) {
if (typeof d._def.button.enabled === "function") { if (typeof d._def.button.enabled === "function") {
buttonEnabled = d._def.button.enabled.call(d); buttonEnabled = d._def.button.enabled.call(d);
@ -4076,7 +4122,7 @@ RED.view = (function() {
} }
var activeWorkspace = RED.workspaces.active(); var activeWorkspace = RED.workspaces.active();
var ws = RED.nodes.workspace(activeWorkspace); 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) { if (d._def.button.toggle) {
d[d._def.button.toggle] = !d[d._def.button.toggle]; d[d._def.button.toggle] = !d[d._def.button.toggle];
d.dirty = true; d.dirty = true;
@ -4091,7 +4137,7 @@ RED.view = (function() {
if (d.dirty) { if (d.dirty) {
redraw(); redraw();
} }
} else { } else if (!ws || !ws.locked){
if (activeSubflow) { if (activeSubflow) {
RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning");
} else { } else {
@ -4106,14 +4152,15 @@ RED.view = (function() {
function showTouchMenu(obj,pos) { function showTouchMenu(obj,pos) {
var mdn = mousedown_node; var mdn = mousedown_node;
var options = []; var options = [];
options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); const isActiveLocked = RED.workspaces.isActiveLocked()
options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); 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:"select",onselect:function() {selectAll();}});
options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); 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(); chartPos = chart.offset();
showQuickAddDialog({ showQuickAddDialog({
position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], 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) { if (mouse_mode === RED.state.SELECTING_NODE) {
return; return;
} }
if (activeFlowLocked) {
return
}
var workspaceSelection = RED.workspaces.selection(); var workspaceSelection = RED.workspaces.selection();
var changed = false; var changed = false;
if (workspaceSelection.length > 0) { if (workspaceSelection.length > 0) {

View File

@ -58,6 +58,9 @@ RED.workspaces = (function() {
if (!ws.closeable) { if (!ws.closeable) {
ws.hideable = true; ws.hideable = true;
} }
if (!ws.hasOwnProperty('locked')) {
ws.locked = false
}
workspace_tabs.addTab(ws,targetIndex); workspace_tabs.addTab(ws,targetIndex);
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}"); var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
@ -75,6 +78,7 @@ RED.workspaces = (function() {
type: "tab", type: "tab",
id: tabId, id: tabId,
disabled: false, disabled: false,
locked: false,
info: "", info: "",
label: RED._('workspace.defaultName',{number:workspaceIndex}), label: RED._('workspace.defaultName',{number:workspaceIndex}),
env: [], env: [],
@ -99,6 +103,9 @@ RED.workspaces = (function() {
if (workspaceTabCount === 1) { if (workspaceTabCount === 1) {
return; return;
} }
if (ws.locked) {
return
}
var workspaceOrder = RED.nodes.getWorkspaceOrder(); var workspaceOrder = RED.nodes.getWorkspaceOrder();
ws._index = workspaceOrder.indexOf(ws.id); ws._index = workspaceOrder.indexOf(ws.id);
removeWorkspace(ws); removeWorkspace(ws);
@ -119,7 +126,9 @@ RED.workspaces = (function() {
RED.editor.editSubflow(subflow); RED.editor.editSubflow(subflow);
} }
} else { } 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 activeWorkspace = tab || RED.nodes.workspace(RED.workspaces.active()) || RED.nodes.subflow(RED.workspaces.active())
let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false
let isCurrentLocked = RED.workspaces.isActiveLocked()
if (tab) {
isCurrentLocked = tab.locked
}
var menuItems = [] var menuItems = []
if (isMenuButton) { if (isMenuButton) {
menuItems.push({ menuItems.push({
@ -184,14 +198,30 @@ RED.workspaces = (function() {
shortcut: RED.keyboard.getShortcut("core:enable-flow"), shortcut: RED.keyboard.getShortcut("core:enable-flow"),
onselect: function() { onselect: function() {
RED.actions.invoke("core:enable-flow", tab?tab.id:undefined) RED.actions.invoke("core:enable-flow", tab?tab.id:undefined)
} },
disabled: isCurrentLocked
} : { } : {
label: RED._("workspace.disableFlow"), label: RED._("workspace.disableFlow"),
shortcut: RED.keyboard.getShortcut("core:disable-flow"), shortcut: RED.keyboard.getShortcut("core:disable-flow"),
onselect: function() { onselect: function() {
RED.actions.invoke("core:disable-flow", tab?tab.id:undefined) 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() const currentTabs = workspace_tabs.listTabs()
@ -235,6 +265,7 @@ RED.workspaces = (function() {
} }
} }
) )
} }
menuItems.push( menuItems.push(
{ {
@ -260,6 +291,7 @@ RED.workspaces = (function() {
null, null,
{ {
label: RED._("common.label.delete"), label: RED._("common.label.delete"),
disabled: isCurrentLocked,
onselect: function() { onselect: function() {
if (tab.type === 'tab') { if (tab.type === 'tab') {
RED.workspaces.delete(tab) RED.workspaces.delete(tab)
@ -297,7 +329,8 @@ RED.workspaces = (function() {
$("#red-ui-workspace-chart").show(); $("#red-ui-workspace-chart").show();
activeWorkspace = tab.id; activeWorkspace = tab.id;
window.location.hash = 'flow/'+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 { } else {
$("#red-ui-workspace-chart").hide(); $("#red-ui-workspace-chart").hide();
activeWorkspace = 0; activeWorkspace = 0;
@ -329,6 +362,12 @@ RED.workspaces = (function() {
if (tab.disabled) { if (tab.disabled) {
$("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-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); RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
if (workspaceTabCount === 1) { if (workspaceTabCount === 1) {
showWorkspace(); showWorkspace();
@ -465,6 +504,8 @@ RED.workspaces = (function() {
RED.actions.add("core:remove-flow",removeWorkspace); RED.actions.add("core:remove-flow",removeWorkspace);
RED.actions.add("core:enable-flow",enableWorkspace); RED.actions.add("core:enable-flow",enableWorkspace);
RED.actions.add("core:disable-flow",disableWorkspace); 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-start", function(id) { moveWorkspace(id, 'start') });
RED.actions.add("core:move-flow-to-end", function(id) { moveWorkspace(id, 'end') }); RED.actions.add("core:move-flow-to-end", function(id) { moveWorkspace(id, 'end') });
@ -603,7 +644,7 @@ RED.workspaces = (function() {
} }
function setWorkspaceState(id,disabled) { function setWorkspaceState(id,disabled) {
var workspace = RED.nodes.workspace(id||activeWorkspace); var workspace = RED.nodes.workspace(id||activeWorkspace);
if (!workspace) { if (!workspace || workspace.locked) {
return; return;
} }
if (workspace.disabled !== disabled) { 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) { function removeWorkspace(ws) {
if (!ws) { if (!ws) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace)); ws = RED.nodes.workspace(activeWorkspace)
if (ws && !ws.locked) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
}
} else { } else {
if (ws.locked) { return }
if (workspace_tabs.contains(ws.id)) { if (workspace_tabs.contains(ws.id)) {
workspace_tabs.removeTab(ws.id); workspace_tabs.removeTab(ws.id);
} }
@ -737,6 +825,10 @@ RED.workspaces = (function() {
active: function() { active: function() {
return activeWorkspace return activeWorkspace
}, },
isActiveLocked: function() {
var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
return ws && ws.locked
},
selection: function() { selection: function() {
return workspace_tabs.selection(); return workspace_tabs.selection();
}, },
@ -793,6 +885,8 @@ RED.workspaces = (function() {
workspace_tabs.resize(); workspace_tabs.resize();
}, },
enable: enableWorkspace, enable: enableWorkspace,
disable: disableWorkspace disable: disableWorkspace,
lock: lockWorkspace,
unlock: unlockWorkspace
} }
})(); })();

View File

@ -68,6 +68,9 @@
stroke: var(--red-ui-node-border); stroke: var(--red-ui-node-border);
cursor: move; cursor: move;
stroke-width: 1; stroke-width: 1;
.red-ui-workspace-locked & {
cursor: pointer;
}
} }
.red-ui-workspace-select-mode { .red-ui-workspace-select-mode {
g.red-ui-flow-node.red-ui-flow-node-hovered * { g.red-ui-flow-node.red-ui-flow-node-hovered * {
@ -287,9 +290,11 @@ g.red-ui-flow-node-selected {
text-anchor:start; text-anchor:start;
} }
.red-ui-flow-port-hovered { #red-ui-workspace:not(.red-ui-workspace-locked) {
stroke: var(--red-ui-port-selected-color); .red-ui-flow-port-hovered {
fill: var(--red-ui-port-selected-color); stroke: var(--red-ui-port-selected-color);
fill: var(--red-ui-port-selected-color);
}
} }
.red-ui-flow-subflow-port { .red-ui-flow-subflow-port {

View File

@ -467,6 +467,9 @@ div.red-ui-info-table {
.fa-eye { .fa-eye {
display: none; display: none;
} }
.fa-unlock-alt {
display: none;
}
} }
.red-ui-info-outline-item-control-reveal, .red-ui-info-outline-item-control-reveal,
.red-ui-info-outline-item-control-action { .red-ui-info-outline-item-control-action {
@ -500,6 +503,25 @@ div.red-ui-info-table {
display: none; 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 { button {
margin-right: 3px margin-right: 3px
} }
@ -517,8 +539,6 @@ div.red-ui-info-table {
} }
} }
.red-ui-icons { .red-ui-icons {
display: inline-block; display: inline-block;
width: 18px; width: 18px;

View File

@ -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 { #red-ui-navigator-canvas {
position: absolute; position: absolute;