Merge branch 'gg-changes' into validate-users-in-history

This commit is contained in:
Nick O'Leary
2024-12-03 14:54:50 +00:00
committed by GitHub
63 changed files with 1218 additions and 581 deletions

View File

@@ -100,16 +100,36 @@ RED.multiplayer = (function () {
break
}
}
if (isInWorkspace) {
const chart = $('#red-ui-workspace-chart')
const chartOffset = chart.offset()
const scaleFactor = RED.view.scale()
location.cursor = {
x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
}
}
return location
}
let publishLocationTimeout
let lastPosition = [0,0]
let isInWorkspace = false
function publishLocation () {
const location = getLocation()
if (location.workspace !== 0) {
log('send', 'multiplayer/location', location)
RED.comms.send('multiplayer/location', location)
if (!publishLocationTimeout) {
publishLocationTimeout = setTimeout(() => {
const location = getLocation()
if (location.workspace !== 0) {
log('send', 'multiplayer/location', location)
RED.comms.send('multiplayer/location', location)
}
publishLocationTimeout = null
}, 100)
}
}
function revealUser(location, skipWorkspace) {
if (location.node) {
// Need to check if this is a known node, so we can fall back to revealing
@@ -271,7 +291,16 @@ RED.multiplayer = (function () {
function removeUserLocation (sessionId) {
updateUserLocation(sessionId, {})
removeUserCursor(sessionId)
}
function removeUserCursor (sessionId) {
// return
if (sessions[sessionId]?.cursor) {
sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
delete sessions[sessionId].cursor
}
}
function updateUserLocation (sessionId, location) {
let viewTouched = false
const oldLocation = sessions[sessionId].location
@@ -291,6 +320,28 @@ RED.multiplayer = (function () {
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
if (location.workspace) {
getWorkspaceTray(location.workspace).addUser(sessionId)
if (location.cursor && location.workspace === RED.workspaces.active()) {
if (!sessions[sessionId].cursor) {
const user = sessions[sessionId].user
const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
cursorIcon.appendChild(createAnnotationUser(user, true))
$(cursorIcon).css({
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
transition: 'transform 0.1s linear'
})
$("#red-ui-workspace-chart svg").append(cursorIcon)
sessions[sessionId].cursor = cursorIcon
} else {
const cursorIcon = sessions[sessionId].cursor
$(cursorIcon).css({
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
})
}
} else if (sessions[sessionId].cursor) {
removeUserCursor(sessionId)
}
}
if (location.node) {
addUserToNode(sessionId, location.node)
@@ -309,67 +360,69 @@ RED.multiplayer = (function () {
// }
// }
function createAnnotationUser(user, pointer = false) {
const radius = 20
const halfRadius = radius/2
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
let shapePath
if (!pointer) {
shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
} else {
shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
}
badge.setAttribute('d', shapePath)
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
group.appendChild(badge)
if (user && user.profileColor !== undefined) {
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
}
if (user && user.image) {
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
image.setAttribute("width", radius)
image.setAttribute("height", radius)
image.setAttribute("href", user.image)
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
group.appendChild(image)
} else if (user && user.anonymous) {
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
anonIconHead.setAttribute("cx", radius/2)
anonIconHead.setAttribute("cy", radius/2 - 2)
anonIconHead.setAttribute("r", 2.4)
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
group.appendChild(anonIconHead)
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
group.appendChild(anonIconBody)
} else {
const labelText = user.username ? user.username.substring(0,2) : user
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
if (user.username) {
label.setAttribute("class","red-ui-multiplayer-annotation-label");
label.textContent = user.username.substring(0,2)
} else {
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
label.textContent = user
}
label.setAttribute("text-anchor", "middle")
label.setAttribute("x",radius/2);
label.setAttribute("y",radius/2 + 3);
group.appendChild(label)
}
const border = document.createElementNS("http://www.w3.org/2000/svg","path");
border.setAttribute('d', shapePath)
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
group.appendChild(border)
return group
}
return {
init: function () {
function createAnnotationUser(user) {
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
const radius = 20
badge.setAttribute("cx",radius/2);
badge.setAttribute("cy",radius/2);
badge.setAttribute("r",radius/2);
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
group.appendChild(badge)
if (user && user.profileColor !== undefined) {
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
}
if (user && user.image) {
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
image.setAttribute("width", radius)
image.setAttribute("height", radius)
image.setAttribute("href", user.image)
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
group.appendChild(image)
} else if (user && user.anonymous) {
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
anonIconHead.setAttribute("cx", radius/2)
anonIconHead.setAttribute("cy", radius/2 - 2)
anonIconHead.setAttribute("r", 2.4)
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
group.appendChild(anonIconHead)
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
group.appendChild(anonIconBody)
} else {
const labelText = user.username ? user.username.substring(0,2) : user
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
if (user.username) {
label.setAttribute("class","red-ui-multiplayer-annotation-label");
label.textContent = user.username.substring(0,2)
} else {
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
label.textContent = user
}
label.setAttribute("text-anchor", "middle")
label.setAttribute("x",radius/2);
label.setAttribute("y",radius/2 + 3);
group.appendChild(label)
}
const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
border.setAttribute("cx",radius/2);
border.setAttribute("cy",radius/2);
border.setAttribute("r",radius/2);
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
group.appendChild(border)
return group
}
RED.view.annotations.register("red-ui-multiplayer",{
type: 'badge',
@@ -479,6 +532,24 @@ RED.multiplayer = (function () {
RED.comms.send('multiplayer/disconnect', disconnectInfo)
RED.settings.removeLocal('multiplayer:sessionId')
})
const chart = $('#red-ui-workspace-chart')
chart.on('mousemove', function (evt) {
lastPosition[0] = evt.clientX
lastPosition[1] = evt.clientY
publishLocation()
})
chart.on('scroll', function (evt) {
publishLocation()
})
chart.on('mouseenter', function () {
isInWorkspace = true
publishLocation()
})
chart.on('mouseleave', function () {
isInWorkspace = false
publishLocation()
})
}
}

View File

@@ -73,7 +73,13 @@ RED.nodes = (function() {
var exports = {
setModulePendingUpdated: function(module,version) {
moduleList[module].pending_version = version;
if (!!RED.plugins.getModule(module)) {
// The module updated is a plugin
RED.plugins.getModule(module).pending_version = version;
} else {
moduleList[module].pending_version = version;
}
RED.events.emit("registry:module-updated",{module:module,version:version});
},
getModule: function(module) {
@@ -1032,23 +1038,34 @@ RED.nodes = (function() {
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
}
/**
* Add a Subflow to the Workspace
*
* @param {object} sf The Subflow to add.
* @param {boolean|undefined} createNewIds Whether to update the name.
*/
function addSubflow(sf, createNewIds) {
if (createNewIds) {
var subflowNames = Object.keys(subflows).map(function(sfid) {
return subflows[sfid].name;
});
// Update the Subflow name to highlight that this is a copy
const subflowNames = Object.keys(subflows).map(function (sfid) {
return subflows[sfid].name || "";
})
subflowNames.sort()
subflowNames.sort();
var copyNumber = 1;
var subflowName = sf.name;
let copyNumber = 1;
let subflowName = sf.name;
subflowNames.forEach(function(name) {
if (subflowName == name) {
subflowName = sf.name + " (" + copyNumber + ")";
copyNumber++;
subflowName = sf.name+" ("+copyNumber+")";
}
});
sf.name = subflowName;
}
sf.instances = [];
subflows[sf.id] = sf;
allNodes.addTab(sf.id);
linkTabMap[sf.id] = [];
@@ -1101,7 +1118,7 @@ RED.nodes = (function() {
module: "node-red"
}
});
sf.instances = [];
sf._def = RED.nodes.getType("subflow:"+sf.id);
RED.events.emit("subflows:add",sf);
}
@@ -1743,7 +1760,8 @@ RED.nodes = (function() {
// Remove the old subflow definition - but leave the instances in place
var removalResult = RED.subflow.removeSubflow(n.id, true);
// Create the list of nodes for the new subflow def
var subflowNodes = [n].concat(zMap[n.id]);
// Need to sort the list in order to remove missing nodes
var subflowNodes = [n].concat(zMap[n.id]).filter((s) => !!s);
// Import the new subflow - no clashes should occur as we've removed
// the old version
var result = importNodes(subflowNodes);
@@ -2023,6 +2041,8 @@ RED.nodes = (function() {
if (matchingSubflow) {
subflow_denylist[n.id] = matchingSubflow;
} else {
const oldId = n.id;
subflow_map[n.id] = n;
if (createNewIds || options.importMap[n.id] === "copy") {
nid = getID();
@@ -2050,7 +2070,7 @@ RED.nodes = (function() {
n.status.id = getID();
}
new_subflows.push(n);
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
addSubflow(n,createNewIds || options.importMap[oldId] === "copy");
}
}
}
@@ -2170,7 +2190,7 @@ RED.nodes = (function() {
x:parseFloat(n.x || 0),
y:parseFloat(n.y || 0),
z:n.z,
type:0,
type: n.type,
info: n.info,
changed:false,
_config:{}
@@ -2231,7 +2251,6 @@ RED.nodes = (function() {
}
}
}
node.type = n.type;
node._def = def;
if (node.type === "group") {
node._def = RED.group.def;
@@ -2261,6 +2280,15 @@ RED.nodes = (function() {
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
set: registry.getNodeSet("node-red/unknown")
}
var orig = {};
for (var p in n) {
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
orig[p] = n[p];
}
}
node._orig = orig;
node.name = n.type;
node.type = "unknown";
} else {
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
@@ -2406,11 +2434,28 @@ RED.nodes = (function() {
} else {
delete n.g
}
// If importing into a subflow, ensure an outbound-link doesn't get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
// If importing a link node, ensure both ends of each link are either:
// - not in a subflow
// - both in the same subflow (not for link call node)
if (/^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace);
if (!otherNode) {
// Cannot find other end - remove the link
return false
}
if (otherNode.z === n.z) {
// Both ends in the same flow/subflow
return true
} else if (n.type === "link call" && !getSubflow(otherNode.z)) {
// Link call node can call out of a subflow as long as otherNode is
// not in a subflow
return true
} else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
// One end is in a subflow - remove the link
return false
}
return true
});
}
for (var d3 in n._def.defaults) {
@@ -2441,9 +2486,11 @@ RED.nodes = (function() {
n = new_subflows[i];
n.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
addLink(link);
new_links.push(link);
if (node_map.hasOwnProperty(wire.id)) {
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
addLink(link);
new_links.push(link);
}
});
delete input.wires;
});
@@ -2452,11 +2499,13 @@ RED.nodes = (function() {
var link;
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
} else {
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
}
addLink(link);
new_links.push(link);
if (link) {
addLink(link);
new_links.push(link);
}
});
delete output.wires;
});
@@ -2465,11 +2514,13 @@ RED.nodes = (function() {
var link;
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
} else {
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
}
addLink(link);
new_links.push(link);
if (link) {
addLink(link);
new_links.push(link);
}
});
delete n.status.wires;
}

View File

@@ -205,7 +205,9 @@ RED.actionList = (function() {
}
function init() {
RED.actions.add("core:show-action-list",show);
if (RED.settings.theme("menu.menu-item-action-list", true)) {
RED.actions.add("core:show-action-list",show);
}
RED.events.on("editor:open",function() { disabled = true; });
RED.events.on("editor:close",function() { disabled = false; });

View File

@@ -32,24 +32,28 @@ RED.contextMenu = (function () {
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
if (hasSelection) {
selection.nodes.forEach(n => {
const nodes = selection.nodes.slice();
while (nodes.length) {
const n = nodes.shift();
if (n.type === 'group') {
hasGroup = true;
nodes.push(...n.nodes);
} else {
isAllGroups = false;
}
if (n.d) {
hasDisabledNode = true;
} else {
hasEnabledNode = true;
if (n.d) {
hasDisabledNode = true;
} else {
hasEnabledNode = true;
}
}
if (n.l === undefined || n.l) {
hasLabeledNode = true;
} else {
hasUnlabeledNode = true;
}
});
}
}
const offset = $("#red-ui-workspace-chart").offset()
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
@@ -61,10 +65,11 @@ RED.contextMenu = (function () {
addY = gridSize * Math.floor(addY / gridSize)
}
menuItems.push(
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
)
if (RED.settings.theme("menu.menu-item-action-list", true)) {
menuItems.push(
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
)
}
const insertOptions = []
menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
insertOptions.push(

View File

@@ -589,7 +589,9 @@ RED.deploy = (function() {
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
}
const flowsToLock = new Set()
// Node's properties cannot be modified if its workspace is locked.
function ensureUnlocked(id) {
// TODO: `RED.nodes.subflow` is useless
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false;
if (flow && isLocked) {
@@ -642,6 +644,7 @@ RED.deploy = (function() {
delete confNode.credentials;
}
});
// Subflow cannot be locked
RED.nodes.eachSubflow(function (subflow) {
if (subflow.changed) {
subflow.changed = false;
@@ -650,12 +653,18 @@ RED.deploy = (function() {
});
RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) {
ensureUnlocked(ws.z)
// Ensure the Workspace is unlocked to modify its properties.
ensureUnlocked(ws.id);
ws.changed = false;
delete ws.added
if (flowsToLock.has(ws)) {
ws.locked = true;
flowsToLock.delete(ws);
}
RED.events.emit("flows:change", ws)
}
});
// Ensures all workspaces to be locked have been locked.
flowsToLock.forEach(flow => {
flow.locked = true
})

View File

@@ -497,7 +497,7 @@ RED.diff = (function() {
}
})
if (c === 0) {
result.text("none");
result.text(RED._("diff.type.none"));
} else {
list.appendTo(result);
}
@@ -821,7 +821,7 @@ RED.diff = (function() {
conflict = true;
}
row = $("<tr>").appendTo(nodePropertiesTableBody);
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.position")).appendTo(row);
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) {
localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
@@ -899,7 +899,7 @@ RED.diff = (function() {
conflict = true;
}
row = $("<tr>").appendTo(nodePropertiesTableBody);
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("wires").appendTo(row);
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.wires")).appendTo(row);
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) {
if (!conflict) {
@@ -2029,15 +2029,14 @@ RED.diff = (function() {
if (!isSeparator) {
var isOurs = /^..<<<<<<</.test(lineText);
if (isOurs) {
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
$('<span>').text("<<<<<<< " + RED._("diff.localChanges")).appendTo(line);
hunk.localChangeStart = actualLineNumber;
} else {
hunk.remoteChangeEnd = actualLineNumber;
$('<span>').text(">>>>>>> Remote Changes").appendTo(line);
$('<span>').text(">>>>>>> " + RED._("diff.remoteChanges")).appendTo(line);
}
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> '+RED._(isOurs?"diff.useLocalChanges":"diff.useRemoteChanges")+'</button>')
.appendTo(line)
.on("click", function(evt) {
evt.preventDefault();
@@ -2119,7 +2118,7 @@ RED.diff = (function() {
$("<h3>").text(commit.title).appendTo(content);
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
var summary = $('<div class="commit-summary"></div>').appendTo(content);
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
$('<div style="float: right">').text(RED._('diff.commit')+" "+commit.sha).appendTo(summary);
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
if (commit.files) {

View File

@@ -808,6 +808,17 @@ RED.editor = (function() {
}
}
const oldCreds = {};
if (editing_node._def.credentials) {
for (const prop in editing_node._def.credentials) {
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
if (prop in editing_node.credentials) {
oldCreds[prop] = editing_node.credentials[prop];
}
}
}
}
try {
const rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
@@ -839,6 +850,21 @@ RED.editor = (function() {
}
}
}
if (editing_node._def.credentials) {
for (const prop in editing_node._def.credentials) {
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
if (oldCreds[prop] !== editing_node.credentials[prop]) {
if (editing_node.credentials[prop] === '__PWRD__') {
continue;
}
editState.changes.credentials = editState.changes.credentials || {};
editState.changes.credentials[prop] = oldCreds[prop];
editState.changed = true;
}
}
}
}
}
}
@@ -899,7 +925,7 @@ RED.editor = (function() {
const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
const config = {
env: tenv,
id: '${' + parentEnv[0].name + '}',
id: '${' + tenv.name + '}',
type: type,
label: labelText,
__label__: `[env] ${labelText}`
@@ -1481,134 +1507,181 @@ RED.editor = (function() {
},
{
id: "node-config-dialog-ok",
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
class: "primary",
click: function() {
var editState = {
// TODO: Already defined
const configProperty = name;
const configType = type;
const configTypeDef = RED.nodes.getType(configType);
const wasChanged = editing_config_node.changed;
const editState = {
changes: {},
changed: false,
outputMap: null
};
var configProperty = name;
var configId = editing_config_node.id;
var configType = type;
var configAdding = adding;
var configTypeDef = RED.nodes.getType(configType);
var d;
var input;
// Call `oneditsave` and search for changes
handleEditSave(editing_config_node, editState);
if (configTypeDef.oneditsave) {
try {
configTypeDef.oneditsave.call(editing_config_node);
} catch(err) {
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
}
}
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
var newValue;
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null && newValue !== editing_config_node[d]) {
if (editing_config_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
// Change to a related config node
var configNode = RED.nodes.node(editing_config_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_config_node),1);
RED.events.emit("nodes:change",configNode);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_config_node);
RED.events.emit("nodes:change",configNode);
}
}
editing_config_node[d] = newValue;
}
}
}
activeEditPanes.forEach(function(pane) {
// Search for changes in the edit box (panes)
activeEditPanes.forEach(function (pane) {
if (pane.apply) {
pane.apply.call(pane, editState);
}
})
});
editing_config_node.label = configTypeDef.label;
var scope = $("#red-ui-editor-config-scope").val();
editing_config_node.z = scope;
// TODO: Why?
editing_config_node.label = configTypeDef.label
// Check if disabled has changed
if ($("#node-config-input-node-disabled").prop('checked')) {
if (editing_config_node.d !== true) {
editState.changes.d = editing_config_node.d;
editState.changed = true;
editing_config_node.d = true;
}
} else {
if (editing_config_node.d === true) {
editState.changes.d = editing_config_node.d;
editState.changed = true;
delete editing_config_node.d;
}
}
// NOTE: must be undefined if no scope used
const scope = $("#red-ui-editor-config-scope").val() || undefined;
// Check if the scope has changed
if (editing_config_node.z !== scope) {
editState.changes.z = editing_config_node.z;
editState.changed = true;
editing_config_node.z = scope;
}
// Search for nodes that use this config node that are no longer
// in scope, so must be removed
const historyEvents = [];
if (scope) {
// Search for nodes that use this one that are no longer
// in scope, so must be removed
editing_config_node.users = editing_config_node.users.filter(function(n) {
var keep = true;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
if (n._def.defaults[d].type === editing_config_node.type &&
n[d] === editing_config_node.id &&
n.z !== scope) {
keep = false;
// Remove the reference to this node
// and revalidate
n[d] = null;
n.dirty = true;
n.changed = true;
validateNode(n);
const newUsers = editing_config_node.users.filter(function (node) {
let keepNode = false;
let nodeModified = null;
for (const d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
if (node._def.defaults[d].type === editing_config_node.type) {
if (node[d] === editing_config_node.id) {
if (node.z === editing_config_node.z) {
// The node is kept only if at least one property uses
// this config node in the correct scope.
keepNode = true;
} else {
if (!nodeModified) {
nodeModified = {
t: "edit",
node: node,
changes: { [d]: node[d] },
changed: node.changed,
dirty: node.dirty
};
} else {
nodeModified.changes[d] = node[d];
}
// Remove the reference to the config node
node[d] = "";
}
}
}
}
}
return keep;
});
}
if (configAdding) {
RED.nodes.add(editing_config_node);
}
validateNode(editing_config_node);
var validatedNodes = {};
validatedNodes[editing_config_node.id] = true;
var userStack = editing_config_node.users.slice();
while(userStack.length > 0) {
var user = userStack.pop();
if (!validatedNodes[user.id]) {
validatedNodes[user.id] = true;
if (user.users) {
userStack = userStack.concat(user.users);
// Add the node modified to the history
if (nodeModified) {
historyEvents.push(nodeModified);
}
validateNode(user);
// Mark as changed and revalidate this node
if (!keepNode) {
node.changed = true;
node.dirty = true;
validateNode(node);
RED.events.emit("nodes:change", node);
}
return keepNode;
});
// Check if users are changed
if (editing_config_node.users.length !== newUsers.length) {
editState.changes.users = editing_config_node.users;
editState.changed = true;
editing_config_node.users = newUsers;
}
}
RED.nodes.dirty(true);
RED.view.redraw(true);
if (!configAdding) {
RED.events.emit("editor:save",editing_config_node);
RED.events.emit("nodes:change",editing_config_node);
if (editState.changed) {
// Set the congig node as changed
editing_config_node.changed = true;
}
// Now, validate the config node
validateNode(editing_config_node);
// And validate nodes using this config node too
const validatedNodes = new Set();
const userStack = editing_config_node.users.slice();
validatedNodes.add(editing_config_node.id);
while (userStack.length) {
const node = userStack.pop();
if (!validatedNodes.has(node.id)) {
validatedNodes.add(node.id);
if (node.users) {
userStack.push(...node.users);
}
validateNode(node);
}
}
let historyEvent = {
t: "edit",
node: editing_config_node,
changes: editState.changes,
changed: wasChanged,
dirty: RED.nodes.dirty()
};
if (historyEvents.length) {
// Need a multi events
historyEvent = {
t: "multi",
events: [historyEvent].concat(historyEvents),
dirty: historyEvent.dirty
};
}
if (!adding) {
// This event is triggered when the edit box is saved,
// regardless of whether there are any modifications.
RED.events.emit("editor:save", editing_config_node);
}
if (editState.changed) {
if (adding) {
RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() });
// Add the new config node and trigger the `nodes:add` event
RED.nodes.add(editing_config_node);
} else {
RED.history.push(historyEvent);
RED.events.emit("nodes:change", editing_config_node);
}
RED.nodes.dirty(true);
RED.view.redraw(true);
}
RED.tray.close(function() {
var filter = null;
// when editing a config via subflow edit panel, the `configProperty` will not

View File

@@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() {
//Handles orphaned models
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
RED.events.on("editor:close",function() {
let models = window.monaco ? monaco.editor.getModels() : null;
if (!window.monaco) { return; }
const editors = window.monaco.editor.getEditors()
const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode()))
orphanEditors.forEach(editor => {
editor.dispose();
});
let models = monaco.editor.getModels()
if(models && models.length) {
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
for (let index = 0; index < models.length; index++) {
@@ -1124,6 +1130,7 @@ RED.editor.codeEditor.monaco = (function() {
$(el).remove();
$(toolbarRow).remove();
ed.dispose();
}
ed.resize = function resize() {

View File

@@ -131,7 +131,7 @@ RED.editor.envVarList = (function() {
nameField.trigger('change');
}
},
sortable: ".red-ui-editableList-item-handle",
sortable: true,
removable: false
});
var parentEnv = {};

View File

@@ -839,10 +839,10 @@ RED.library = (function() {
if (file && file.label && !file.children) {
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
//TODO: nls + sanitize
var propRow = $('<tr class="red-ui-help-info-row"><td>Type</td><td></td></tr>').appendTo(table);
var propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.type")+'</td><td></td></tr>').appendTo(table);
$(propRow.children()[1]).text(activeLibrary.type);
if (file.props.hasOwnProperty('name')) {
propRow = $('<tr class="red-ui-help-info-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.name")+'</td><td>'+file.props.name+'</td></tr>').appendTo(table);
$(propRow.children()[1]).text(file.props.name);
}
for (var p in file.props) {

View File

@@ -308,7 +308,7 @@ RED.projects.settings = (function() {
if (activeProject.dependencies) {
for (var m in activeProject.dependencies) {
if (activeProject.dependencies.hasOwnProperty(m)) {
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version;
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m]?.version;
depsList.editableList('addItem',{
id: m,
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
@@ -1256,7 +1256,7 @@ RED.projects.settings = (function() {
notification.close();
}
},{
text: 'Delete branch',
text: RED._("sidebar.project.projectSettings.deleteBranch"),
click: function() {
notification.close();
var url = "projects/"+activeProject.name+"/branches/"+entry.name;

View File

@@ -204,7 +204,7 @@ RED.sidebar.info = (function() {
propertiesPanelHeaderIcon.empty();
RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
propertiesPanelHeaderLabel.text("Selection");
propertiesPanelHeaderLabel.text(RED._("sidebar.info.selection"));
propertiesPanelHeaderReveal.hide();
propertiesPanelHeaderHelp.hide();
propertiesPanelHeaderCopyLink.hide();

View File

@@ -279,6 +279,11 @@ RED.typeSearch = (function() {
if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
opts.y = opts.y - 275;
}
const dialogWidth = dialog.width() || 300 // default is 300 (defined in class .red-ui-search)
const workspaceWidth = $('#red-ui-workspace').width()
if (workspaceWidth > dialogWidth && workspaceWidth - opts.x - dialogWidth < 0) {
opts.x = opts.x - (dialogWidth - RED.view.node_width)
}
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
searchResultsDiv.slideDown(300);
setTimeout(function() {
@@ -330,13 +335,25 @@ RED.typeSearch = (function() {
}
}
function applyFilter(filter,type,def) {
return !def || !filter ||
(
(!filter.spliceMultiple) &&
(!filter.type || type === filter.type) &&
(!filter.input || type === 'junction' || def.inputs > 0) &&
(!filter.output || type === 'junction' || def.outputs > 0)
)
if (!filter) {
// No filter; allow everything
return true
}
if (type === 'junction') {
// Only allow Junction is there's no specific type filter
return !filter.type
}
if (filter.type) {
// Handle explicit type filter
return filter.type === type
}
if (!def) {
// No node definition available - allow it
return true
}
// Check if the filter is for input/outputs and apply
return (!filter.input || def.inputs > 0) &&
(!filter.output || def.outputs > 0)
}
function refreshTypeList(opts) {
var i;
@@ -365,7 +382,7 @@ RED.typeSearch = (function() {
var items = [];
RED.nodes.registry.getNodeTypes().forEach(function(t) {
var def = RED.nodes.getType(t);
if (def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
if (def.set?.enabled !== false && def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
items.push({type:t,def: def, label:getTypeLabel(t,def)});
}
});

View File

@@ -1102,18 +1102,27 @@ RED.view.tools = (function() {
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
if (!typeIndex.hasOwnProperty(n.type)) {
const existingNodes = RED.nodes.filterNodes({type: n.type})
let maxNameNumber = 0;
existingNodes.forEach(n => {
let match = defaultNodeNameRE.exec(n.name)
const existingNodes = RED.nodes.filterNodes({ type: n.type });
const existingIds = existingNodes.reduce((ids, node) => {
let match = defaultNodeNameRE.exec(node.name);
if (match) {
let nodeNumber = parseInt(match[1])
if (nodeNumber > maxNameNumber) {
maxNameNumber = nodeNumber
const nodeNumber = parseInt(match[1], 10);
if (!ids.includes(nodeNumber)) {
ids.push(nodeNumber);
}
}
})
typeIndex[n.type] = maxNameNumber + 1
return ids;
}, []).sort((a, b) => a - b);
let availableNameNumber = 1;
for (let i = 0; i < existingIds.length; i++) {
if (existingIds[i] !== availableNameNumber) {
break;
}
availableNameNumber++;
}
typeIndex[n.type] = availableNameNumber;
}
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
if (generateHistory) {

View File

@@ -288,7 +288,7 @@ RED.view = (function() {
}
selectedLinks.clearUnselected()
},
length: () => groups.length,
length: () => groups.size,
forEach: (func) => { groups.forEach(func) },
toArray: () => [...groups],
clear: function () {
@@ -1209,7 +1209,10 @@ RED.view = (function() {
lasso = null;
}
if (d3.event.touches || d3.event.button === 0) {
if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) {
if (
(mouse_mode === 0 && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) ||
mouse_mode === RED.state.QUICK_JOINING
) {
// Trigger quick add dialog
d3.event.stopPropagation();
clearSelection();
@@ -1285,7 +1288,6 @@ RED.view = (function() {
}
var mainPos = $("#red-ui-main-container").position();
if (mouse_mode !== RED.state.QUICK_JOINING) {
mouse_mode = RED.state.QUICK_JOINING;
$(window).on('keyup',disableQuickJoinEventHandler);
@@ -2687,22 +2689,21 @@ RED.view = (function() {
addToRemovedLinks(reconnectResult.removedLinks)
}
var startDirty = RED.nodes.dirty();
var startChanged = false;
var selectedGroups = [];
const startDirty = RED.nodes.dirty();
let movingSelectedGroups = [];
if (movingSet.length() > 0) {
for (var i=0;i<movingSet.length();i++) {
node = movingSet.get(i).n;
if (node.type === "group") {
selectedGroups.push(node);
movingSelectedGroups.push(node);
}
}
// Make sure we have identified all groups about to be deleted
for (i=0;i<selectedGroups.length;i++) {
selectedGroups[i].nodes.forEach(function(n) {
if (n.type === "group" && selectedGroups.indexOf(n) === -1) {
selectedGroups.push(n);
for (i=0;i<movingSelectedGroups.length;i++) {
movingSelectedGroups[i].nodes.forEach(function(n) {
if (n.type === "group" && movingSelectedGroups.indexOf(n) === -1) {
movingSelectedGroups.push(n);
}
})
}
@@ -2719,7 +2720,7 @@ RED.view = (function() {
addToRemovedLinks(removedEntities.links);
if (node.g) {
var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) {
if (movingSelectedGroups.indexOf(group) === -1) {
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
var index = group.nodes.indexOf(node);
@@ -2733,7 +2734,7 @@ RED.view = (function() {
removedLinks = removedLinks.concat(result.links);
if (node.g) {
var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) {
if (movingSelectedGroups.indexOf(group) === -1) {
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
var index = group.nodes.indexOf(node);
@@ -2755,8 +2756,8 @@ RED.view = (function() {
// Groups must be removed in the right order - from inner-most
// to outermost.
for (i = selectedGroups.length-1; i>=0; i--) {
var g = selectedGroups[i];
for (i = movingSelectedGroups.length-1; i>=0; i--) {
var g = movingSelectedGroups[i];
removedGroups.push(g);
RED.nodes.removeGroup(g);
}
@@ -3057,8 +3058,8 @@ RED.view = (function() {
}
function disableQuickJoinEventHandler(evt) {
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari), or Escape
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91 || evt.keyCode === 27) {
resetMouseVars();
hideDragLines();
redraw();
@@ -3189,27 +3190,59 @@ RED.view = (function() {
for (i=0;i<drag_lines.length;i++) {
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
var drag_line = drag_lines[i];
var src,dst,src_port;
let drag_line = drag_lines[i];
let src,dst,src_port;
let oldDst;
let oldSrc;
if (drag_line.portType === PORT_TYPE_OUTPUT) {
src = drag_line.node;
src_port = drag_line.port;
dst = mouseup_node;
oldSrc = src;
if (drag_line.link) {
oldDst = drag_line.link.target;
}
} else if (drag_line.portType === PORT_TYPE_INPUT) {
src = mouseup_node;
dst = drag_line.node;
src_port = portIndex || 0;
oldSrc = dst;
if (drag_line.link) {
oldDst = drag_line.link.source
}
}
var link = {source: src, sourcePort:src_port, target: dst};
if (drag_line.virtualLink) {
if (/^link (in|out)$/.test(src.type) && /^link (in|out)$/.test(dst.type) && src.type !== dst.type) {
if (src.links.indexOf(dst.id) === -1 && dst.links.indexOf(src.id) === -1) {
var oldSrcLinks = $.extend(true,{},{v:src.links}).v
var oldDstLinks = $.extend(true,{},{v:dst.links}).v
var oldSrcLinks = [...src.links]
var oldDstLinks = [...dst.links]
src.links.push(dst.id);
dst.links.push(src.id);
if (oldDst) {
src.links = src.links.filter(id => id !== oldDst.id)
dst.links = dst.links.filter(id => id !== oldDst.id)
var oldOldDstLinks = [...oldDst.links]
oldDst.links = oldDst.links.filter(id => id !== oldSrc.id)
oldDst.dirty = true;
modifiedNodes.push(oldDst);
linkEditEvents.push({
t:'edit',
node: oldDst,
dirty: RED.nodes.dirty(),
changed: oldDst.changed,
changes: {
links:oldOldDstLinks
}
});
oldDst.changed = true;
}
src.dirty = true;
dst.dirty = true;
modifiedNodes.push(src);
modifiedNodes.push(dst);
@@ -3237,6 +3270,7 @@ RED.view = (function() {
links:oldDstLinks
}
});
src.changed = true;
dst.changed = true;
}

View File

@@ -183,25 +183,29 @@ RED.workspaces = (function() {
},
null)
}
menuItems.push(
{
id:"red-ui-tabs-menu-option-add-flow",
label: RED._("workspace.addFlow"),
onselect: "core:add-flow"
}
)
if (isMenuButton || !!tab) {
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
menuItems.push(
{
id:"red-ui-tabs-menu-option-add-flow-right",
label: RED._("workspace.addFlowToRight"),
shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
onselect: function() {
RED.actions.invoke("core:add-flow-to-right", tab)
}
},
null
id:"red-ui-tabs-menu-option-add-flow",
label: RED._("workspace.addFlow"),
onselect: "core:add-flow"
}
)
}
if (isMenuButton || !!tab) {
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
menuItems.push(
{
id:"red-ui-tabs-menu-option-add-flow-right",
label: RED._("workspace.addFlowToRight"),
shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
onselect: function() {
RED.actions.invoke("core:add-flow-to-right", tab)
}
},
null
)
}
if (activeWorkspace && activeWorkspace.type === 'tab') {
menuItems.push(
isFlowDisabled ? {
@@ -255,7 +259,9 @@ RED.workspaces = (function() {
}
)
}
menuItems.push(null)
if (menuItems.length > 0) {
menuItems.push(null)
}
if (isMenuButton || !!tab) {
menuItems.push(
{
@@ -299,19 +305,24 @@ RED.workspaces = (function() {
}
)
if (tab) {
menuItems.push(null)
if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
menuItems.push(
{
label: RED._("common.label.delete"),
onselect: function() {
if (tab.type === 'tab') {
RED.workspaces.delete(tab)
} else if (tab.type === 'subflow') {
RED.subflow.delete(tab.id)
}
},
disabled: isCurrentLocked || (workspaceTabCount === 1)
}
)
}
menuItems.push(
null,
{
label: RED._("common.label.delete"),
onselect: function() {
if (tab.type === 'tab') {
RED.workspaces.delete(tab)
} else if (tab.type === 'subflow') {
RED.subflow.delete(tab.id)
}
},
disabled: isCurrentLocked || (workspaceTabCount === 1)
},
{
label: RED._("menu.label.export"),
shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
@@ -468,7 +479,7 @@ RED.workspaces = (function() {
},
minimumActiveTabWidth: 150,
scrollable: true,
addButton: "core:add-flow",
addButton: RED.settings.theme("menu.menu-item-workspace-add", true) ? "core:add-flow" : undefined,
addButtonCaption: RED._("workspace.addFlow"),
menu: function() { return getMenuItems(true) },
contextmenu: function(tab) { return getMenuItems(false, tab) }
@@ -525,19 +536,24 @@ RED.workspaces = (function() {
$(window).on("resize", function() {
workspace_tabs.resize();
});
RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
RED.actions.add("core:add-flow-to-right",function(workspace) {
let index
if (workspace) {
index = workspace_tabs.getTabIndex(workspace.id)+1
} else {
index = workspace_tabs.activeIndex()+1
}
addWorkspace(undefined,undefined,index)
});
RED.actions.add("core:edit-flow",editWorkspace);
RED.actions.add("core:remove-flow",removeWorkspace);
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
RED.actions.add("core:add-flow-to-right",function(workspace) {
let index
if (workspace) {
index = workspace_tabs.getTabIndex(workspace.id)+1
} else {
index = workspace_tabs.activeIndex()+1
}
addWorkspace(undefined,undefined,index)
});
}
if (RED.settings.theme("menu.menu-item-workspace-edit", true)) {
RED.actions.add("core:edit-flow",editWorkspace);
}
if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
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);
@@ -904,6 +920,17 @@ RED.workspaces = (function() {
}
},
refresh: function() {
var workspace = RED.nodes.workspace(RED.workspaces.active());
if (workspace) {
document.title = `${documentTitle} : ${workspace.label}`;
} else {
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (subflow) {
document.title = `${documentTitle} : ${subflow.name}`;
} else {
document.title = documentTitle
}
}
RED.nodes.eachWorkspace(function(ws) {
workspace_tabs.renameTab(ws.id,ws.label);
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)

View File

@@ -259,7 +259,7 @@ $deploy-button-background-disabled-hover: #555;
$header-background: #000;
$header-button-background-active: #121212;
$header-accent: #d41313;
$header-accent: #C02020;
$header-menu-color: #eee;
$header-menu-color-disabled: #666;
$header-menu-heading-color: #fff;

View File

@@ -151,8 +151,9 @@
&.red-ui-tabs-add {
padding-right: 29px;
}
&.red-ui-tabs-add.red-ui-tabs-scrollable {
padding-right: 53px;
&.red-ui-tabs-add.red-ui-tabs-scrollable,
&.red-ui-tabs-menu.red-ui-tabs-scrollable {
padding-right: 53px;
}
&.red-ui-tabs-add.red-ui-tabs-menu.red-ui-tabs-scrollable,
&.red-ui-tabs-add.red-ui-tabs-search.red-ui-tabs-scrollable {
@@ -310,8 +311,9 @@
}
}
.red-ui-tabs.red-ui-tabs-add .red-ui-tab-scroll-right {
right: 32px;
.red-ui-tabs.red-ui-tabs-add .red-ui-tab-scroll-right,
.red-ui-tabs.red-ui-tabs-menu .red-ui-tab-scroll-right {
right: 32px;
}
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-menu .red-ui-tab-scroll-right,