1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into group-rework

This commit is contained in:
Nick O'Leary 2023-03-02 15:26:01 +00:00 committed by GitHub
commit 08eaa9274f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 298 additions and 76 deletions

View File

@ -1382,6 +1382,7 @@
"copy-item-edit-url": "要素の編集URLをコピー", "copy-item-edit-url": "要素の編集URLをコピー",
"move-flow-to-start": "フローを先頭に移動", "move-flow-to-start": "フローを先頭に移動",
"move-flow-to-end": "フローを末尾に移動", "move-flow-to-end": "フローを末尾に移動",
"show-global-env": "大域環境変数を表示",
"lock-flow": "フローを固定", "lock-flow": "フローを固定",
"unlock-flow": "フローの固定を解除", "unlock-flow": "フローの固定を解除",
"show-node-help": "ノードのヘルプを表示" "show-node-help": "ノードのヘルプを表示"

View File

@ -422,6 +422,9 @@ RED.history = (function() {
ev.node[i] = ev.changes[i]; ev.node[i] = ev.changes[i];
} }
} }
ev.node.dirty = true;
ev.node.changed = ev.changed;
var eventType; var eventType;
switch(ev.node.type) { switch(ev.node.type) {
case 'tab': eventType = "flows"; break; case 'tab': eventType = "flows"; break;
@ -512,8 +515,6 @@ RED.history = (function() {
inverseEv.links.push(ev.createdLinks[i]); inverseEv.links.push(ev.createdLinks[i]);
} }
} }
ev.node.dirty = true;
ev.node.changed = ev.changed;
} else if (ev.t == "createSubflow") { } else if (ev.t == "createSubflow") {
inverseEv = { inverseEv = {
t: "deleteSubflow", t: "deleteSubflow",

View File

@ -46,6 +46,9 @@ RED.nodes = (function() {
function setDirty(d) { function setDirty(d) {
dirty = d; dirty = d;
if (!d) {
allNodes.clearState()
}
RED.events.emit("workspace:dirty",{dirty:dirty}); RED.events.emit("workspace:dirty",{dirty:dirty});
} }
@ -237,8 +240,16 @@ RED.nodes = (function() {
// allNodes holds information about the Flow nodes. // allNodes holds information about the Flow nodes.
var allNodes = (function() { var allNodes = (function() {
// Map node.id -> node
var nodes = {}; var nodes = {};
// Map tab.id -> Array of nodes on that tab
var tabMap = {}; var tabMap = {};
// Map tab.id -> Set of dirty object ids on that tab
var tabDirtyMap = {};
// Map tab.id -> Set of object ids of things deleted from the tab that weren't otherwise dirty
var tabDeletedNodesMap = {};
// Set of object ids of things added to a tab after initial import
var addedDirtyObjects = new Set()
function changeCollectionDepth(tabNodes, toMove, direction, singleStep) { function changeCollectionDepth(tabNodes, toMove, direction, singleStep) {
const result = [] const result = []
@ -279,17 +290,22 @@ RED.nodes = (function() {
var api = { var api = {
addTab: function(id) { addTab: function(id) {
tabMap[id] = []; tabMap[id] = [];
tabDirtyMap[id] = new Set();
tabDeletedNodesMap[id] = new Set();
}, },
hasTab: function(z) { hasTab: function(z) {
return tabMap.hasOwnProperty(z) return tabMap.hasOwnProperty(z)
}, },
removeTab: function(id) { removeTab: function(id) {
delete tabMap[id]; delete tabMap[id];
delete tabDirtyMap[id];
delete tabDeletedNodesMap[id];
}, },
addNode: function(n) { addNode: function(n) {
nodes[n.id] = n; nodes[n.id] = n;
if (tabMap.hasOwnProperty(n.z)) { if (tabMap.hasOwnProperty(n.z)) {
tabMap[n.z].push(n); tabMap[n.z].push(n);
api.addObjectToWorkspace(n.z, n.id, n.changed || n.moved)
} else { } else {
console.warn("Node added to unknown tab/subflow:",n); console.warn("Node added to unknown tab/subflow:",n);
tabMap["_"] = tabMap["_"] || []; tabMap["_"] = tabMap["_"] || [];
@ -303,8 +319,37 @@ RED.nodes = (function() {
if (i > -1) { if (i > -1) {
tabMap[n.z].splice(i,1); tabMap[n.z].splice(i,1);
} }
api.removeObjectFromWorkspace(n.z, n.id)
} }
}, },
/**
* Add an object to our dirty/clean tracking state
* @param {String} z
* @param {String} id
* @param {Boolean} isDirty
*/
addObjectToWorkspace: function (z, id, isDirty) {
if (isDirty) {
addedDirtyObjects.add(id)
}
if (tabDeletedNodesMap[z].has(id)) {
tabDeletedNodesMap[z].delete(id)
}
api.markNodeDirty(z, id, isDirty)
},
/**
* Remove an object from our dirty/clean tracking state
* @param {String} z
* @param {String} id
*/
removeObjectFromWorkspace: function (z, id) {
if (!addedDirtyObjects.has(id)) {
tabDeletedNodesMap[z].add(id)
} else {
addedDirtyObjects.delete(id)
}
api.markNodeDirty(z, id, false)
},
hasNode: function(id) { hasNode: function(id) {
return nodes.hasOwnProperty(id); return nodes.hasOwnProperty(id);
}, },
@ -371,6 +416,33 @@ RED.nodes = (function() {
clear: function() { clear: function() {
nodes = {}; nodes = {};
tabMap = {}; tabMap = {};
tabDirtyMap = {};
tabDeletedNodesMap = {};
addedDirtyObjects = new Set();
},
/**
* Clear all internal state on what is dirty.
*/
clearState: function () {
// Called when a deploy happens, we can forget about added/remove
// items as they have now been deployed.
addedDirtyObjects = new Set()
const flowsToCheck = new Set()
for (const [z, set] of Object.entries(tabDeletedNodesMap)) {
if (set.size > 0) {
set.clear()
flowsToCheck.add(z)
}
}
for (const [z, set] of Object.entries(tabDirtyMap)) {
if (set.size > 0) {
set.clear()
flowsToCheck.add(z)
}
}
for (const z of flowsToCheck) {
api.checkTabState(z)
}
}, },
eachNode: function(cb) { eachNode: function(cb) {
var nodeList,i,j; var nodeList,i,j;
@ -453,6 +525,36 @@ RED.nodes = (function() {
return orderMap[A.id] - orderMap[B.id]; return orderMap[A.id] - orderMap[B.id];
}) })
} }
},
/**
* Update our records if an object is dirty or not
* @param {String} z tab id
* @param {String} id object id
* @param {Boolean} dirty whether the object is dirty or not
*/
markNodeDirty: function(z, id, dirty) {
if (tabDirtyMap[z]) {
if (dirty) {
tabDirtyMap[z].add(id)
} else {
tabDirtyMap[z].delete(id)
}
api.checkTabState(z)
}
},
/**
* Check if a tab should update its contentsChange flag
* @param {String} z tab id
*/
checkTabState: function (z) {
const ws = workspaces[z]
if (ws) {
const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
if (Boolean(ws.contentsChanged) !== contentsChanged) {
ws.contentsChanged = contentsChanged
RED.events.emit("flows:change", ws);
}
}
} }
} }
return api; return api;
@ -540,6 +642,11 @@ RED.nodes = (function() {
throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`) throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`)
} }
} }
if (node.z && (prop === 'changed' || prop === 'moved')) {
setTimeout(() => {
allNodes.markNodeDirty(node.z, node.id, node.changed || node.moved)
}, 0)
}
node[prop] = value; node[prop] = value;
return true return true
} }
@ -609,10 +716,16 @@ RED.nodes = (function() {
} }
if (l.source.z === l.target.z && linkTabMap[l.source.z]) { if (l.source.z === l.target.z && linkTabMap[l.source.z]) {
linkTabMap[l.source.z].push(l); linkTabMap[l.source.z].push(l);
allNodes.addObjectToWorkspace(l.source.z, getLinkId(l), true)
} }
RED.events.emit("links:add",l); RED.events.emit("links:add",l);
} }
function getLinkId(link) {
return link.source.id + ':' + link.sourcePort + ':' + link.target.id
}
function getNode(id) { function getNode(id) {
if (id in configNodes) { if (id in configNodes) {
return configNodes[id]; return configNodes[id];
@ -807,6 +920,7 @@ RED.nodes = (function() {
if (index !== -1) { if (index !== -1) {
linkTabMap[l.source.z].splice(index,1) linkTabMap[l.source.z].splice(index,1)
} }
allNodes.removeObjectFromWorkspace(l.source.z, getLinkId(l))
} }
} }
RED.events.emit("links:remove",l); RED.events.emit("links:remove",l);
@ -1631,6 +1745,7 @@ RED.nodes = (function() {
* Options: * Options:
* - generateIds - whether to replace all node ids * - generateIds - whether to replace all node ids
* - addFlow - whether to import nodes to a new tab * - addFlow - whether to import nodes to a new tab
* - markChanged - whether to set changed=true on all newly imported objects
* - reimport - if node has a .z property, dont overwrite it * - reimport - if node has a .z property, dont overwrite it
* Only applicible when `generateIds` is false * Only applicible when `generateIds` is false
* - importMap - how to resolve any conflicts. * - importMap - how to resolve any conflicts.
@ -1639,7 +1754,7 @@ RED.nodes = (function() {
* - id:replace - import over the top of existing * - id:replace - import over the top of existing
*/ */
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} } const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
options = Object.assign({}, defOpts, options) options = Object.assign({}, defOpts, options)
options.importMap = options.importMap || {} options.importMap = options.importMap || {}
const createNewIds = options.generateIds; const createNewIds = options.generateIds;
@ -1665,7 +1780,7 @@ RED.nodes = (function() {
newNodes = newNodesObj; newNodes = newNodesObj;
} }
if (!$.isArray(newNodes)) { if (!Array.isArray(newNodes)) {
newNodes = [newNodes]; newNodes = [newNodes];
} }
@ -1963,6 +2078,9 @@ RED.nodes = (function() {
if (!n.z) { if (!n.z) {
delete configNode.z; delete configNode.z;
} }
if (options.markChanged) {
configNode.changed = true
}
if (n.hasOwnProperty('d')) { if (n.hasOwnProperty('d')) {
configNode.d = n.d; configNode.d = n.d;
} }
@ -2025,6 +2143,9 @@ RED.nodes = (function() {
if (n.hasOwnProperty('g')) { if (n.hasOwnProperty('g')) {
node.g = n.g; node.g = n.g;
} }
if (options.markChanged) {
node.changed = true
}
if (createNewIds || options.importMap[n.id] === "copy") { if (createNewIds || options.importMap[n.id] === "copy") {
if (subflow_denylist[n.z]) { if (subflow_denylist[n.z]) {
continue; continue;
@ -2245,7 +2366,7 @@ RED.nodes = (function() {
// get added // get added
if (activeSubflow && /^link /.test(n.type) && n.links) { if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) { n.links = n.links.filter(function(id) {
var otherNode = RED.nodes.node(id); const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace) return (otherNode && otherNode.z === activeWorkspace)
}); });
} }
@ -2538,6 +2659,7 @@ RED.nodes = (function() {
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;
allNodes.addObjectToWorkspace(group.z, group.id, group.changed || group.moved)
RED.events.emit("groups:add",group); RED.events.emit("groups:add",group);
return group return group
} }
@ -2554,7 +2676,7 @@ RED.nodes = (function() {
} }
} }
RED.group.markDirty(group); RED.group.markDirty(group);
allNodes.removeObjectFromWorkspace(group.z, group.id)
delete groups[group.id]; delete groups[group.id];
RED.events.emit("groups:remove",group); RED.events.emit("groups:remove",group);
} }
@ -2573,6 +2695,7 @@ RED.nodes = (function() {
if (!nodeLinks[junction.id]) { if (!nodeLinks[junction.id]) {
nodeLinks[junction.id] = {in:[],out:[]}; nodeLinks[junction.id] = {in:[],out:[]};
} }
allNodes.addObjectToWorkspace(junction.z, junction.id, junction.changed || junction.moved)
RED.events.emit("junctions:add", junction) RED.events.emit("junctions:add", junction)
return junction return junction
} }
@ -2584,6 +2707,7 @@ RED.nodes = (function() {
} }
delete junctions[junction.id] delete junctions[junction.id]
delete nodeLinks[junction.id]; delete nodeLinks[junction.id];
allNodes.removeObjectFromWorkspace(junction.z, junction.id)
RED.events.emit("junctions:remove", junction) RED.events.emit("junctions:remove", junction)
var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); }); var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); });
@ -2821,6 +2945,9 @@ RED.nodes = (function() {
RED.view.redraw(true); RED.view.redraw(true);
} }
}); });
RED.events.on('deploy', function () {
allNodes.clearState()
})
}, },
registry:registry, registry:registry,
setNodeList: registry.setNodeList, setNodeList: registry.setNodeList,
@ -2923,6 +3050,20 @@ RED.nodes = (function() {
} }
} }
}, },
eachGroup: function(cb) {
for (var group of Object.values(groups)) {
if (cb(group) === false) {
break
}
}
},
eachJunction: function(cb) {
for (var junction of Object.values(junctions)) {
if (cb(junction) === false) {
break
}
}
},
node: getNode, node: getNode,

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 || RED.workspaces.isActiveLocked()) { if (RED.workspaces.active() === 0 || RED.workspaces.isLocked()) {
$("#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 {
@ -1278,7 +1278,7 @@ 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 (!RED.workspaces.isActiveLocked() && ( if (!RED.workspaces.isLocked() && (
$.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 || $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
$.inArray("Files",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();
@ -1288,7 +1288,7 @@ RED.clipboard = (function() {
$('#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()) { RED.workspaces.isLocked()) {
event.preventDefault(); event.preventDefault();
} }
}) })
@ -1296,7 +1296,7 @@ RED.clipboard = (function() {
hideDropTarget(); hideDropTarget();
}) })
.on("drop",function(event) { .on("drop",function(event) {
if (!RED.workspaces.isActiveLocked()) { if (!RED.workspaces.isLocked()) {
try { try {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
var data = event.originalEvent.dataTransfer.getData("text/plain"); var data = event.originalEvent.dataTransfer.getData("text/plain");

View File

@ -417,6 +417,9 @@
} else { } else {
return null; return null;
} }
},
cancel: function() {
this.element.sortable("cancel");
} }
}); });
})(jQuery); })(jQuery);

View File

@ -845,7 +845,6 @@ RED.tabs = (function() {
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li); var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
if (options.onselect) { if (options.onselect) {
$('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
$('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges); $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
} }

View File

@ -28,7 +28,7 @@ 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 canEdit = !RED.workspaces.isLocked()
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 isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0 const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
@ -79,7 +79,8 @@ RED.contextMenu = (function () {
w: 0, h: 0, w: 0, h: 0,
outputs: 1, outputs: 1,
inputs: 1, inputs: 1,
dirty: true dirty: true,
moved: true
} }
const historyEvent = { const historyEvent = {
dirty: RED.nodes.dirty(), dirty: RED.nodes.dirty(),

View File

@ -557,12 +557,17 @@ RED.deploy = (function() {
} else { } else {
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success"); RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
} }
RED.nodes.eachNode(function (node) { const flowsToLock = new Set()
const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null); function ensureUnlocked(id) {
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false; const isLocked = flow ? flow.locked : false;
if (flow && isLocked) { if (flow && isLocked) {
flow.locked = false; flow.locked = false;
flowsToLock.add(flow)
} }
}
RED.nodes.eachNode(function (node) {
ensureUnlocked(node.z)
if (node.changed) { if (node.changed) {
node.dirty = true; node.dirty = true;
node.changed = false; node.changed = false;
@ -574,11 +579,33 @@ RED.deploy = (function() {
if (node.credentials) { if (node.credentials) {
delete node.credentials; delete node.credentials;
} }
if (flow && isLocked) {
flow.locked = isLocked;
}
}); });
RED.nodes.eachGroup(function (node) {
ensureUnlocked(node.z)
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
})
RED.nodes.eachJunction(function (node) {
ensureUnlocked(node.z)
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
})
RED.nodes.eachConfig(function (confNode) { RED.nodes.eachConfig(function (confNode) {
if (confNode.z) {
ensureUnlocked(confNode.z)
}
confNode.changed = false; confNode.changed = false;
if (confNode.credentials) { if (confNode.credentials) {
delete confNode.credentials; delete confNode.credentials;
@ -588,8 +615,16 @@ RED.deploy = (function() {
subflow.changed = false; subflow.changed = false;
}); });
RED.nodes.eachWorkspace(function (ws) { RED.nodes.eachWorkspace(function (ws) {
ws.changed = false; if (ws.changed || ws.added) {
ensureUnlocked(ws.z)
ws.changed = false;
delete ws.added
RED.events.emit("flows:change", ws)
}
}); });
flowsToLock.forEach(flow => {
flow.locked = true
})
// Once deployed, cannot undo back to a clean state // Once deployed, cannot undo back to a clean state
RED.history.markAllDirty(); RED.history.markAllDirty();
RED.view.redraw(); RED.view.redraw();

View File

@ -860,6 +860,7 @@ RED.editor = (function() {
function showEditDialog(node, defaultTab) { function showEditDialog(node, defaultTab) {
if (buildingEditDialog) { return } if (buildingEditDialog) { return }
buildingEditDialog = true; buildingEditDialog = true;
if (node.z && RED.workspaces.isLocked(node.z)) { return }
var editing_node = node; var editing_node = node;
var removeInfoEditorOnClose = false; var removeInfoEditorOnClose = false;
var skipInfoRefreshOnClose = false; var skipInfoRefreshOnClose = false;
@ -1155,6 +1156,8 @@ RED.editor = (function() {
var editing_config_node = RED.nodes.node(id); var editing_config_node = RED.nodes.node(id);
var activeEditPanes = []; var activeEditPanes = [];
if (editing_config_node && editing_config_node.z && RED.workspaces.isLocked(editing_config_node.z)) { return }
var configNodeScope = ""; // default to global var configNodeScope = ""; // default to global
var activeSubflow = RED.nodes.subflow(RED.workspaces.active()); var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow) { if (activeSubflow) {
@ -1708,6 +1711,7 @@ RED.editor = (function() {
function showEditGroupDialog(group, defaultTab) { function showEditGroupDialog(group, defaultTab) {
if (buildingEditDialog) { return } if (buildingEditDialog) { return }
buildingEditDialog = true; buildingEditDialog = true;
if (group.z && RED.workspaces.isLocked(group.z)) { return }
var editing_node = group; var editing_node = group;
editStack.push(group); editStack.push(group);
RED.view.state(RED.state.EDITING); RED.view.state(RED.state.EDITING);

View File

@ -166,6 +166,10 @@ RED.envVar = (function() {
} }
} }
}); });
RED.actions.add("core:show-global-env", function() {
RED.userSettings.show('envvar');
});
} }
return { return {

View File

@ -188,7 +188,7 @@ 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() var locked = RED.workspaces.isLocked()
if (activateGroup) { if (activateGroup) {
singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group'; singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
@ -266,7 +266,7 @@ RED.group = (function() {
} }
} }
function pasteGroupStyle() { function pasteGroupStyle() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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();
@ -301,7 +301,7 @@ RED.group = (function() {
} }
function groupSelection() { function groupSelection() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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) {
@ -320,7 +320,7 @@ RED.group = (function() {
} }
} }
function ungroupSelection() { function ungroupSelection() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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) {
@ -344,7 +344,7 @@ RED.group = (function() {
} }
function ungroup(g) { function ungroup(g) {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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) {
@ -371,7 +371,7 @@ RED.group = (function() {
} }
function mergeSelection() { function mergeSelection() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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) {
@ -441,7 +441,7 @@ RED.group = (function() {
} }
function removeSelection() { function removeSelection() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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) {
@ -469,7 +469,7 @@ RED.group = (function() {
} }
} }
function createGroup(nodes) { function createGroup(nodes) {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { return }
if (nodes.length === 0) { if (nodes.length === 0) {
return; return;
} }
@ -496,7 +496,8 @@ RED.group = (function() {
y: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY,
w: 0, w: 0,
h: 0, h: 0,
_def: RED.group.def _def: RED.group.def,
changed: true
} }
group.z = nodes[0].z; group.z = nodes[0].z;
@ -583,7 +584,7 @@ RED.group = (function() {
markDirty(group); markDirty(group);
} }
function removeFromGroup(group, nodes, reparent) { function removeFromGroup(group, nodes, reparent) {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { return }
if (!Array.isArray(nodes)) { if (!Array.isArray(nodes)) {
nodes = [nodes]; nodes = [nodes];
} }

View File

@ -573,7 +573,7 @@ RED.subflow = (function() {
} }
}); });
RED.events.on("view:selection-changed",function(selection) { RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes || RED.workspaces.isActiveLocked()) { if (!selection.nodes || RED.workspaces.isLocked()) {
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);
@ -636,7 +636,7 @@ RED.subflow = (function() {
} }
function convertToSubflow() { function convertToSubflow() {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();

View File

@ -145,17 +145,19 @@ RED.sidebar.config = (function() {
} else { } else {
var currentType = ""; var currentType = "";
nodes.forEach(function(node) { nodes.forEach(function(node) {
var label = RED.utils.getNodeLabel(node,node.id); var labelText = RED.utils.getNodeLabel(node,node.id);
if (node.type != currentType) { if (node.type != currentType) {
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list); $('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
currentType = node.type; currentType = node.type;
} }
if (node.changed) {
labelText += "!!"
}
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list); var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry); var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
entry.data('node',node.id); entry.data('node',node.id);
nodeDiv.data('node',node.id); nodeDiv.data('node',node.id);
var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv); var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
if (node.d) { if (node.d) {
nodeDiv.addClass("red-ui-palette-node-config-disabled"); nodeDiv.addClass("red-ui-palette-node-config-disabled");
$('<i class="fa fa-ban"></i>').prependTo(label); $('<i class="fa fa-ban"></i>').prependTo(label);

View File

@ -39,7 +39,7 @@ RED.view.tools = (function() {
} }
function alignToGrid() { function alignToGrid() {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -90,7 +90,7 @@ RED.view.tools = (function() {
} }
function moveSelection(dx,dy) { function moveSelection(dx,dy) {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
if (moving_set === null) { if (moving_set === null) {
@ -159,7 +159,7 @@ RED.view.tools = (function() {
} }
function setSelectedNodeLabelState(labelShown) { function setSelectedNodeLabelState(labelShown) {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -448,9 +448,9 @@ RED.view.tools = (function() {
} }
function alignSelectionToEdge(direction) { function alignSelectionToEdge(direction) {
// if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
// return 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) {
@ -552,7 +552,7 @@ RED.view.tools = (function() {
} }
function distributeSelection(direction) { function distributeSelection(direction) {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -713,7 +713,7 @@ RED.view.tools = (function() {
} }
function reorderSelection(dir) { function reorderSelection(dir) {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -751,7 +751,7 @@ RED.view.tools = (function() {
} }
function wireSeriesOfNodes() { function wireSeriesOfNodes() {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -794,7 +794,7 @@ RED.view.tools = (function() {
} }
function wireNodeToMultiple() { function wireNodeToMultiple() {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -840,7 +840,7 @@ RED.view.tools = (function() {
} }
function wireMultipleToNode() { function wireMultipleToNode() {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return return
} }
var selection = RED.view.selection(); var selection = RED.view.selection();
@ -902,7 +902,7 @@ 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()) { if (RED.workspaces.isLocked()) {
return 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));
@ -1073,7 +1073,7 @@ 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()) { if (RED.workspaces.isLocked()) {
return return
} }
options = Object.assign({ options = Object.assign({
@ -1146,7 +1146,7 @@ RED.view.tools = (function() {
} }
function addJunctionsToWires(wires) { function addJunctionsToWires(wires) {
if (RED.workspaces.isActiveLocked()) { if (RED.workspaces.isLocked()) {
return 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));
@ -1190,7 +1190,8 @@ RED.view.tools = (function() {
w: 0, h: 0, w: 0, h: 0,
outputs: 1, outputs: 1,
inputs: 1, inputs: 1,
dirty: true dirty: true,
moved: true
} }
links = links.filter(function(l) { return !removedLinks.has(l) }) links = links.filter(function(l) { return !removedLinks.has(l) })
if (links.length === 0) { if (links.length === 0) {

View File

@ -697,7 +697,7 @@ 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()) { if (RED.workspaces.isLocked()) {
return return
} }
importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'}); importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
@ -1279,7 +1279,8 @@ RED.view = (function() {
w: 0, h: 0, w: 0, h: 0,
outputs: 1, outputs: 1,
inputs: 1, inputs: 1,
dirty: true dirty: true,
moved: true
} }
historyEvent = { historyEvent = {
t:'add', t:'add',
@ -2454,7 +2455,7 @@ RED.view = (function() {
} }
function editSelection() { function editSelection() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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") {
@ -2788,7 +2789,7 @@ RED.view = (function() {
function detachSelectedNodes() { function detachSelectedNodes() {
if (RED.workspaces.isActiveLocked()) { return } if (RED.workspaces.isLocked()) { 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);
@ -3213,7 +3214,7 @@ RED.view = (function() {
console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err); console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err);
result = null; result = null;
} }
} else if ($.isArray(portLabels)) { } else if (Array.isArray(portLabels)) {
result = portLabels[portIndex]; result = portLabels[portIndex];
} }
return result; return result;
@ -3371,7 +3372,7 @@ 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()) { if (RED.workspaces.isLocked()) {
clickElapsed = 0; clickElapsed = 0;
d3.event.stopPropagation(); d3.event.stopPropagation();
return return
@ -3771,7 +3772,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 (RED.workspaces.isLocked()) { 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);
@ -3967,7 +3968,7 @@ RED.view = (function() {
function showTouchMenu(obj,pos) { function showTouchMenu(obj,pos) {
var mdn = mousedown_node; var mdn = mousedown_node;
var options = []; var options = [];
const isActiveLocked = RED.workspaces.isActiveLocked() const isActiveLocked = RED.workspaces.isLocked()
options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
@ -5452,7 +5453,7 @@ RED.view = (function() {
if (mouse_mode === RED.state.SELECTING_NODE) { if (mouse_mode === RED.state.SELECTING_NODE) {
return; return;
} }
const wasDirty = RED.nodes.dirty()
var nodesToImport; var nodesToImport;
if (typeof newNodesObj === "string") { if (typeof newNodesObj === "string") {
if (newNodesObj === "") { if (newNodesObj === "") {
@ -5469,7 +5470,7 @@ RED.view = (function() {
nodesToImport = newNodesObj; nodesToImport = newNodesObj;
} }
if (!$.isArray(nodesToImport)) { if (!Array.isArray(nodesToImport)) {
nodesToImport = [nodesToImport]; nodesToImport = [nodesToImport];
} }
if (options.generateDefaultNames) { if (options.generateDefaultNames) {
@ -5502,7 +5503,12 @@ RED.view = (function() {
return (n.type === "global-config"); return (n.type === "global-config");
}); });
} }
var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap}); var result = RED.nodes.import(filteredNodesToImport,{
generateIds: options.generateIds,
addFlow: addNewFlow,
importMap: options.importMap,
markChanged: true
});
if (result) { if (result) {
var new_nodes = result.nodes; var new_nodes = result.nodes;
var new_links = result.links; var new_links = result.links;
@ -5518,7 +5524,7 @@ RED.view = (function() {
var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()}))
var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); var new_node_ids = new_nodes.map(function(n){ return n.id; });
clearSelection(); clearSelection();
movingSet.clear(); movingSet.clear();
@ -5589,14 +5595,14 @@ RED.view = (function() {
} }
var historyEvent = { var historyEvent = {
t:"add", t: "add",
nodes:new_node_ids, nodes: new_node_ids,
links:new_links, links: new_links,
groups:new_groups, groups: new_groups,
junctions: new_junctions, junctions: new_junctions,
workspaces:new_workspaces, workspaces: new_workspaces,
subflows:new_subflows, subflows: new_subflows,
dirty:RED.nodes.dirty() dirty: wasDirty
}; };
if (movingSet.length() === 0) { if (movingSet.length() === 0) {
RED.nodes.dirty(true); RED.nodes.dirty(true);
@ -5605,7 +5611,7 @@ RED.view = (function() {
var subflowRefresh = RED.subflow.refresh(true); var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) { if (subflowRefresh) {
historyEvent.subflow = { historyEvent.subflow = {
id:activeSubflow.id, id: activeSubflow.id,
changed: activeSubflowChanged, changed: activeSubflowChanged,
instances: subflowRefresh.instances instances: subflowRefresh.instances
} }

View File

@ -82,8 +82,11 @@ RED.workspaces = (function() {
info: "", info: "",
label: RED._('workspace.defaultName',{number:workspaceIndex}), label: RED._('workspace.defaultName',{number:workspaceIndex}),
env: [], env: [],
hideable: true hideable: true,
}; };
if (!skipHistoryEntry) {
ws.added = true
}
RED.nodes.addWorkspace(ws,targetIndex); RED.nodes.addWorkspace(ws,targetIndex);
workspace_tabs.addTab(ws,targetIndex); workspace_tabs.addTab(ws,targetIndex);
@ -93,8 +96,7 @@ RED.workspaces = (function() {
RED.nodes.dirty(true); RED.nodes.dirty(true);
} }
} }
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label) $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
RED.view.focus(); RED.view.focus();
return ws; return ws;
} }
@ -160,7 +162,7 @@ RED.workspaces = (function() {
} }
}); });
let isCurrentLocked = RED.workspaces.isActiveLocked() let isCurrentLocked = RED.workspaces.isLocked()
if (tab) { if (tab) {
isCurrentLocked = tab.locked isCurrentLocked = tab.locked
} }
@ -375,6 +377,12 @@ RED.workspaces = (function() {
$("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked'); $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked');
} }
const changeBadgeContainer = $('<svg class="red-ui-flow-tab-changed red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo("#red-ui-tab-"+(tab.id.replace(".","-")))
const changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle");
changeBadge.setAttribute("cx",5);
changeBadge.setAttribute("cy",5);
changeBadge.setAttribute("r",5);
changeBadgeContainer.append(changeBadge)
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
if (workspaceTabCount === 1) { if (workspaceTabCount === 1) {
@ -637,6 +645,11 @@ RED.workspaces = (function() {
RED.workspaces.show(viewStack[++viewStackPos],true); RED.workspaces.show(viewStack[++viewStackPos],true);
} }
}) })
RED.events.on("flows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
})
hideWorkspace(); hideWorkspace();
} }
@ -833,8 +846,9 @@ RED.workspaces = (function() {
active: function() { active: function() {
return activeWorkspace return activeWorkspace
}, },
isActiveLocked: function() { isLocked: function(id) {
var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace) id = id || activeWorkspace
var ws = RED.nodes.workspace(id) || RED.nodes.subflow(id)
return ws && ws.locked return ws && ws.locked
}, },
selection: function() { selection: function() {

View File

@ -105,6 +105,15 @@
} }
} }
} }
.red-ui-tab:not(.red-ui-workspace-changed) .red-ui-flow-tab-changed {
display: none;
}
.red-ui-tab.red-ui-workspace-changed .red-ui-flow-tab-changed {
display: inline-block;
position: absolute;
top: 1px;
right: 1px;
}
.red-ui-workspace-locked-icon { .red-ui-workspace-locked-icon {
display: none; display: none;

View File

@ -478,7 +478,7 @@ module.exports = function(RED) {
var completeSend = function(partId) { var completeSend = function(partId) {
var group = inflight[partId]; var group = inflight[partId];
if (group.timeout) { clearTimeout(group.timeout); } if (group.timeout) { clearTimeout(group.timeout); }
if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; } if (node.mode === 'auto' || node.accumulate !== true || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
if (group.type === 'array' && group.arrayLen > 1) { if (group.type === 'array' && group.arrayLen > 1) {
var newArray = []; var newArray = [];
group.payload.forEach(function(n) { group.payload.forEach(function(n) {