From 4339016bac19bb0203a56c0afe9fd69ee58a1d1e Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:29:24 +0100 Subject: [PATCH 1/6] Refactor `RED.history` to add docs + prettier --- .../@node-red/editor-client/src/js/history.js | 1667 ++++++++++------- 1 file changed, 979 insertions(+), 688 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 2fa4e4427..df29b354e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -18,17 +18,18 @@ * An API for undo / redo history buffer * @namespace RED.history */ -RED.history = (function() { - var undoHistory = []; - var redoHistory = []; +RED.history = (function () { + let undoHistory = []; + let redoHistory = []; function nodeOrJunction(id) { - var node = RED.nodes.node(id); + const node = RED.nodes.node(id); if (node) { return node; } return RED.nodes.junction(id); } + function ensureUnlocked(id, flowsToLock) { const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null); const isLocked = flow ? flow.locked : false; @@ -37,693 +38,980 @@ RED.history = (function() { flowsToLock.add(flow) } } - function undoEvent(ev) { - var i; - var len; - var node; - var group; - var subflow; - var modifiedTabs = {}; - var inverseEv; - if (ev) { - if (ev.t == 'multi') { - inverseEv = { - t: 'multi', - events: [] - }; - len = ev.events.length; - for (i=len-1;i>=0;i--) { - var r = undoEvent(ev.events[i]); - inverseEv.events.push(r); - } - } else if (ev.t == 'replace') { - if (ev.complete) { - // This is a replace of everything. We can short-cut - // the logic by clearing everyting first, then importing - // the ev.config. - // Used by RED.diff.mergeDiff - inverseEv = { - t: 'replace', - config: RED.nodes.createCompleteNodeSet(), - changed: {}, - moved: {}, - complete: true, - rev: RED.nodes.version(), - dirty: RED.nodes.dirty() - }; - var selectedTab = RED.workspaces.active(); - inverseEv.config.forEach(n => { - const node = RED.nodes.node(n.id) - if (node) { - inverseEv.changed[n.id] = node.changed - inverseEv.moved[n.id] = node.moved - } - }) - RED.nodes.clear(); - var imported = RED.nodes.import(ev.config); - // Clear all change flags from the import - RED.nodes.dirty(false); - const flowsToLock = new Set() - - imported.nodes.forEach(function(n) { - if (ev.changed[n.id]) { - ensureUnlocked(n.z, flowsToLock) - n.changed = true; - } - if (ev.moved[n.id]) { - ensureUnlocked(n.z, flowsToLock) - n.moved = true; - } - }) - flowsToLock.forEach(flow => { - flow.locked = true - }) + /** + * @typedef {"add"|"delete"|"edit"|"move"|"multi"|"remove"|"reorder"|"replace"| + * "createSubflow"|"deleteSubflow"|"addToGroup"|"createGroup"|"ungroup"|"removeFromGroup"} HistoryType + * @typedef {{ t: HistoryType; dirty: boolean; callback?: (ev: HistoryEvent) => void; } & {}} HistoryEvent + * + * @typedef {{ source: Node; sourcePort: number; target: Node; }} Link + * @typedef {Node & {}} Group + * @typedef {Node & {}} Junction + * @typedef {Node & { instances: Array; }} Subflow + * @typedef {Node & {}} Workspace + */ - RED.nodes.version(ev.rev); - RED.view.redraw(true); - RED.palette.refresh(); - RED.workspaces.refresh(); - RED.workspaces.show(selectedTab, true); - RED.sidebar.config.refresh(); - } else { - var importMap = {}; - ev.config.forEach(function(n) { - importMap[n.id] = "replace"; - }) - var importedResult = RED.nodes.import(ev.config,{importMap: importMap}) - inverseEv = { - t: 'replace', - config: importedResult.removedNodes, - dirty: RED.nodes.dirty() - } - } - } else if (ev.t == 'add') { - inverseEv = { - t: "delete", - dirty: RED.nodes.dirty() - }; - if (ev.nodes) { - inverseEv.nodes = []; - for (i=0;i} [ev.groups] + * @param {Array} [ev.junctions] + * @param {Array} [ev.links] + * @param {Array} [ev.nodes] + * @param {Array} [ev.removedLinks] + * @param {Subflow} [ev.subflow] + * @param {Array} [ev.subflows] + * @param {Array} [ev.workspaces] + * @param {object} [modifiedTabs] + * @returns {HistoryEvent} The generated history event to redo + */ + function onAdd(ev, modifiedTabs) { + const inverseEv = { + t: "delete", + dirty: RED.nodes.dirty() + }; + if (ev.nodes) { + inverseEv.nodes = []; + for (let i = 0; i < ev.nodes.length; i++) { + const node = RED.nodes.node(ev.nodes[i]); + if (node.z) { + modifiedTabs[node.z] = true; + } + inverseEv.nodes.push(node); + RED.nodes.remove(ev.nodes[i]); + if (node.g) { + const group = RED.nodes.group(node.g); + const index = group.nodes.indexOf(node); + if (index !== -1) { + group.nodes.splice(index, 1); + RED.group.markDirty(group); + } + } + } + } - } - } - if (ev.groups) { - inverseEv.groups = []; - for (i = ev.groups.length - 1;i>=0;i--) { - group = ev.groups[i]; - modifiedTabs[group.z] = true; - // The order of groups is important - // - to invert the action, the order is reversed - inverseEv.groups.unshift(group); - RED.nodes.removeGroup(group); - } - } - if (ev.workspaces) { - inverseEv.workspaces = []; - for (i=0;i= 0; i--) { + const group = ev.groups[i]; + modifiedTabs[group.z] = true; + // The order of groups is important + // - to invert the action, the order is reversed + inverseEv.groups.unshift(group); + RED.nodes.removeGroup(group); + } + } + + if (ev.workspaces) { + inverseEv.workspaces = []; + for (let i = 0; i < ev.workspaces.length; i++) { + const workspaceOrder = RED.nodes.getWorkspaceOrder(); + ev.workspaces[i]._index = workspaceOrder.indexOf(ev.workspaces[i].id); + inverseEv.workspaces.push(ev.workspaces[i]); + RED.nodes.removeWorkspace(ev.workspaces[i].id); + RED.workspaces.remove(ev.workspaces[i]); + } + } + + if (ev.subflows) { + inverseEv.subflows = []; + for (let i = 0; i < ev.subflows.length; i++) { + inverseEv.subflows.push(ev.subflows[i]); + RED.nodes.removeSubflow(ev.subflows[i]); + RED.workspaces.remove(ev.subflows[i]); + } + } + + if (ev.subflow) { + inverseEv.subflow = {}; + if (ev.subflow.instances) { + inverseEv.subflow.instances = []; + ev.subflow.instances.forEach(function (n) { + inverseEv.subflow.instances.push(n); + const node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; } - } - if (ev.subflowInputs && ev.subflowInputs.length > 0) { - subflow = RED.nodes.subflow(ev.subflowInputs[0].z); - subflow.in.push(ev.subflowInputs[0]); - subflow.in[0].dirty = true; - } - if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { - subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); - ev.subflowOutputs.sort(function(a,b) { return a.i-b.i}); - for (i=0;i= output.i) { - l.sourcePort++; - } - } - }); - } - } - if (ev.subflow) { - inverseEv.subflow = {}; - if (ev.subflow.hasOwnProperty('instances')) { - inverseEv.subflow.instances = []; - ev.subflow.instances.forEach(function(n) { - inverseEv.subflow.instances.push(n); - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - if (ev.subflow.hasOwnProperty('status')) { - subflow = RED.nodes.subflow(ev.subflow.id); - subflow.status = ev.subflow.status; - } - } + }); + } + + if (ev.subflow.hasOwnProperty("changed")) { + const subflow = RED.nodes.subflow(ev.subflow.id); if (subflow) { - RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { - n.inputs = subflow.in.length; - n.outputs = subflow.out.length; - n.resize = true; - n.dirty = true; + subflow.changed = ev.subflow.changed; + } + } + } + + if (ev.removedLinks) { + inverseEv.createdLinks = []; + for (let i = 0; i < ev.removedLinks.length; i++) { + inverseEv.createdLinks.push(ev.removedLinks[i]); + RED.nodes.addLink(ev.removedLinks[i]); + } + } + + return inverseEv; + } + + /** + * Called on the `delete` history event + * @param {object} ev The history event to undo + * @param {"delete"} ev.t The history event type + * @param {Record} [ev.changes] + * @param {Array} [ev.createdLinks] + * @param {boolean} [ev.dirty] + * @param {Array} [ev.groups] + * @param {Array} [ev.junctions] + * @param {Array} [ev.links] + * @param {Array} [ev.nodes] + * @param {Subflow} [ev.subflow] + * @param {Array} [ev.subflowInputs] + * @param {Array} [ev.subflowOutputs] + * @param {Array} [ev.subflows] + * @param {Array} [ev.workspaces] + * @param {object} [modifiedTabs] + * @returns {HistoryEvent} The generated history event to redo + */ + function onDelete(ev, modifiedTabs) { + // TODO: check why global + let subflow; + const inverseEv = { + t: "add", + dirty: RED.nodes.dirty() + }; + + if (ev.workspaces) { + inverseEv.workspaces = []; + for (let i = 0; i < ev.workspaces.length; i++) { + inverseEv.workspaces.push(ev.workspaces[i]); + RED.nodes.addWorkspace(ev.workspaces[i], ev.workspaces[i]._index); + RED.workspaces.add(ev.workspaces[i], undefined, ev.workspaces[i]._index); + delete ev.workspaces[i]._index; + } + } + + if (ev.subflows) { + inverseEv.subflows = []; + for (let i = 0; i < ev.subflows.length; i++) { + inverseEv.subflows.push(ev.subflows[i]); + RED.nodes.addSubflow(ev.subflows[i]); + } + } + + if (ev.subflowInputs && ev.subflowInputs.length > 0) { + subflow = RED.nodes.subflow(ev.subflowInputs[0].z); + subflow.in.push(ev.subflowInputs[0]); + subflow.in[0].dirty = true; + } + + if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { + subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); + ev.subflowOutputs.sort(function (a, b) { return a.i - b.i }); + for (let i = 0; i < ev.subflowOutputs.length; i++) { + const output = ev.subflowOutputs[i]; + subflow.out.splice(output.i, 0, output); + for (let j = output.i + 1; j < subflow.out.length; j++) { + subflow.out[j].i++; + subflow.out[j].dirty = true; + } + RED.nodes.eachLink(function (l) { + if (l.source.type == "subflow:" + subflow.id) { + if (l.sourcePort >= output.i) { + l.sourcePort++; + } + } + }); + } + } + + if (ev.subflow) { + inverseEv.subflow = {}; + if (ev.subflow.hasOwnProperty("instances")) { + inverseEv.subflow.instances = []; + ev.subflow.instances.forEach(function (n) { + inverseEv.subflow.instances.push(n); + const node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + + if (ev.subflow.hasOwnProperty("status")) { + const subflow = RED.nodes.subflow(ev.subflow.id); + subflow.status = ev.subflow.status; + } + } + + if (subflow) { + RED.nodes.filterNodes({ type: "subflow:" + subflow.id }).forEach(function (n) { + n.inputs = subflow.in.length; + n.outputs = subflow.out.length; + n.resize = true; + n.dirty = true; + }); + } + + if (ev.groups) { + inverseEv.groups = []; + const groupsToAdd = {}; + ev.groups.forEach(function (g) { groupsToAdd[g.id] = g; }); + for (let i = ev.groups.length - 1; i >= 0; i--) { + RED.nodes.addGroup(ev.groups[i]) + modifiedTabs[ev.groups[i].z] = true; + // The order of groups is important + // - to invert the action, the order is reversed + inverseEv.groups.unshift(ev.groups[i]); + if (ev.groups[i].g) { + let group; + if (!groupsToAdd[ev.groups[i].g]) { + group = RED.nodes.group(ev.groups[i].g); + } else { + group = groupsToAdd[ev.groups[i].g]; + } + if (group.nodes.indexOf(ev.groups[i]) === -1) { + group.nodes.push(ev.groups[i]); + } + RED.group.markDirty(ev.groups[i]) + } + } + } + + if (ev.nodes) { + inverseEv.nodes = []; + for (let i = 0; i < ev.nodes.length; i++) { + RED.nodes.add(ev.nodes[i]); + modifiedTabs[ev.nodes[i].z] = true; + inverseEv.nodes.push(ev.nodes[i].id); + if (ev.nodes[i].g) { + const group = RED.nodes.group(ev.nodes[i].g); + if (group.nodes.indexOf(ev.nodes[i]) === -1) { + group.nodes.push(ev.nodes[i]); + } + RED.group.markDirty(group) + } + } + } + if (ev.junctions) { + inverseEv.junctions = []; + for (let i = 0; i < ev.junctions.length; i++) { + inverseEv.junctions.push(ev.junctions[i]); + RED.nodes.addJunction(ev.junctions[i]); + if (ev.junctions[i].g) { + const group = RED.nodes.group(ev.junctions[i].g); + if (group.nodes.indexOf(ev.junctions[i]) === -1) { + group.nodes.push(ev.junctions[i]); + } + RED.group.markDirty(group); + } + } + } + + if (ev.links) { + inverseEv.links = []; + for (let i = 0; i < ev.links.length; i++) { + RED.nodes.addLink(ev.links[i]); + inverseEv.links.push(ev.links[i]); + } + } + + if (ev.createdLinks) { + inverseEv.removedLinks = []; + for (let i = 0; i < ev.createdLinks.length; i++) { + inverseEv.removedLinks.push(ev.createdLinks[i]); + RED.nodes.removeLink(ev.createdLinks[i]); + } + } + + if (ev.changes) { + for (const i in ev.changes) { + if (ev.changes.hasOwnProperty(i)) { + const node = RED.nodes.node(i); + if (node) { + for (const d in ev.changes[i]) { + if (ev.changes[i].hasOwnProperty(d)) { + node[d] = ev.changes[i][d]; + } + } + node.dirty = true; + } + RED.events.emit("nodes:change", node); + } + } + } + + if (subflow) { + RED.events.emit("subflows:change", subflow); + } + + return inverseEv; + } + + /** + * Called on the `move` history event + * @param {object} ev The history event to undo + * @param {"move"} ev.t The history event type + * @param {Group} [ev.addToGroup] + * @param {Array} [ev.links] + * @param {Array<{ n: Group; ox: number; oy: number; dx: number; dy: number; }>} ev.nodes + * @param {Array} [ev.removedLinks] + * @param {Group} [ev.removeFromGroup] + * @returns {HistoryEvent} The generated history event to redo + */ + function onMove(ev) { + const inverseEv = { + t: "move", + nodes: [], + dirty: RED.nodes.dirty() + }; + + for (let i = 0; i < ev.nodes.length; i++) { + const n = ev.nodes[i]; + const rn = { n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.n.moved }; + inverseEv.nodes.push(rn); + n.n.x = n.ox; + n.n.y = n.oy; + n.n.dirty = true; + n.n.moved = n.moved; + } + + // A move could have caused a link splice + if (ev.links) { + inverseEv.removedLinks = []; + for (let i = 0; i < ev.links.length; i++) { + inverseEv.removedLinks.push(ev.links[i]); + RED.nodes.removeLink(ev.links[i]); + } + } + + if (ev.removedLinks) { + inverseEv.links = []; + for (let i = 0; i < ev.removedLinks.length; i++) { + inverseEv.links.push(ev.removedLinks[i]); + RED.nodes.addLink(ev.removedLinks[i]); + } + } + + if (ev.addToGroup) { + RED.group.removeFromGroup(ev.addToGroup, ev.nodes.map(function (n) { return n.n }), false); + inverseEv.removeFromGroup = ev.addToGroup; + } + + if (ev.removeFromGroup) { + RED.group.addToGroup(ev.removeFromGroup, ev.nodes.map(function (n) { return n.n })); + inverseEv.addToGroup = ev.removeFromGroup; + } + + return inverseEv; + } + + /** + * Called on the `createSubflow` history event + * @param {object} ev The history event to undo + * @param {"createSubflow"} ev.t The history event type + * @param {string} ev.activeWorkspace + * @param {Array} [ev.links] + * @param {Array} [ev.nodes] + * @param {Array} [ev.removedLinks] + * @param {{ subflow: Subflow; offsetX?: number; offsetY?: number; }} ev.subflow + * @returns {HistoryEvent} The generated history event to redo + */ + function onCreateSubflow(ev) { + const inverseEv = { + t: "deleteSubflow", + activeWorkspace: ev.activeWorkspace, + dirty: RED.nodes.dirty() + }; + + if (ev.nodes) { + inverseEv.movedNodes = []; + const z = ev.activeWorkspace; + let fullNodeList = RED.nodes.filterNodes({ z: ev.subflow.subflow.id }); + fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id)); + fullNodeList = fullNodeList.concat(RED.nodes.junctions(ev.subflow.subflow.id)); + fullNodeList.forEach(function (n) { + n.x += ev.subflow.offsetX; + n.y += ev.subflow.offsetY; + n.dirty = true; + inverseEv.movedNodes.push(n.id); + RED.nodes.moveNodeToTab(n, z); + }); + inverseEv.subflows = []; + for (let i = 0; i < ev.nodes.length; i++) { + inverseEv.subflows.push(nodeOrJunction(ev.nodes[i])); + RED.nodes.remove(ev.nodes[i]); + } + } + if (ev.links) { + inverseEv.links = []; + for (let i = 0; i < ev.links.length; i++) { + inverseEv.links.push(ev.links[i]); + RED.nodes.removeLink(ev.links[i]); + } + } + + inverseEv.subflow = ev.subflow; + RED.nodes.removeSubflow(ev.subflow.subflow); + RED.workspaces.remove(ev.subflow.subflow); + + if (ev.removedLinks) { + inverseEv.createdLinks = []; + for (let i = 0; i < ev.removedLinks.length; i++) { + inverseEv.createdLinks.push(ev.removedLinks[i]); + RED.nodes.addLink(ev.removedLinks[i]); + } + } + + return inverseEv; + } + + /** + * Called on the `deleteSubflow` history event + * @param {object} ev The history event to undo + * @param {"deleteSubflow"} ev.t The history event type + * @param {string} ev.activeWorkspace + * @param {Array} [ev.createdLinks] + * @param {Array} [ev.links] + * @param {Array} [ev.movedNodes] + * @param {{ subflow: Subflow; offsetX?: number; offsetY?: number; }} ev.subflow + * @param {Array} [ev.subflows] + * @returns {HistoryEvent} The generated history event to redo + */ + function onDeleteSubflow(ev) { + const inverseEv = { + t: "createSubflow", + activeWorkspace: ev.activeWorkspace, + dirty: RED.nodes.dirty(), + }; + + if (ev.subflow) { + RED.nodes.addSubflow(ev.subflow.subflow); + inverseEv.subflow = ev.subflow; + if (ev.subflow.subflow.g) { + RED.group.addToGroup(RED.nodes.group(ev.subflow.subflow.g), ev.subflow.subflow); + } + } + + if (ev.subflows) { + inverseEv.nodes = []; + for (let i = 0; i < ev.subflows.length; i++) { + RED.nodes.add(ev.subflows[i]); + inverseEv.nodes.push(ev.subflows[i].id); + } + } + + if (ev.movedNodes) { + ev.movedNodes.forEach(function (nid) { + let nn = RED.nodes.node(nid); + if (!nn) { + nn = RED.nodes.group(nid); + } + nn.x -= ev.subflow.offsetX; + nn.y -= ev.subflow.offsetY; + nn.dirty = true; + RED.nodes.moveNodeToTab(nn, ev.subflow.subflow.id); + }); + } + + if (ev.links) { + inverseEv.links = []; + for (let i = 0; i < ev.links.length; i++) { + inverseEv.links.push(ev.links[i]); + RED.nodes.addLink(ev.links[i]); + } + } + + if (ev.createdLinks) { + inverseEv.removedLinks = []; + for (let i = 0; i < ev.createdLinks.length; i++) { + inverseEv.removedLinks.push(ev.createdLinks[i]); + RED.nodes.removeLink(ev.createdLinks[i]); + } + } + + return inverseEv; + } + + /** + * Called on the `reorder` history event + * @param {object} ev The history event to undo + * @param {"reorder"} ev.t The history event type + * @param {{ from: string; to: string; z: string; }} [ev.nodes] + * @param {{ from: Array; to: Array; }} [ev.workspaces] + * @returns {HistoryEvent} The generated history event to redo + */ + function onReoder(ev) { + const inverseEv = { + t: "reorder", + dirty: RED.nodes.dirty() + }; + + if (ev.workspaces) { + inverseEv.workspaces = { + from: ev.workspaces.to, + to: ev.workspaces.from + } + RED.workspaces.order(ev.workspaces.from); + } + + if (ev.nodes) { + inverseEv.nodes = { + z: ev.nodes.z, + from: ev.nodes.to, + to: ev.nodes.from + } + RED.nodes.setNodeOrder(ev.nodes.z, ev.nodes.from); + } + + return inverseEv; + } + + /** + * Called on the `createGroup` history event + * @param {object} ev The history event to undo + * @param {"createGroup"} ev.t The history event type + * @param {Array} [ev.groups] + * @returns {HistoryEvent} The generated history event to redo + */ + function onCreateGroup(ev) { + const inverseEv = { + t: "ungroup", + dirty: RED.nodes.dirty(), + groups: [] + }; + + if (ev.groups) { + for (let i = 0; i < ev.groups.length; i++) { + inverseEv.groups.push(ev.groups[i]); + RED.group.ungroup(ev.groups[i]); + } + } + + return inverseEv; + } + + /** + * Called on the `ungroup` history event + * @param {object} ev The history event to undo + * @param {"ungroup"} ev.t The history event type + * @param {Array} [ev.groups] + * @returns {HistoryEvent} The generated history event to redo + */ + function onUngroup(ev) { + const inverseEv = { + t: "createGroup", + dirty: RED.nodes.dirty(), + groups: [] + }; + + if (ev.groups) { + for (let i = 0; i < ev.groups.length; i++) { + inverseEv.groups.push(ev.groups[i]); + const nodes = ev.groups[i].nodes.slice(); + ev.groups[i].nodes = []; + RED.nodes.addGroup(ev.groups[i]); + RED.group.addToGroup(ev.groups[i], nodes); + if (ev.groups[i].g) { + const parentGroup = RED.nodes.group(ev.groups[i].g); + if (parentGroup) { + RED.group.addToGroup(parentGroup, ev.groups[i]); + } + } + } + } + + return inverseEv; + } + + /** + * Called on the `addToGroup` history event + * @param {object} ev The history event to undo + * @param {"addToGroup"} ev.t The history event type + * @param {Group} [ev.group] + * @param {Array | Node} [ev.nodes] + * @param {boolean} [ev.reparent] + * @returns {HistoryEvent} The generated history event to redo + */ + function onAddTogroup(ev) { + const inverseEv = { + t: "removeFromGroup", + dirty: RED.nodes.dirty(), + group: ev.group, + nodes: ev.nodes, + reparent: ev.reparent + }; + + if (ev.nodes) { + RED.group.removeFromGroup(ev.group, ev.nodes, (ev.hasOwnProperty("reparent") && ev.hasOwnProperty("reparent") !== undefined) ? ev.reparent : true); + } + + return inverseEv; + } + + /** + * Called on the `removeFromGroup` history event + * @param {object} ev The history event to undo + * @param {"removeFromGroup"} ev.t The history event type + * @param {Group} [ev.group] + * @param {Array | Node} [ev.nodes] + * @param {boolean} [ev.reparent] + * @returns {HistoryEvent} The generated history event to redo + */ + function onRemoveFromgroup(ev) { + const inverseEv = { + t: "addToGroup", + dirty: RED.nodes.dirty(), + group: ev.group, + nodes: ev.nodes, + reparent: ev.reparent + }; + + if (ev.nodes) { + RED.group.addToGroup(ev.group, ev.nodes); + } + + return inverseEv; + } + + /** + * Called on the `edit` history event + * @param {object} ev The history event to undo + * @param {"edit"} ev.t The history event type + * @param {object} ev.changes + * @param {Array} [ev.createdLinks] + * @param {Array} [ev.links] + * @param {Node} ev.node + * @param {object} [ev.outputMap] + * @param {Subflow} [ev.subflow] + * @returns {HistoryEvent} The generated history event to redo + */ + function onEdit(ev) { + const inverseEv = { + t: "edit", + changes: {}, + changed: ev.node.changed, + dirty: RED.nodes.dirty() + }; + + inverseEv.node = ev.node; + for (const i in ev.changes) { + if (ev.changes.hasOwnProperty(i)) { + inverseEv.changes[i] = ev.node[i]; + if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) { + // This property is a reference to another node or nodes. + let nodeList = ev.node[i]; + if (!Array.isArray(nodeList)) { + nodeList = [nodeList]; + } + + nodeList.forEach(function (id) { + const currentConfigNode = RED.nodes.node(id); + if (currentConfigNode && currentConfigNode._def.category === "config") { + currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node), 1); + RED.events.emit("nodes:change", currentConfigNode); + } + }); + + nodeList = ev.changes[i]; + if (!Array.isArray(nodeList)) { + nodeList = [nodeList]; + } + + nodeList.forEach(function (id) { + const newConfigNode = RED.nodes.node(id); + if (newConfigNode && newConfigNode._def.category === "config") { + newConfigNode.users.push(ev.node); + RED.events.emit("nodes:change", newConfigNode); + } }); } - if (ev.groups) { - inverseEv.groups = []; - var groupsToAdd = {}; - ev.groups.forEach(function(g) { groupsToAdd[g.id] = g; }); - for (i = ev.groups.length - 1;i>=0;i--) { - RED.nodes.addGroup(ev.groups[i]) - modifiedTabs[ev.groups[i].z] = true; - // The order of groups is important - // - to invert the action, the order is reversed - inverseEv.groups.unshift(ev.groups[i]); - if (ev.groups[i].g) { - if (!groupsToAdd[ev.groups[i].g]) { - group = RED.nodes.group(ev.groups[i].g); - } else { - group = groupsToAdd[ev.groups[i].g]; - } - if (group.nodes.indexOf(ev.groups[i]) === -1) { - group.nodes.push(ev.groups[i]); - } - RED.group.markDirty(ev.groups[i]) - } - } - } - if (ev.nodes) { - inverseEv.nodes = []; - for (i=0;i ev.subflow.inputCount) { - inverseEv.subflow.inputs = ev.node.in.slice(ev.subflow.inputCount); - ev.node.in.splice(ev.subflow.inputCount); - } else if (ev.subflow.inputs.length > 0) { - ev.node.in = ev.node.in.concat(ev.subflow.inputs); - } - } - if (ev.subflow.hasOwnProperty('outputCount')) { - inverseEv.subflow.outputCount = ev.node.out.length; - if (ev.node.out.length > ev.subflow.outputCount) { - inverseEv.subflow.outputs = ev.node.out.slice(ev.subflow.outputCount); - ev.node.out.splice(ev.subflow.outputCount); - } else if (ev.subflow.outputs.length > 0) { - ev.node.out = ev.node.out.concat(ev.subflow.outputs); - } - } - if (ev.subflow.hasOwnProperty('instances')) { - inverseEv.subflow.instances = []; - ev.subflow.instances.forEach(function(n) { - inverseEv.subflow.instances.push(n); - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - if (ev.subflow.hasOwnProperty('status')) { - if (ev.subflow.status) { - delete ev.node.status; - } - } - RED.editor.validateNode(ev.node); - RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { - n.inputs = ev.node.in.length; - n.outputs = ev.node.out.length; - RED.editor.updateNodeProperties(n); - RED.editor.validateNode(n); - }); - } else { - var outputMap; - if (ev.outputMap) { - outputMap = {}; - inverseEv.outputMap = {}; - for (var port in ev.outputMap) { - if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") { - outputMap[ev.outputMap[port]] = port; - inverseEv.outputMap[ev.outputMap[port]] = port; - } - } - } - ev.node.__outputs = inverseEv.changes.outputs; - RED.editor.updateNodeProperties(ev.node,outputMap); - RED.editor.validateNode(ev.node); - } - if (ev.links) { - inverseEv.createdLinks = []; - for (i=0;i ev.subflow.inputCount) { + inverseEv.subflow.inputs = ev.node.in.slice(ev.subflow.inputCount); + ev.node.in.splice(ev.subflow.inputCount); + } else if (ev.subflow.inputs.length > 0) { + ev.node.in = ev.node.in.concat(ev.subflow.inputs); } } - if(ev.callback && typeof ev.callback === 'function') { + if (ev.subflow.hasOwnProperty("outputCount")) { + inverseEv.subflow.outputCount = ev.node.out.length; + if (ev.node.out.length > ev.subflow.outputCount) { + inverseEv.subflow.outputs = ev.node.out.slice(ev.subflow.outputCount); + ev.node.out.splice(ev.subflow.outputCount); + } else if (ev.subflow.outputs.length > 0) { + ev.node.out = ev.node.out.concat(ev.subflow.outputs); + } + } + + if (ev.subflow.hasOwnProperty("instances")) { + inverseEv.subflow.instances = []; + ev.subflow.instances.forEach(function (n) { + inverseEv.subflow.instances.push(n); + const node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + + if (ev.subflow.hasOwnProperty("status")) { + if (ev.subflow.status) { + delete ev.node.status; + } + } + + RED.editor.validateNode(ev.node); + RED.nodes.filterNodes({ type: "subflow:" + ev.node.id }).forEach(function (n) { + n.inputs = ev.node.in.length; + n.outputs = ev.node.out.length; + RED.editor.updateNodeProperties(n); + RED.editor.validateNode(n); + }); + } else { + let outputMap; + if (ev.outputMap) { + outputMap = {}; + inverseEv.outputMap = {}; + for (const port in ev.outputMap) { + if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") { + outputMap[ev.outputMap[port]] = port; + inverseEv.outputMap[ev.outputMap[port]] = port; + } + } + } + ev.node.__outputs = inverseEv.changes.outputs; + RED.editor.updateNodeProperties(ev.node, outputMap); + RED.editor.validateNode(ev.node); + } + + if (ev.links) { + inverseEv.createdLinks = []; + for (let i = 0; i < ev.links.length; i++) { + RED.nodes.addLink(ev.links[i]); + inverseEv.createdLinks.push(ev.links[i]); + } + } + + if (ev.createdLinks) { + inverseEv.links = []; + for (let i = 0; i < ev.createdLinks.length; i++) { + RED.nodes.removeLink(ev.createdLinks[i]); + inverseEv.links.push(ev.createdLinks[i]); + } + } + + return inverseEv; + } + + /** + * Called on the `replace` history event + * @param {object} ev The history event to undo + * @param {"replace"} ev.t The history event type + * @param {Record} ev.changed + * @param {boolean} [ev.complete] + * @param {Array} ev.config + * @param {Record} ev.moved + * @param {string} [ev.rev] + * @returns {HistoryEvent} The generated history event to redo + */ + function onReplace(ev) { + let inverseEv; + + if (ev.complete) { + // This is a replace of everything. We can short-cut + // the logic by clearing everyting first, then importing + // the ev.config. + // Used by RED.diff.mergeDiff + inverseEv = { + t: "replace", + config: RED.nodes.createCompleteNodeSet(), + changed: {}, + moved: {}, + complete: true, + rev: RED.nodes.version(), + dirty: RED.nodes.dirty() + }; + + const selectedTab = RED.workspaces.active(); + inverseEv.config.forEach((n) => { + const node = RED.nodes.node(n.id); + if (node) { + inverseEv.changed[n.id] = node.changed; + inverseEv.moved[n.id] = node.moved; + } + }); + + RED.nodes.clear(); + const flowsToLock = new Set(); + const imported = RED.nodes.import(ev.config); + // Clear all change flags from the import + RED.nodes.dirty(false); + + imported.nodes.forEach(function (n) { + if (ev.changed[n.id]) { + ensureUnlocked(n.z, flowsToLock) + n.changed = true; + } + if (ev.moved[n.id]) { + ensureUnlocked(n.z, flowsToLock) + n.moved = true; + } + }); + + flowsToLock.forEach((flow) => { + flow.locked = true; + }); + + RED.nodes.version(ev.rev); + RED.view.redraw(true); + RED.palette.refresh(); + RED.workspaces.refresh(); + RED.workspaces.show(selectedTab, true); + RED.sidebar.config.refresh(); + } else { + const importMap = {}; + + ev.config.forEach(function (n) { + importMap[n.id] = "replace"; + }); + + const importedResult = RED.nodes.import(ev.config, { importMap: importMap }); + inverseEv = { + t: "replace", + config: importedResult.removedNodes, + dirty: RED.nodes.dirty() + }; + } + + return inverseEv; + } + + /** + * Called on the `multi` history event + * @param {object} ev The history event to undo + * @param {"multi"} ev.t The history event type + * @param {Array} ev.events + * @returns {HistoryEvent} The generated history event to redo + */ + function onMultipleEvents(ev) { + const inverseEv = { + t: "multi", + events: [] + }; + + const len = ev.events.length; + for (let i = len - 1; i >= 0; i--) { + const redoEvent = undoEvent(ev.events[i]); + inverseEv.events.push(redoEvent); + } + + return inverseEv; + } + + /** + * Called to undo the history event + * @param {HistoryEvent} ev The history event to undo + * @returns {HistoryEvent} The generated history event to redo + */ + function undoEvent(ev) { + let modifiedTabs = {}; + let inverseEv; + + if (ev) { + if (ev.t == "multi") { + inverseEv = onMultipleEvents(ev); + } else if (ev.t == "replace") { + inverseEv = onReplace(ev); + } else if (ev.t == "add") { + inverseEv = onAdd(ev, modifiedTabs); + } else if (ev.t == "delete") { + inverseEv = onDelete(ev, modifiedTabs); + } else if (ev.t == "move") { + inverseEv = onMove(ev); + } else if (ev.t == "edit") { + inverseEv = onEdit(ev); + } else if (ev.t == "createSubflow") { + inverseEv = onCreateSubflow(ev); + } else if (ev.t == "deleteSubflow") { + inverseEv = onDeleteSubflow(ev); + } else if (ev.t == "reorder") { + inverseEv = onReoder(ev); + } else if (ev.t == "createGroup") { + inverseEv = onCreateGroup(ev); + } else if (ev.t == "ungroup") { + inverseEv = onUngroup(ev); + } else if (ev.t == "addToGroup") { + inverseEv = onAddTogroup(ev); + } else if (ev.t == "removeFromGroup") { + inverseEv = onRemoveFromgroup(ev); + } + + if (ev.callback && typeof ev.callback === "function") { inverseEv.callback = ev.callback; ev.callback(ev); } - Object.keys(modifiedTabs).forEach(function(id) { - var subflow = RED.nodes.subflow(id); + Object.keys(modifiedTabs).forEach(function (id) { + const subflow = RED.nodes.subflow(id); if (subflow) { RED.editor.validateNode(subflow); } @@ -738,67 +1026,70 @@ RED.history = (function() { return inverseEv; } - } return { //TODO: this function is a placeholder until there is a 'save' event that can be listened to - markAllDirty: function() { - for (var i=0;i Date: Sat, 9 Nov 2024 22:30:45 +0100 Subject: [PATCH 2/6] Add comments --- .../@node-red/editor-client/src/js/history.js | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index df29b354e..8b28ac1ae 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -72,6 +72,7 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; + // Remove each node and if the node is in a group remove it from the group if (ev.nodes) { inverseEv.nodes = []; for (let i = 0; i < ev.nodes.length; i++) { @@ -87,11 +88,13 @@ RED.history = (function () { if (index !== -1) { group.nodes.splice(index, 1); RED.group.markDirty(group); + // TODO: no change event? } } } } + // Remove each links if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -100,6 +103,7 @@ RED.history = (function () { } } + // Remove each junction and if the junction is in a group remove it from the group if (ev.junctions) { inverseEv.junctions = []; for (let i = 0; i < ev.junctions.length; i++) { @@ -111,11 +115,13 @@ RED.history = (function () { if (index !== -1) { group.nodes.splice(index, 1); RED.group.markDirty(group); + // TODO: no change event? } } } } + // Remove each group if (ev.groups) { inverseEv.groups = []; for (let i = ev.groups.length - 1; i >= 0; i--) { @@ -128,10 +134,12 @@ RED.history = (function () { } } + // Remove each workspace if (ev.workspaces) { inverseEv.workspaces = []; for (let i = 0; i < ev.workspaces.length; i++) { const workspaceOrder = RED.nodes.getWorkspaceOrder(); + // Save the current index for the redo event ev.workspaces[i]._index = workspaceOrder.indexOf(ev.workspaces[i].id); inverseEv.workspaces.push(ev.workspaces[i]); RED.nodes.removeWorkspace(ev.workspaces[i].id); @@ -139,6 +147,7 @@ RED.history = (function () { } } + // Remove each subflow (tab) if (ev.subflows) { inverseEv.subflows = []; for (let i = 0; i < ev.subflows.length; i++) { @@ -148,6 +157,8 @@ RED.history = (function () { } } + // Mark each nodes which use this subflow has changed + // TODO: why? if (ev.subflow) { inverseEv.subflow = {}; if (ev.subflow.instances) { @@ -170,6 +181,7 @@ RED.history = (function () { } } + // Add links previously removed (in other delete event) if (ev.removedLinks) { inverseEv.createdLinks = []; for (let i = 0; i < ev.removedLinks.length; i++) { @@ -208,6 +220,7 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; + // Add each workspace if (ev.workspaces) { inverseEv.workspaces = []; for (let i = 0; i < ev.workspaces.length; i++) { @@ -223,6 +236,7 @@ RED.history = (function () { for (let i = 0; i < ev.subflows.length; i++) { inverseEv.subflows.push(ev.subflows[i]); RED.nodes.addSubflow(ev.subflows[i]); + // TODO: add to workspace? } } @@ -281,6 +295,7 @@ RED.history = (function () { }); } + // For each group, create the group and add nodes to it if (ev.groups) { inverseEv.groups = []; const groupsToAdd = {}; @@ -306,6 +321,7 @@ RED.history = (function () { } } + // Add each nodes, if the node has a group add to it if (ev.nodes) { inverseEv.nodes = []; for (let i = 0; i < ev.nodes.length; i++) { @@ -321,6 +337,8 @@ RED.history = (function () { } } } + + // Add each junctions, if the junction has a group add to it if (ev.junctions) { inverseEv.junctions = []; for (let i = 0; i < ev.junctions.length; i++) { @@ -336,6 +354,7 @@ RED.history = (function () { } } + // Add each links if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -344,6 +363,7 @@ RED.history = (function () { } } + // Remove each links if (ev.createdLinks) { inverseEv.removedLinks = []; for (let i = 0; i < ev.createdLinks.length; i++) { @@ -352,6 +372,8 @@ RED.history = (function () { } } + // Apply chanes to each nodes + // { [nodeId]: object - changes to apply} if (ev.changes) { for (const i in ev.changes) { if (ev.changes.hasOwnProperty(i)) { @@ -394,6 +416,7 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; + // Move each nodes for (let i = 0; i < ev.nodes.length; i++) { const n = ev.nodes[i]; const rn = { n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.n.moved }; @@ -405,6 +428,7 @@ RED.history = (function () { } // A move could have caused a link splice + // So remove each links then readd them if (ev.links) { inverseEv.removedLinks = []; for (let i = 0; i < ev.links.length; i++) { @@ -421,11 +445,13 @@ RED.history = (function () { } } + // Remove each nodes from the group if (ev.addToGroup) { RED.group.removeFromGroup(ev.addToGroup, ev.nodes.map(function (n) { return n.n }), false); inverseEv.removeFromGroup = ev.addToGroup; } + // Add each nodes to the group if (ev.removeFromGroup) { RED.group.addToGroup(ev.removeFromGroup, ev.nodes.map(function (n) { return n.n })); inverseEv.addToGroup = ev.removeFromGroup; @@ -452,9 +478,11 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; + // Remove all nodes from the subflow if (ev.nodes) { inverseEv.movedNodes = []; const z = ev.activeWorkspace; + // Get all nodes from the subflow defiition let fullNodeList = RED.nodes.filterNodes({ z: ev.subflow.subflow.id }); fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id)); fullNodeList = fullNodeList.concat(RED.nodes.junctions(ev.subflow.subflow.id)); @@ -471,6 +499,8 @@ RED.history = (function () { RED.nodes.remove(ev.nodes[i]); } } + + // Remove each links if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -479,10 +509,12 @@ RED.history = (function () { } } + // Remove the subflow (tab) inverseEv.subflow = ev.subflow; RED.nodes.removeSubflow(ev.subflow.subflow); RED.workspaces.remove(ev.subflow.subflow); + // Add each links if (ev.removedLinks) { inverseEv.createdLinks = []; for (let i = 0; i < ev.removedLinks.length; i++) { @@ -513,14 +545,17 @@ RED.history = (function () { dirty: RED.nodes.dirty(), }; + // Add the subflow (tab) if (ev.subflow) { RED.nodes.addSubflow(ev.subflow.subflow); inverseEv.subflow = ev.subflow; if (ev.subflow.subflow.g) { + // TODO: why? RED.group.addToGroup(RED.nodes.group(ev.subflow.subflow.g), ev.subflow.subflow); } } + // Create all nodes of the subflow if (ev.subflows) { inverseEv.nodes = []; for (let i = 0; i < ev.subflows.length; i++) { @@ -542,6 +577,7 @@ RED.history = (function () { }); } + // Add each links if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -550,6 +586,7 @@ RED.history = (function () { } } + // Remove each links if (ev.createdLinks) { inverseEv.removedLinks = []; for (let i = 0; i < ev.createdLinks.length; i++) { @@ -609,6 +646,7 @@ RED.history = (function () { groups: [] }; + // For each group, remove each nodes from the group then delete the group if (ev.groups) { for (let i = 0; i < ev.groups.length; i++) { inverseEv.groups.push(ev.groups[i]); @@ -638,9 +676,12 @@ RED.history = (function () { inverseEv.groups.push(ev.groups[i]); const nodes = ev.groups[i].nodes.slice(); ev.groups[i].nodes = []; + // Create the group RED.nodes.addGroup(ev.groups[i]); + // Add each nodes to the created group RED.group.addToGroup(ev.groups[i], nodes); if (ev.groups[i].g) { + // If the created group has a parent group, add to it const parentGroup = RED.nodes.group(ev.groups[i].g); if (parentGroup) { RED.group.addToGroup(parentGroup, ev.groups[i]); @@ -670,6 +711,7 @@ RED.history = (function () { reparent: ev.reparent }; + // Remove each nodes from the group if (ev.nodes) { RED.group.removeFromGroup(ev.group, ev.nodes, (ev.hasOwnProperty("reparent") && ev.hasOwnProperty("reparent") !== undefined) ? ev.reparent : true); } @@ -695,6 +737,7 @@ RED.history = (function () { reparent: ev.reparent }; + // Add each nodes to the group if (ev.nodes) { RED.group.addToGroup(ev.group, ev.nodes); } @@ -706,12 +749,14 @@ RED.history = (function () { * Called on the `edit` history event * @param {object} ev The history event to undo * @param {"edit"} ev.t The history event type + * @param {boolean} ev.changed * @param {object} ev.changes * @param {Array} [ev.createdLinks] * @param {Array} [ev.links] * @param {Node} ev.node * @param {object} [ev.outputMap] - * @param {Subflow} [ev.subflow] + * @param {{ instances?: Array; inputCount?: number; + * outputCount?: number; status?: string; }} [ev.subflow] * @returns {HistoryEvent} The generated history event to redo */ function onEdit(ev) { @@ -719,10 +764,12 @@ RED.history = (function () { t: "edit", changes: {}, changed: ev.node.changed, - dirty: RED.nodes.dirty() + dirty: RED.nodes.dirty(), + node: ev.node }; - inverseEv.node = ev.node; + // Apply old values to the current node + // And saves values ​​changed by old values for (const i in ev.changes) { if (ev.changes.hasOwnProperty(i)) { inverseEv.changes[i] = ev.node[i]; @@ -757,6 +804,9 @@ RED.history = (function () { ev.node[i] = ev.changes[i]; } } + + // The value of ev.node.changed is true (setted before to add to the history) + // During the undo, need to set the value the property had before it was set to true ev.node.dirty = true; ev.node.changed = ev.changed; @@ -768,13 +818,16 @@ RED.history = (function () { default: eventType = "nodes"; break; } + // Trigger the event after modifying the node eventType += ":change"; RED.events.emit(eventType, ev.node); + // If it is a tab, show or hide the disabled icon if (ev.node.type === "tab" && ev.changes.hasOwnProperty("disabled")) { $("#red-ui-tab-" + (ev.node.id.replace(".", "-"))).toggleClass("red-ui-workspace-disabled", !!ev.node.disabled); } + // If it is a tab, show or hide the locked icon if (ev.node.type === "tab" && ev.changes.hasOwnProperty("locked")) { $("#red-ui-tab-" + (ev.node.id.replace(".", "-"))).toggleClass("red-ui-workspace-locked", !!ev.node.locked); } @@ -813,13 +866,16 @@ RED.history = (function () { }); } + // Delete the current subflow status if (ev.subflow.hasOwnProperty("status")) { if (ev.subflow.status) { delete ev.node.status; } } + // Validate the subflow (tab) RED.editor.validateNode(ev.node); + // Update each subflow node and validate it RED.nodes.filterNodes({ type: "subflow:" + ev.node.id }).forEach(function (n) { n.inputs = ev.node.in.length; n.outputs = ev.node.out.length; @@ -843,6 +899,7 @@ RED.history = (function () { RED.editor.validateNode(ev.node); } + // Add links previously removed (undo) if (ev.links) { inverseEv.createdLinks = []; for (let i = 0; i < ev.links.length; i++) { @@ -851,6 +908,7 @@ RED.history = (function () { } } + // Remove links previously created (undo) if (ev.createdLinks) { inverseEv.links = []; for (let i = 0; i < ev.createdLinks.length; i++) { From 7ba7333310882ab666160b659d02e3106d47b00c Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:14:02 +0100 Subject: [PATCH 3/6] Improve docs and comments --- .../@node-red/editor-client/src/js/history.js | 321 +++++++++++------- 1 file changed, 199 insertions(+), 122 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 8b28ac1ae..e129353e2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -19,7 +19,36 @@ * @namespace RED.history */ RED.history = (function () { + /** + * GLOBAL NOTE: Handle event and users ONLY for edit/change event + * TODO: Import types from RED.nodes + * + * @typedef {object} Node + * @typedef {{ source: Node; sourcePort: number; target: Node; }} Link + * @typedef {Node & { env?: Array; }} Group + * @typedef {Node & {}} Junction + * @typedef {Node & { in: Array; out: Array; + * instances: Array; env?: Array; }} Subflow + * @typedef {Node & { type: "subflow"; direction: "in"|"out"; }} SubflowNode + * @typedef {Node & {}} Workspace + * + * @typedef {"add"|"delete"|"edit"|"move"|"multi"|"reorder"|"replace"| + * "createSubflow"|"deleteSubflow"|"addToGroup"|"createGroup"|"ungroup"| + * "removeFromGroup"} HistoryType + * @typedef {{ t: HistoryType; dirty: boolean; callback?: (ev: HistoryEvent) => void; } + * & (AddEvent | DeleteEvent | EditEvent | MoveEvent | MultiEvent | ReorderEvent | + * ReplaceEvent | CreateSubflowEvent | DeleteSubflowEvent | AddToGroupEvent | + * CreateGroupEvent | UngroupEvent | RemoveFromGroupEvent) } HistoryEvent + */ + + /** + * @type {Array} + */ let undoHistory = []; + + /** + * @type {Array} + */ let redoHistory = []; function nodeOrJunction(id) { @@ -39,32 +68,23 @@ RED.history = (function () { } } - /** - * @typedef {"add"|"delete"|"edit"|"move"|"multi"|"remove"|"reorder"|"replace"| - * "createSubflow"|"deleteSubflow"|"addToGroup"|"createGroup"|"ungroup"|"removeFromGroup"} HistoryType - * @typedef {{ t: HistoryType; dirty: boolean; callback?: (ev: HistoryEvent) => void; } & {}} HistoryEvent - * - * @typedef {{ source: Node; sourcePort: number; target: Node; }} Link - * @typedef {Node & {}} Group - * @typedef {Node & {}} Junction - * @typedef {Node & { instances: Array; }} Subflow - * @typedef {Node & {}} Workspace - */ - /** * Called on the `add` history event - * @param {object} ev The history event to undo - * @param {"add"} ev.t The history event type - * @param {Array} [ev.groups] - * @param {Array} [ev.junctions] - * @param {Array} [ev.links] - * @param {Array} [ev.nodes] - * @param {Array} [ev.removedLinks] - * @param {Subflow} [ev.subflow] - * @param {Array} [ev.subflows] - * @param {Array} [ev.workspaces] + * + * @typedef {object} AddEvent + * @property {"add"} t The history event type + * @property {Array} [groups] An array with added groups + * @property {Array} [junctions] An array with added junctions + * @property {Array} [links] An array with added links + * @property {Array} [nodes] An array with added nodes + * @property {Array} [removedLinks] An array with removed links + * @property {{ id: string; changed?: boolean; instances: Array; }} [subflow] + * @property {Array} [subflows] An array with added subflows (tabs) + * @property {Array} [workspaces] An array with added workspaces + * + * @param {AddEvent} ev The history event to undo * @param {object} [modifiedTabs] - * @returns {HistoryEvent} The generated history event to redo + * @returns {DeleteEvent} The generated history event to redo */ function onAdd(ev, modifiedTabs) { const inverseEv = { @@ -157,7 +177,6 @@ RED.history = (function () { } } - // Mark each nodes which use this subflow has changed // TODO: why? if (ev.subflow) { inverseEv.subflow = {}; @@ -195,22 +214,25 @@ RED.history = (function () { /** * Called on the `delete` history event - * @param {object} ev The history event to undo - * @param {"delete"} ev.t The history event type - * @param {Record} [ev.changes] - * @param {Array} [ev.createdLinks] - * @param {boolean} [ev.dirty] - * @param {Array} [ev.groups] - * @param {Array} [ev.junctions] - * @param {Array} [ev.links] - * @param {Array} [ev.nodes] - * @param {Subflow} [ev.subflow] - * @param {Array} [ev.subflowInputs] - * @param {Array} [ev.subflowOutputs] - * @param {Array} [ev.subflows] - * @param {Array} [ev.workspaces] + * + * @typedef {object} DeleteEvent + * @property {"delete"} t The history event type + * @property {Record} [changes] An object with changes. + * The key is the id of a node and the value is an object with the changes to apply + * @property {Array} [createdLinks] An array with created links + * @property {Array} [groups] An array with removed groups + * @property {Array} [junctions] An array with removed junctions + * @property {Array} [links] An array with removed links + * @property {Array} [nodes] An array with removed nodes + * @property {{ id?: string; instances?: Array; status?: string;}} [subflow] + * @property {Array} [subflowInputs] An array with removed subflow input + * @property {Array} [subflowOutputs] An array with removed subflow outputs + * @property {Array} [subflows] An array with removed subflows (tabs) + * @property {Array} [workspaces] An array with removed workspace + * + * @param {DeleteEvent} ev The history event to undo * @param {object} [modifiedTabs] - * @returns {HistoryEvent} The generated history event to redo + * @returns {AddEvent} The generated history event to redo */ function onDelete(ev, modifiedTabs) { // TODO: check why global @@ -266,6 +288,7 @@ RED.history = (function () { } } + // TODO: why? if (ev.subflow) { inverseEv.subflow = {}; if (ev.subflow.hasOwnProperty("instances")) { @@ -400,14 +423,18 @@ RED.history = (function () { /** * Called on the `move` history event - * @param {object} ev The history event to undo - * @param {"move"} ev.t The history event type - * @param {Group} [ev.addToGroup] - * @param {Array} [ev.links] - * @param {Array<{ n: Group; ox: number; oy: number; dx: number; dy: number; }>} ev.nodes - * @param {Array} [ev.removedLinks] - * @param {Group} [ev.removeFromGroup] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} MoveEvent + * @property {"move"} t The history event type + * @property {Group} [addToGroup] The group in which the nodes were added + * @property {Array} [links] + * @property {Array<{ n: Node; ox: number; oy: number; dx: number; + * dy: number; }>} nodes An array with nodes moved + * @property {Array} [removedLinks] + * @property {Group} [removeFromGroup] The group in which the nodes were removed + * + * @param {MoveEvent} ev The history event to undo + * @returns {MoveEvent} The generated history event to redo */ function onMove(ev) { const inverseEv = { @@ -428,7 +455,7 @@ RED.history = (function () { } // A move could have caused a link splice - // So remove each links then readd them + // TODO: still used? Ctrl + X ? if (ev.links) { inverseEv.removedLinks = []; for (let i = 0; i < ev.links.length; i++) { @@ -462,14 +489,20 @@ RED.history = (function () { /** * Called on the `createSubflow` history event - * @param {object} ev The history event to undo - * @param {"createSubflow"} ev.t The history event type - * @param {string} ev.activeWorkspace - * @param {Array} [ev.links] - * @param {Array} [ev.nodes] - * @param {Array} [ev.removedLinks] - * @param {{ subflow: Subflow; offsetX?: number; offsetY?: number; }} ev.subflow - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} CreateSubflowEvent + * @property {"createSubflow"} t The history event type + * @property {string} activeWorkspace The id of the active workspace + * @property {Array} [links] An array with added links (during + * conversion to Subflow - links inside the subflow) + * @property {Array} [nodes] An array with subflow node ids + * @property {Array} [removedLinks] An array with removed links + * (during conversion to Subflow - links from active workspace) + * @property {{ subflow: Subflow; offsetX?: number; + * offsetY?: number; }} subflow The subflow created to delete + * + * @param {CreateSubflowEvent} ev The history event to undo + * @returns {DeleteSubflowEvent} The generated history event to redo */ function onCreateSubflow(ev) { const inverseEv = { @@ -478,11 +511,12 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; - // Remove all nodes from the subflow + // Remove all subflow nodes and move all nodes from subflow definition + // to the active workspace if (ev.nodes) { inverseEv.movedNodes = []; const z = ev.activeWorkspace; - // Get all nodes from the subflow defiition + // Get all nodes from the subflow definition let fullNodeList = RED.nodes.filterNodes({ z: ev.subflow.subflow.id }); fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id)); fullNodeList = fullNodeList.concat(RED.nodes.junctions(ev.subflow.subflow.id)); @@ -500,7 +534,7 @@ RED.history = (function () { } } - // Remove each links + // Remove each links (from the conversion - inside the subflow) if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -514,7 +548,7 @@ RED.history = (function () { RED.nodes.removeSubflow(ev.subflow.subflow); RED.workspaces.remove(ev.subflow.subflow); - // Add each links + // Add each links (from the undo of conversion - active workspace) if (ev.removedLinks) { inverseEv.createdLinks = []; for (let i = 0; i < ev.removedLinks.length; i++) { @@ -528,15 +562,23 @@ RED.history = (function () { /** * Called on the `deleteSubflow` history event - * @param {object} ev The history event to undo - * @param {"deleteSubflow"} ev.t The history event type - * @param {string} ev.activeWorkspace - * @param {Array} [ev.createdLinks] - * @param {Array} [ev.links] - * @param {Array} [ev.movedNodes] - * @param {{ subflow: Subflow; offsetX?: number; offsetY?: number; }} ev.subflow - * @param {Array} [ev.subflows] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} DeleteSubflowEvent + * @property {"deleteSubflow"} t The history event type + * @property {string} activeWorkspace The id of the active workspace + * @property {Array} [createdLinks] An array with added links + * (during undo conversion to Subflow - links from active workspace) + * @property {Array} [links] An array with removed links (during + * undo conversion to Subflow - links inside the subflow) + * @property {Array} [movedNodes] An array with nodes to move to the + * subflow to create + * @property {{ subflow: Subflow; offsetX?: number; + * offsetY?: number; }} subflow The deleted subflow to create + * @property {Array} [subflows] An array with subflow nodes + * (redo conversion to subflow) + * + * @param {DeleteSubflowEvent} ev The history event to undo + * @returns {CreateSubflowEvent} The generated history event to redo */ function onDeleteSubflow(ev) { const inverseEv = { @@ -555,7 +597,7 @@ RED.history = (function () { } } - // Create all nodes of the subflow + // Create all subflow nodes if (ev.subflows) { inverseEv.nodes = []; for (let i = 0; i < ev.subflows.length; i++) { @@ -564,6 +606,8 @@ RED.history = (function () { } } + // Move each node from the active workspace to the subflow created + // Nodes selected for the conversion to subflow if (ev.movedNodes) { ev.movedNodes.forEach(function (nid) { let nn = RED.nodes.node(nid); @@ -577,7 +621,7 @@ RED.history = (function () { }); } - // Add each links + // Add each links (from redo of conversion - inside the subflow) if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -586,7 +630,7 @@ RED.history = (function () { } } - // Remove each links + // Remove each links (from redo of conversion - on active workspace) if (ev.createdLinks) { inverseEv.removedLinks = []; for (let i = 0; i < ev.createdLinks.length; i++) { @@ -600,11 +644,14 @@ RED.history = (function () { /** * Called on the `reorder` history event - * @param {object} ev The history event to undo - * @param {"reorder"} ev.t The history event type - * @param {{ from: string; to: string; z: string; }} [ev.nodes] - * @param {{ from: Array; to: Array; }} [ev.workspaces] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} ReorderEvent + * @property {"reorder"} t The history event type + * @property {{ from: string; to: string; z: string; }} [nodes] + * @property {{ from: Array; to: Array; }} [workspaces] + * + * @param {ReorderEvent} ev The history event to undo + * @returns {ReorderEvent} The generated history event to redo */ function onReoder(ev) { const inverseEv = { @@ -634,10 +681,13 @@ RED.history = (function () { /** * Called on the `createGroup` history event - * @param {object} ev The history event to undo - * @param {"createGroup"} ev.t The history event type - * @param {Array} [ev.groups] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} CreateGroupEvent + * @property {"createGroup"} t The history event type + * @property {Array} [groups] An array with groups to remove + * + * @param {CreateGroupEvent} ev The history event to undo + * @returns {UngroupEvent} The generated history event to redo */ function onCreateGroup(ev) { const inverseEv = { @@ -659,10 +709,13 @@ RED.history = (function () { /** * Called on the `ungroup` history event - * @param {object} ev The history event to undo - * @param {"ungroup"} ev.t The history event type - * @param {Array} [ev.groups] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} UngroupEvent + * @property {"ungroup"} t The history event type + * @property {Array} [groups] An array with groups to create + * + * @param {UngroupEvent} ev The history event to undo + * @returns {CreateGroupEvent} The generated history event to redo */ function onUngroup(ev) { const inverseEv = { @@ -695,12 +748,16 @@ RED.history = (function () { /** * Called on the `addToGroup` history event - * @param {object} ev The history event to undo - * @param {"addToGroup"} ev.t The history event type - * @param {Group} [ev.group] - * @param {Array | Node} [ev.nodes] - * @param {boolean} [ev.reparent] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} AddToGroupEvent + * @property {"addToGroup"} t The history event type + * @property {Group} group The group in which remove nodes + * @property {Array | Node} [nodes] An array of nodes or one node + * to remove from the group + * @property {boolean} [reparent] + * + * @param {AddToGroupEvent} ev The history event to undo + * @returns {RemoveFromGroupEvent} The generated history event to redo */ function onAddTogroup(ev) { const inverseEv = { @@ -721,12 +778,16 @@ RED.history = (function () { /** * Called on the `removeFromGroup` history event - * @param {object} ev The history event to undo - * @param {"removeFromGroup"} ev.t The history event type - * @param {Group} [ev.group] - * @param {Array | Node} [ev.nodes] - * @param {boolean} [ev.reparent] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} RemoveFromGroupEvent + * @property {"removeFromGroup"} t The history event type + * @property {Group} group The group in which add nodes + * @property {Array | Node} [nodes] An array of nodes or one node + * to add to the group + * @property {boolean} [reparent] + * + * @param {RemoveFromGroupEvent} ev The history event to undo + * @returns {AddToGroupEvent} The generated history event to redo */ function onRemoveFromgroup(ev) { const inverseEv = { @@ -747,17 +808,20 @@ RED.history = (function () { /** * Called on the `edit` history event - * @param {object} ev The history event to undo - * @param {"edit"} ev.t The history event type - * @param {boolean} ev.changed - * @param {object} ev.changes - * @param {Array} [ev.createdLinks] - * @param {Array} [ev.links] - * @param {Node} ev.node - * @param {object} [ev.outputMap] - * @param {{ instances?: Array; inputCount?: number; - * outputCount?: number; status?: string; }} [ev.subflow] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} EditEvent + * @property {"edit"} t The history event type + * @property {boolean} changed The changed node state before modifications + * @property {object} changes An object with previous node properties value + * @property {Array} [createdLinks] An array with links to create (redo) + * @property {Array} [links] An array with removed links + * @property {Node} node The current node + * @property {object} [outputMap] + * @property {{ instances?: Array; inputCount?: number; + * outputCount?: number; status?: string; }} [subflow] + * + * @param {EditEvent} ev The history event to undo + * @returns {EditEvent} The generated history event to redo */ function onEdit(ev) { const inverseEv = { @@ -769,7 +833,7 @@ RED.history = (function () { }; // Apply old values to the current node - // And saves values ​​changed by old values + // And saves values ​​changed by old values for the redo for (const i in ev.changes) { if (ev.changes.hasOwnProperty(i)) { inverseEv.changes[i] = ev.node[i]; @@ -908,7 +972,7 @@ RED.history = (function () { } } - // Remove links previously created (undo) + // Remove links previously created (redo) if (ev.createdLinks) { inverseEv.links = []; for (let i = 0; i < ev.createdLinks.length; i++) { @@ -922,14 +986,22 @@ RED.history = (function () { /** * Called on the `replace` history event - * @param {object} ev The history event to undo - * @param {"replace"} ev.t The history event type - * @param {Record} ev.changed - * @param {boolean} [ev.complete] - * @param {Array} ev.config - * @param {Record} ev.moved - * @param {string} [ev.rev] - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} ReplaceEvent + * @property {"replace"} t The history event type + * @property {Record} [changed] An object with a node id + * as key and the node changed property as value + * @property {boolean} [complete] If the {@link ReplaceEvent.config} + * property contains the complete flows + * @property {Array} config An array with config nodes and/or + * subflow definitions to replace. Can be the complete flows too + * @property {boolean} [dirty] The dirty state before replacement + * @property {Record} [moved] An object with a node id + * as key and the node moved property as value + * @property {string} [rev] A revision version + * + * @param {ReplaceEvent} ev The history event to undo + * @returns {ReplaceEvent} The generated history event to redo */ function onReplace(ev) { let inverseEv; @@ -954,6 +1026,7 @@ RED.history = (function () { const node = RED.nodes.node(n.id); if (node) { inverseEv.changed[n.id] = node.changed; + // TODO: Why moved? inverseEv.moved[n.id] = node.moved; } }); @@ -1005,10 +1078,13 @@ RED.history = (function () { /** * Called on the `multi` history event - * @param {object} ev The history event to undo - * @param {"multi"} ev.t The history event type - * @param {Array} ev.events - * @returns {HistoryEvent} The generated history event to redo + * + * @typedef {object} MultiEvent + * @property {"multi"} t The history event type + * @property {Array} events An array with events + * + * @param {MultiEvent} ev The history event to undo + * @returns {MultiEvent} The generated history event to redo */ function onMultipleEvents(ev) { const inverseEv = { @@ -1027,6 +1103,7 @@ RED.history = (function () { /** * Called to undo the history event + * * @param {HistoryEvent} ev The history event to undo * @returns {HistoryEvent} The generated history event to redo */ From 4aff2ae08a9d3516015b32218be0ba62d9d1cd51 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Sun, 17 Nov 2024 14:22:34 +0100 Subject: [PATCH 4/6] Further improve docs and comments --- .../@node-red/editor-client/src/js/history.js | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index e129353e2..3159a4919 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -114,7 +114,7 @@ RED.history = (function () { } } - // Remove each links + // Remove each link if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -177,13 +177,14 @@ RED.history = (function () { } } - // TODO: why? + // Mark each subflow node as changed (because node added) if (ev.subflow) { inverseEv.subflow = {}; if (ev.subflow.instances) { inverseEv.subflow.instances = []; ev.subflow.instances.forEach(function (n) { inverseEv.subflow.instances.push(n); + // TODO: no need getNode, n is the node const node = RED.nodes.node(n.id); if (node) { node.changed = n.changed; @@ -192,6 +193,7 @@ RED.history = (function () { }); } + // Set the changed prop of subflow (tab) if (ev.subflow.hasOwnProperty("changed")) { const subflow = RED.nodes.subflow(ev.subflow.id); if (subflow) { @@ -224,7 +226,7 @@ RED.history = (function () { * @property {Array} [junctions] An array with removed junctions * @property {Array} [links] An array with removed links * @property {Array} [nodes] An array with removed nodes - * @property {{ id?: string; instances?: Array; status?: string;}} [subflow] + * @property {{ id?: string; instances?: Array; status?: string;}} [subflow] * @property {Array} [subflowInputs] An array with removed subflow input * @property {Array} [subflowOutputs] An array with removed subflow outputs * @property {Array} [subflows] An array with removed subflows (tabs) @@ -253,6 +255,7 @@ RED.history = (function () { } } + // Add each subflow (tab) if (ev.subflows) { inverseEv.subflows = []; for (let i = 0; i < ev.subflows.length; i++) { @@ -262,12 +265,14 @@ RED.history = (function () { } } + // Update the input array - 0 or one element if (ev.subflowInputs && ev.subflowInputs.length > 0) { subflow = RED.nodes.subflow(ev.subflowInputs[0].z); subflow.in.push(ev.subflowInputs[0]); subflow.in[0].dirty = true; } + // Update the output array - 0 or more elements if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); ev.subflowOutputs.sort(function (a, b) { return a.i - b.i }); @@ -288,13 +293,14 @@ RED.history = (function () { } } - // TODO: why? if (ev.subflow) { inverseEv.subflow = {}; + // Mark each subflow node as changed if (ev.subflow.hasOwnProperty("instances")) { inverseEv.subflow.instances = []; ev.subflow.instances.forEach(function (n) { inverseEv.subflow.instances.push(n); + // TODO: Use n instead of getNode const node = RED.nodes.node(n.id); if (node) { node.changed = n.changed; @@ -303,6 +309,7 @@ RED.history = (function () { }); } + // Add the status (redo) if (ev.subflow.hasOwnProperty("status")) { const subflow = RED.nodes.subflow(ev.subflow.id); subflow.status = ev.subflow.status; @@ -310,6 +317,7 @@ RED.history = (function () { } if (subflow) { + // TODO: use subflow.instances RED.nodes.filterNodes({ type: "subflow:" + subflow.id }).forEach(function (n) { n.inputs = subflow.in.length; n.outputs = subflow.out.length; @@ -443,7 +451,7 @@ RED.history = (function () { dirty: RED.nodes.dirty() }; - // Move each nodes + // Move each node for (let i = 0; i < ev.nodes.length; i++) { const n = ev.nodes[i]; const rn = { n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.n.moved }; @@ -472,13 +480,13 @@ RED.history = (function () { } } - // Remove each nodes from the group + // Remove each node from the group if (ev.addToGroup) { RED.group.removeFromGroup(ev.addToGroup, ev.nodes.map(function (n) { return n.n }), false); inverseEv.removeFromGroup = ev.addToGroup; } - // Add each nodes to the group + // Add each node to the group if (ev.removeFromGroup) { RED.group.addToGroup(ev.removeFromGroup, ev.nodes.map(function (n) { return n.n })); inverseEv.addToGroup = ev.removeFromGroup; @@ -534,7 +542,7 @@ RED.history = (function () { } } - // Remove each links (from the conversion - inside the subflow) + // Remove each link (from the conversion - inside the subflow) if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -548,7 +556,7 @@ RED.history = (function () { RED.nodes.removeSubflow(ev.subflow.subflow); RED.workspaces.remove(ev.subflow.subflow); - // Add each links (from the undo of conversion - active workspace) + // Add each link (from the undo of conversion - active workspace) if (ev.removedLinks) { inverseEv.createdLinks = []; for (let i = 0; i < ev.removedLinks.length; i++) { @@ -621,7 +629,7 @@ RED.history = (function () { }); } - // Add each links (from redo of conversion - inside the subflow) + // Add each link (from redo of conversion - inside the subflow) if (ev.links) { inverseEv.links = []; for (let i = 0; i < ev.links.length; i++) { @@ -630,7 +638,7 @@ RED.history = (function () { } } - // Remove each links (from redo of conversion - on active workspace) + // Remove each link (from redo of conversion - on active workspace) if (ev.createdLinks) { inverseEv.removedLinks = []; for (let i = 0; i < ev.createdLinks.length; i++) { @@ -648,7 +656,9 @@ RED.history = (function () { * @typedef {object} ReorderEvent * @property {"reorder"} t The history event type * @property {{ from: string; to: string; z: string; }} [nodes] - * @property {{ from: Array; to: Array; }} [workspaces] + * @property {object} [workspaces] + * @property {Array} workspaces[].from Ordered array of workspace ids + * @property {Array} workspaces[].to Ordered array of workspace ids * * @param {ReorderEvent} ev The history event to undo * @returns {ReorderEvent} The generated history event to redo @@ -667,6 +677,7 @@ RED.history = (function () { RED.workspaces.order(ev.workspaces.from); } + // TODO: still used? Maybe replace by convertToSubflow if (ev.nodes) { inverseEv.nodes = { z: ev.nodes.z, @@ -696,7 +707,7 @@ RED.history = (function () { groups: [] }; - // For each group, remove each nodes from the group then delete the group + // For each group, remove each node from the group then delete the group if (ev.groups) { for (let i = 0; i < ev.groups.length; i++) { inverseEv.groups.push(ev.groups[i]); @@ -731,7 +742,7 @@ RED.history = (function () { ev.groups[i].nodes = []; // Create the group RED.nodes.addGroup(ev.groups[i]); - // Add each nodes to the created group + // Add each node to the created group RED.group.addToGroup(ev.groups[i], nodes); if (ev.groups[i].g) { // If the created group has a parent group, add to it @@ -754,7 +765,7 @@ RED.history = (function () { * @property {Group} group The group in which remove nodes * @property {Array | Node} [nodes] An array of nodes or one node * to remove from the group - * @property {boolean} [reparent] + * @property {boolean} [reparent] Either to re-add to parent group * * @param {AddToGroupEvent} ev The history event to undo * @returns {RemoveFromGroupEvent} The generated history event to redo @@ -768,7 +779,7 @@ RED.history = (function () { reparent: ev.reparent }; - // Remove each nodes from the group + // Remove each node from the group if (ev.nodes) { RED.group.removeFromGroup(ev.group, ev.nodes, (ev.hasOwnProperty("reparent") && ev.hasOwnProperty("reparent") !== undefined) ? ev.reparent : true); } @@ -784,7 +795,7 @@ RED.history = (function () { * @property {Group} group The group in which add nodes * @property {Array | Node} [nodes] An array of nodes or one node * to add to the group - * @property {boolean} [reparent] + * @property {boolean} [reparent] Either to re-add to parent group * * @param {RemoveFromGroupEvent} ev The history event to undo * @returns {AddToGroupEvent} The generated history event to redo @@ -798,7 +809,7 @@ RED.history = (function () { reparent: ev.reparent }; - // Add each nodes to the group + // Add each node to the group if (ev.nodes) { RED.group.addToGroup(ev.group, ev.nodes); } @@ -815,10 +826,10 @@ RED.history = (function () { * @property {object} changes An object with previous node properties value * @property {Array} [createdLinks] An array with links to create (redo) * @property {Array} [links] An array with removed links - * @property {Node} node The current node + * @property {Node} node The current node/subflow * @property {object} [outputMap] - * @property {{ instances?: Array; inputCount?: number; - * outputCount?: number; status?: string; }} [subflow] + * @property {{ instances?: Array; inputCount?: number; + * outputCount?: number; status?: string; }} [subflow] Subflow properties * * @param {EditEvent} ev The history event to undo * @returns {EditEvent} The generated history event to redo @@ -834,6 +845,8 @@ RED.history = (function () { // Apply old values to the current node // And saves values ​​changed by old values for the redo + // TODO: Create a function like `RED.editor.updateNodeproperties`? + // => `RED.nodes.updateNodeproperties(node, changes)` for (const i in ev.changes) { if (ev.changes.hasOwnProperty(i)) { inverseEv.changes[i] = ev.node[i]; @@ -844,6 +857,8 @@ RED.history = (function () { nodeList = [nodeList]; } + // Remove the node from each config node users + // TODO: remove only if ot used nodeList.forEach(function (id) { const currentConfigNode = RED.nodes.node(id); if (currentConfigNode && currentConfigNode._def.category === "config") { @@ -857,6 +872,7 @@ RED.history = (function () { nodeList = [nodeList]; } + // Add the node to each config node users nodeList.forEach(function (id) { const newConfigNode = RED.nodes.node(id); if (newConfigNode && newConfigNode._def.category === "config") { @@ -898,6 +914,7 @@ RED.history = (function () { if (ev.subflow) { inverseEv.subflow = {}; + // Update the input array - 0 or one element if (ev.subflow.hasOwnProperty("inputCount")) { inverseEv.subflow.inputCount = ev.node.in.length; if (ev.node.in.length > ev.subflow.inputCount) { @@ -908,6 +925,7 @@ RED.history = (function () { } } + // Update the outputs array - 0 or more elements if (ev.subflow.hasOwnProperty("outputCount")) { inverseEv.subflow.outputCount = ev.node.out.length; if (ev.node.out.length > ev.subflow.outputCount) { @@ -918,10 +936,12 @@ RED.history = (function () { } } + // Mark each subflow node as changed if (ev.subflow.hasOwnProperty("instances")) { inverseEv.subflow.instances = []; ev.subflow.instances.forEach(function (n) { inverseEv.subflow.instances.push(n); + // TODO: why check if the node exist? const node = RED.nodes.node(n.id); if (node) { node.changed = n.changed; @@ -940,6 +960,7 @@ RED.history = (function () { // Validate the subflow (tab) RED.editor.validateNode(ev.node); // Update each subflow node and validate it + // TODO: instances prop can be used here RED.nodes.filterNodes({ type: "subflow:" + ev.node.id }).forEach(function (n) { n.inputs = ev.node.in.length; n.outputs = ev.node.out.length; @@ -987,18 +1008,26 @@ RED.history = (function () { /** * Called on the `replace` history event * - * @typedef {object} ReplaceEvent + * @typedef {object} CompleteReplaceEvent * @property {"replace"} t The history event type - * @property {Record} [changed] An object with a node id + * @property {Record} changed An object with a node id * as key and the node changed property as value - * @property {boolean} [complete] If the {@link ReplaceEvent.config} + * @property {true} complete Either the {@link ReplaceEvent.config} + * property contains the complete flows + * @property {Array} config An array with the complete flows + * @property {boolean} dirty The dirty state before replacement + * @property {Record} moved An object with a node id + * as key and the node moved property as value + * @property {string} rev A revision version + * + * @typedef {object} IncompleteReplaceEvent + * @property {"replace"} t The history event type + * @property {false} [complete] Either the {@link ReplaceEvent.config} * property contains the complete flows * @property {Array} config An array with config nodes and/or - * subflow definitions to replace. Can be the complete flows too - * @property {boolean} [dirty] The dirty state before replacement - * @property {Record} [moved] An object with a node id - * as key and the node moved property as value - * @property {string} [rev] A revision version + * subflow definitions to replace. + * + * @typedef {CompleteReplaceEvent|IncompleteReplaceEvent} ReplaceEvent * * @param {ReplaceEvent} ev The history event to undo * @returns {ReplaceEvent} The generated history event to redo @@ -1141,6 +1170,7 @@ RED.history = (function () { } if (ev.callback && typeof ev.callback === "function") { + inverseEv = inverseEv || {}; inverseEv.callback = ev.callback; ev.callback(ev); } From e234c19ca865797ed514e4a4bb737f2ffceb81b3 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:10:21 +0100 Subject: [PATCH 5/6] Fix the jsdoc syntax and docs API methods --- .../@node-red/editor-client/src/js/history.js | 112 +++++++++++++++--- 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 3159a4919..02747892c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -39,6 +39,7 @@ RED.history = (function () { * & (AddEvent | DeleteEvent | EditEvent | MoveEvent | MultiEvent | ReorderEvent | * ReplaceEvent | CreateSubflowEvent | DeleteSubflowEvent | AddToGroupEvent | * CreateGroupEvent | UngroupEvent | RemoveFromGroupEvent) } HistoryEvent + * @memberof RED.history */ /** @@ -76,11 +77,13 @@ RED.history = (function () { * @property {Array} [groups] An array with added groups * @property {Array} [junctions] An array with added junctions * @property {Array} [links] An array with added links - * @property {Array} [nodes] An array with added nodes + * @property {Array} [nodes] An array with added node ids * @property {Array} [removedLinks] An array with removed links - * @property {{ id: string; changed?: boolean; instances: Array; }} [subflow] + * @property {{ id: string, changed: boolean | undefined, + * instances: Array }} [subflow] * @property {Array} [subflows] An array with added subflows (tabs) * @property {Array} [workspaces] An array with added workspaces + * @memberof RED.history * * @param {AddEvent} ev The history event to undo * @param {object} [modifiedTabs] @@ -220,17 +223,22 @@ RED.history = (function () { * @typedef {object} DeleteEvent * @property {"delete"} t The history event type * @property {Record} [changes] An object with changes. - * The key is the id of a node and the value is an object with the changes to apply + * The key is the id of a node and the value is an object with the changes + * to apply * @property {Array} [createdLinks] An array with created links * @property {Array} [groups] An array with removed groups * @property {Array} [junctions] An array with removed junctions * @property {Array} [links] An array with removed links * @property {Array} [nodes] An array with removed nodes - * @property {{ id?: string; instances?: Array; status?: string;}} [subflow] - * @property {Array} [subflowInputs] An array with removed subflow input - * @property {Array} [subflowOutputs] An array with removed subflow outputs + * @property {{ id: string | undefined, instances: Array | undefined, + * status: string | undefined }} [subflow] + * @property {Array} [subflowInputs] An array with removed + * subflow input + * @property {Array} [subflowOutputs] An array with removed + * subflow outputs * @property {Array} [subflows] An array with removed subflows (tabs) * @property {Array} [workspaces] An array with removed workspace + * @memberof RED.history * * @param {DeleteEvent} ev The history event to undo * @param {object} [modifiedTabs] @@ -436,10 +444,12 @@ RED.history = (function () { * @property {"move"} t The history event type * @property {Group} [addToGroup] The group in which the nodes were added * @property {Array} [links] - * @property {Array<{ n: Node; ox: number; oy: number; dx: number; - * dy: number; }>} nodes An array with nodes moved + * @property {Array<{ n: Node, ox: number, oy: number, dx: number, + * dy: number }>} nodes An array with nodes moved * @property {Array} [removedLinks] - * @property {Group} [removeFromGroup] The group in which the nodes were removed + * @property {Group} [removeFromGroup] The group in which the nodes were + * removed + * @memberof RED.history * * @param {MoveEvent} ev The history event to undo * @returns {MoveEvent} The generated history event to redo @@ -506,8 +516,9 @@ RED.history = (function () { * @property {Array} [nodes] An array with subflow node ids * @property {Array} [removedLinks] An array with removed links * (during conversion to Subflow - links from active workspace) - * @property {{ subflow: Subflow; offsetX?: number; - * offsetY?: number; }} subflow The subflow created to delete + * @property {{ subflow: Subflow, offsetX: number | undefined, + * offsetY: number | undefined }} subflow The subflow created to delete + * @memberof RED.history * * @param {CreateSubflowEvent} ev The history event to undo * @returns {DeleteSubflowEvent} The generated history event to redo @@ -580,10 +591,11 @@ RED.history = (function () { * undo conversion to Subflow - links inside the subflow) * @property {Array} [movedNodes] An array with nodes to move to the * subflow to create - * @property {{ subflow: Subflow; offsetX?: number; - * offsetY?: number; }} subflow The deleted subflow to create + * @property {{ subflow: Subflow, offsetX: number | undefined, + * offsetY: number | undefined }} subflow The deleted subflow to create * @property {Array} [subflows] An array with subflow nodes * (redo conversion to subflow) + * @memberof RED.history * * @param {DeleteSubflowEvent} ev The history event to undo * @returns {CreateSubflowEvent} The generated history event to redo @@ -655,10 +667,11 @@ RED.history = (function () { * * @typedef {object} ReorderEvent * @property {"reorder"} t The history event type - * @property {{ from: string; to: string; z: string; }} [nodes] + * @property {{ from: string, to: string, z: string }} [nodes] * @property {object} [workspaces] * @property {Array} workspaces[].from Ordered array of workspace ids * @property {Array} workspaces[].to Ordered array of workspace ids + * @memberof RED.history * * @param {ReorderEvent} ev The history event to undo * @returns {ReorderEvent} The generated history event to redo @@ -696,6 +709,7 @@ RED.history = (function () { * @typedef {object} CreateGroupEvent * @property {"createGroup"} t The history event type * @property {Array} [groups] An array with groups to remove + * @memberof RED.history * * @param {CreateGroupEvent} ev The history event to undo * @returns {UngroupEvent} The generated history event to redo @@ -724,6 +738,7 @@ RED.history = (function () { * @typedef {object} UngroupEvent * @property {"ungroup"} t The history event type * @property {Array} [groups] An array with groups to create + * @memberof RED.history * * @param {UngroupEvent} ev The history event to undo * @returns {CreateGroupEvent} The generated history event to redo @@ -766,6 +781,7 @@ RED.history = (function () { * @property {Array | Node} [nodes] An array of nodes or one node * to remove from the group * @property {boolean} [reparent] Either to re-add to parent group + * @memberof RED.history * * @param {AddToGroupEvent} ev The history event to undo * @returns {RemoveFromGroupEvent} The generated history event to redo @@ -796,6 +812,7 @@ RED.history = (function () { * @property {Array | Node} [nodes] An array of nodes or one node * to add to the group * @property {boolean} [reparent] Either to re-add to parent group + * @memberof RED.history * * @param {RemoveFromGroupEvent} ev The history event to undo * @returns {AddToGroupEvent} The generated history event to redo @@ -828,8 +845,10 @@ RED.history = (function () { * @property {Array} [links] An array with removed links * @property {Node} node The current node/subflow * @property {object} [outputMap] - * @property {{ instances?: Array; inputCount?: number; - * outputCount?: number; status?: string; }} [subflow] Subflow properties + * @property {{ instances: Array | undefined, + * inputCount: number | undefined, outputCount: number | undefined, + * status: string | undefined }} [subflow] Subflow properties + * @memberof RED.history * * @param {EditEvent} ev The history event to undo * @returns {EditEvent} The generated history event to redo @@ -1019,15 +1038,18 @@ RED.history = (function () { * @property {Record} moved An object with a node id * as key and the node moved property as value * @property {string} rev A revision version - * + * @memberof RED.history + * * @typedef {object} IncompleteReplaceEvent * @property {"replace"} t The history event type * @property {false} [complete] Either the {@link ReplaceEvent.config} * property contains the complete flows * @property {Array} config An array with config nodes and/or * subflow definitions to replace. + * @memberof RED.history * * @typedef {CompleteReplaceEvent|IncompleteReplaceEvent} ReplaceEvent + * @memberof RED.history * * @param {ReplaceEvent} ev The history event to undo * @returns {ReplaceEvent} The generated history event to redo @@ -1111,6 +1133,7 @@ RED.history = (function () { * @typedef {object} MultiEvent * @property {"multi"} t The history event type * @property {Array} events An array with events + * @memberof RED.history * * @param {MultiEvent} ev The history event to undo * @returns {MultiEvent} The generated history event to redo @@ -1131,7 +1154,7 @@ RED.history = (function () { } /** - * Called to undo the history event + * Called to undo/redo the history event * * @param {HistoryEvent} ev The history event to undo * @returns {HistoryEvent} The generated history event to redo @@ -1200,21 +1223,50 @@ RED.history = (function () { undoHistory[i].dirty = true; } }, + /** + * Returns a list with undo events. + * @returns {Array} The list with undo events + * @memberof RED.history + */ list: function () { return undoHistory; }, + /** + * Returns a list with redo events. + * @returns {Array} The list with redo events + * @memberof RED.history + */ listRedo: function () { return redoHistory; }, + /** + * Returns the size of the list with redo events. + * @returns {number} The size of the redo list + * @memberof RED.history + */ depth: function () { return undoHistory.length; }, + /** + * Pushes an event to the History. This event can be undo by + * calling {@link RED.history.pop pop}. + * @param {HistoryEvent} ev The event to push + * @returns {void} + * @memberof RED.history + */ push: function (ev) { undoHistory.push(ev); redoHistory = []; RED.menu.setDisabled("menu-item-edit-undo", false); RED.menu.setDisabled("menu-item-edit-redo", true); }, + /** + * Called to undo an event. + * Takes the last event of the undo list, do undo event and adds + * the redo event generated to the redo list. + * @returns {void} + * @memberof RED.history + */ pop: function () { const ev = undoHistory.pop(); const rev = undoEvent(ev); @@ -1226,9 +1278,21 @@ RED.history = (function () { RED.menu.setDisabled("menu-item-edit-undo", undoHistory.length === 0); RED.menu.setDisabled("menu-item-edit-redo", redoHistory.length === 0); }, + /** + * Returns the last event of undo list. + * @returns {HistoryEvent} + * @memberof RED.history + */ peek: function () { return undoHistory[undoHistory.length - 1]; }, + /** + * Replaces the last event of undo list by the event given + * in parameter. + * @param {HistoryEvent} ev The event to replace + * @returns {void} + * @memberof RED.history + */ replace: function (ev) { if (undoHistory.length === 0) { RED.history.push(ev); @@ -1236,12 +1300,24 @@ RED.history = (function () { undoHistory[undoHistory.length - 1] = ev; } }, + /** + * Clears the undo list and redo list. + * @returns {void} + * @memberof RED.history + */ clear: function () { undoHistory = []; redoHistory = []; RED.menu.setDisabled("menu-item-edit-undo", true); RED.menu.setDisabled("menu-item-edit-redo", true); }, + /** + * Called to redo an event. + * Takes the last event of the redo list, do redo event and adds + * the undo event generated to the undo list. + * @returns {void} + * @memberof RED.history + */ redo: function () { const ev = redoHistory.pop(); From d1001f6be4f39e1e45ce1ad92cbe83589efdc935 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:31:39 +0100 Subject: [PATCH 6/6] Move types definition to global scope --- .../@node-red/editor-client/src/js/history.js | 384 ++++++++++-------- 1 file changed, 215 insertions(+), 169 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 5f719cc45..711500348 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -14,33 +14,224 @@ * limitations under the License. **/ +/** + * TODO: Import types from RED.nodes + * @typedef {object} Node + * @typedef {{ source: Node; sourcePort: number; target: Node; }} Link + * @typedef {Node & { env?: Array; }} Group + * @typedef {Node & {}} Junction + * @typedef {Node & { in: Array; out: Array; + * instances: Array; env?: Array; }} Subflow + * @typedef {Node & { type: "subflow"; direction: "in"|"out"; }} SubflowNode + * @typedef {Node & {}} Workspace + */ + +/** + * @typedef {object} AddEvent + * @property {"add"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [groups] An array with added groups + * @property {Array} [junctions] An array with added junctions + * @property {Array} [links] An array with added links + * @property {Array} [nodes] An array with added node ids + * @property {Array} [removedLinks] An array with removed links + * @property {{ id: string, changed: boolean | undefined, + * instances: Array }} [subflow] + * @property {Array} [subflows] An array with added subflows (tabs) + * @property {Array} [workspaces] An array with added workspaces + * @memberof RED.history + * + * @typedef {object} AddToGroupEvent + * @property {"addToGroup"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Group} group The group in which remove nodes + * @property {Array | Node} [nodes] An array of nodes or one node + * to remove from the group + * @property {boolean} [reparent] Either to re-add to parent group + * @memberof RED.history + * + * @typedef {object} CreateGroupEvent + * @property {"createGroup"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [groups] An array with groups to remove + * @memberof RED.history + * + * @typedef {object} CreateSubflowEvent + * @property {"createSubflow"} t The history event type + * @property {string} activeWorkspace The id of the active workspace + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [links] An array with added links (during + * conversion to Subflow - links inside the subflow) + * @property {Array} [nodes] An array with subflow node ids + * @property {Array} [removedLinks] An array with removed links + * (during conversion to Subflow - links from active workspace) + * @property {{ subflow: Subflow, offsetX: number | undefined, + * offsetY: number | undefined }} subflow The subflow created to delete + * @memberof RED.history + * + * @typedef {object} DeleteEvent + * @property {"delete"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {Record} [changes] An object with changes. + * The key is the id of a node and the value is an object with the changes + * to apply + * @property {Array} [createdLinks] An array with created links + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [groups] An array with removed groups + * @property {Array} [junctions] An array with removed junctions + * @property {Array} [links] An array with removed links + * @property {Array} [nodes] An array with removed nodes + * @property {{ id: string | undefined, instances: Array | undefined, + * status: string | undefined }} [subflow] + * @property {Array} [subflowInputs] An array with removed + * subflow input + * @property {Array} [subflowOutputs] An array with removed + * subflow outputs + * @property {Array} [subflows] An array with removed subflows (tabs) + * @property {Array} [workspaces] An array with removed workspace + * @memberof RED.history + * + * @typedef {object} DeleteSubflowEvent + * @property {"deleteSubflow"} t The history event type + * @property {string} activeWorkspace The id of the active workspace + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {Array} [createdLinks] An array with added links + * (during undo conversion to Subflow - links from active workspace) + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [links] An array with removed links (during + * undo conversion to Subflow - links inside the subflow) + * @property {Array} [movedNodes] An array with nodes to move to the + * subflow to create + * @property {{ subflow: Subflow, offsetX: number | undefined, + * offsetY: number | undefined }} subflow The deleted subflow to create + * @property {Array} [subflows] An array with subflow nodes + * (redo conversion to subflow) + * @memberof RED.history + * + * @typedef {object} EditEvent + * @property {"edit"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} changed The changed node state before modifications + * @property {object} changes An object with previous node properties value + * @property {Array} [createdLinks] An array with links to create (redo) + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [links] An array with removed links + * @property {Node} node The current node/subflow + * @property {object} [outputMap] + * @property {{ instances: Array | undefined, + * inputCount: number | undefined, outputCount: number | undefined, + * status: string | undefined }} [subflow] Subflow properties + * @memberof RED.history + * + * @typedef {object} MoveEvent + * @property {"move"} t The history event type + * @property {Group} [addToGroup] The group in which the nodes were added + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [links] + * @property {Array<{ n: Node, ox: number, oy: number, dx: number, + * dy: number }>} nodes An array with nodes moved + * @property {Array} [removedLinks] + * @property {Group} [removeFromGroup] The group in which the nodes were + * removed + * @memberof RED.history + * + * @typedef {object} MultiEvent + * @property {"multi"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} events An array with events + * @memberof RED.history + * + * @typedef {object} RemoveFromGroupEvent + * @property {"removeFromGroup"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Group} group The group in which add nodes + * @property {Array | Node} [nodes] An array of nodes or one node + * to add to the group + * @property {boolean} [reparent] Either to re-add to parent group + * @memberof RED.history + * + * @typedef {object} ReorderEvent + * @property {"reorder"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {{ from: string, to: string, z: string }} [nodes] + * @property {object} [workspaces] + * @property {Array} workspaces[].from Ordered array of workspace ids + * @property {Array} workspaces[].to Ordered array of workspace ids + * @memberof RED.history + * + * @typedef {object} CompleteReplaceEvent + * @property {"replace"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {Record} changed An object with a node id + * as key and the node changed property as value + * @property {true} complete Either the {@link ReplaceEvent.config} + * property contains the complete flows + * @property {Array} config An array with the complete flows + * @property {boolean} dirty The dirty state before replacement + * @property {Record} moved An object with a node id + * as key and the node moved property as value + * @property {string} rev A revision version + * @memberof RED.history + * + * @typedef {object} IncompleteReplaceEvent + * @property {"replace"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {false} [complete] Either the {@link ReplaceEvent.config} + * property contains the complete flows + * @property {Array} config An array with config nodes and/or + * subflow definitions to replace. + * @property {boolean} dirty Whether or not the workspace is dirty + * @memberof RED.history + * + * @typedef {CompleteReplaceEvent|IncompleteReplaceEvent} ReplaceEvent + * @memberof RED.history + * + * @typedef {object} UngroupEvent + * @property {"ungroup"} t The history event type + * @property {(ev: HistoryEvent) => void} [callback] a function called after + * the undo/redo event + * @property {boolean} dirty Whether or not the workspace is dirty + * @property {Array} [groups] An array with groups to create + * @memberof RED.history + * + * @typedef {"add"|"delete"|"edit"|"move"|"multi"|"reorder"|"replace"| + * "createSubflow"|"deleteSubflow"|"addToGroup"|"createGroup"|"ungroup"| + * "removeFromGroup"} HistoryType + * + * @typedef { AddEvent | DeleteEvent | EditEvent | MoveEvent | MultiEvent | + * ReorderEvent | ReplaceEvent | CreateSubflowEvent | DeleteSubflowEvent | + * AddToGroupEvent | CreateGroupEvent | UngroupEvent | RemoveFromGroupEvent + * } HistoryEvent + * @memberof RED.history + */ + /** * An API for undo / redo history buffer * @namespace RED.history */ RED.history = (function () { - /** - * GLOBAL NOTE: Handle event and users ONLY for edit/change event - * TODO: Import types from RED.nodes - * - * @typedef {object} Node - * @typedef {{ source: Node; sourcePort: number; target: Node; }} Link - * @typedef {Node & { env?: Array; }} Group - * @typedef {Node & {}} Junction - * @typedef {Node & { in: Array; out: Array; - * instances: Array; env?: Array; }} Subflow - * @typedef {Node & { type: "subflow"; direction: "in"|"out"; }} SubflowNode - * @typedef {Node & {}} Workspace - * - * @typedef {"add"|"delete"|"edit"|"move"|"multi"|"reorder"|"replace"| - * "createSubflow"|"deleteSubflow"|"addToGroup"|"createGroup"|"ungroup"| - * "removeFromGroup"} HistoryType - * @typedef {{ t: HistoryType; dirty: boolean; callback?: (ev: HistoryEvent) => void; } - * & (AddEvent | DeleteEvent | EditEvent | MoveEvent | MultiEvent | ReorderEvent | - * ReplaceEvent | CreateSubflowEvent | DeleteSubflowEvent | AddToGroupEvent | - * CreateGroupEvent | UngroupEvent | RemoveFromGroupEvent) } HistoryEvent - * @memberof RED.history - */ + // GLOBAL NOTE: Handle event and users ONLY for edit/change event /** * @type {Array} @@ -72,19 +263,6 @@ RED.history = (function () { /** * Called on the `add` history event * - * @typedef {object} AddEvent - * @property {"add"} t The history event type - * @property {Array} [groups] An array with added groups - * @property {Array} [junctions] An array with added junctions - * @property {Array} [links] An array with added links - * @property {Array} [nodes] An array with added node ids - * @property {Array} [removedLinks] An array with removed links - * @property {{ id: string, changed: boolean | undefined, - * instances: Array }} [subflow] - * @property {Array} [subflows] An array with added subflows (tabs) - * @property {Array} [workspaces] An array with added workspaces - * @memberof RED.history - * * @param {AddEvent} ev The history event to undo * @param {object} [modifiedTabs] * @returns {DeleteEvent} The generated history event to redo @@ -220,26 +398,6 @@ RED.history = (function () { /** * Called on the `delete` history event * - * @typedef {object} DeleteEvent - * @property {"delete"} t The history event type - * @property {Record} [changes] An object with changes. - * The key is the id of a node and the value is an object with the changes - * to apply - * @property {Array} [createdLinks] An array with created links - * @property {Array} [groups] An array with removed groups - * @property {Array} [junctions] An array with removed junctions - * @property {Array} [links] An array with removed links - * @property {Array} [nodes] An array with removed nodes - * @property {{ id: string | undefined, instances: Array | undefined, - * status: string | undefined }} [subflow] - * @property {Array} [subflowInputs] An array with removed - * subflow input - * @property {Array} [subflowOutputs] An array with removed - * subflow outputs - * @property {Array} [subflows] An array with removed subflows (tabs) - * @property {Array} [workspaces] An array with removed workspace - * @memberof RED.history - * * @param {DeleteEvent} ev The history event to undo * @param {object} [modifiedTabs] * @returns {AddEvent} The generated history event to redo @@ -440,17 +598,6 @@ RED.history = (function () { /** * Called on the `move` history event * - * @typedef {object} MoveEvent - * @property {"move"} t The history event type - * @property {Group} [addToGroup] The group in which the nodes were added - * @property {Array} [links] - * @property {Array<{ n: Node, ox: number, oy: number, dx: number, - * dy: number }>} nodes An array with nodes moved - * @property {Array} [removedLinks] - * @property {Group} [removeFromGroup] The group in which the nodes were - * removed - * @memberof RED.history - * * @param {MoveEvent} ev The history event to undo * @returns {MoveEvent} The generated history event to redo */ @@ -508,18 +655,6 @@ RED.history = (function () { /** * Called on the `createSubflow` history event * - * @typedef {object} CreateSubflowEvent - * @property {"createSubflow"} t The history event type - * @property {string} activeWorkspace The id of the active workspace - * @property {Array} [links] An array with added links (during - * conversion to Subflow - links inside the subflow) - * @property {Array} [nodes] An array with subflow node ids - * @property {Array} [removedLinks] An array with removed links - * (during conversion to Subflow - links from active workspace) - * @property {{ subflow: Subflow, offsetX: number | undefined, - * offsetY: number | undefined }} subflow The subflow created to delete - * @memberof RED.history - * * @param {CreateSubflowEvent} ev The history event to undo * @returns {DeleteSubflowEvent} The generated history event to redo */ @@ -582,21 +717,6 @@ RED.history = (function () { /** * Called on the `deleteSubflow` history event * - * @typedef {object} DeleteSubflowEvent - * @property {"deleteSubflow"} t The history event type - * @property {string} activeWorkspace The id of the active workspace - * @property {Array} [createdLinks] An array with added links - * (during undo conversion to Subflow - links from active workspace) - * @property {Array} [links] An array with removed links (during - * undo conversion to Subflow - links inside the subflow) - * @property {Array} [movedNodes] An array with nodes to move to the - * subflow to create - * @property {{ subflow: Subflow, offsetX: number | undefined, - * offsetY: number | undefined }} subflow The deleted subflow to create - * @property {Array} [subflows] An array with subflow nodes - * (redo conversion to subflow) - * @memberof RED.history - * * @param {DeleteSubflowEvent} ev The history event to undo * @returns {CreateSubflowEvent} The generated history event to redo */ @@ -665,14 +785,6 @@ RED.history = (function () { /** * Called on the `reorder` history event * - * @typedef {object} ReorderEvent - * @property {"reorder"} t The history event type - * @property {{ from: string, to: string, z: string }} [nodes] - * @property {object} [workspaces] - * @property {Array} workspaces[].from Ordered array of workspace ids - * @property {Array} workspaces[].to Ordered array of workspace ids - * @memberof RED.history - * * @param {ReorderEvent} ev The history event to undo * @returns {ReorderEvent} The generated history event to redo */ @@ -706,11 +818,6 @@ RED.history = (function () { /** * Called on the `createGroup` history event * - * @typedef {object} CreateGroupEvent - * @property {"createGroup"} t The history event type - * @property {Array} [groups] An array with groups to remove - * @memberof RED.history - * * @param {CreateGroupEvent} ev The history event to undo * @returns {UngroupEvent} The generated history event to redo */ @@ -735,11 +842,6 @@ RED.history = (function () { /** * Called on the `ungroup` history event * - * @typedef {object} UngroupEvent - * @property {"ungroup"} t The history event type - * @property {Array} [groups] An array with groups to create - * @memberof RED.history - * * @param {UngroupEvent} ev The history event to undo * @returns {CreateGroupEvent} The generated history event to redo */ @@ -775,14 +877,6 @@ RED.history = (function () { /** * Called on the `addToGroup` history event * - * @typedef {object} AddToGroupEvent - * @property {"addToGroup"} t The history event type - * @property {Group} group The group in which remove nodes - * @property {Array | Node} [nodes] An array of nodes or one node - * to remove from the group - * @property {boolean} [reparent] Either to re-add to parent group - * @memberof RED.history - * * @param {AddToGroupEvent} ev The history event to undo * @returns {RemoveFromGroupEvent} The generated history event to redo */ @@ -806,14 +900,6 @@ RED.history = (function () { /** * Called on the `removeFromGroup` history event * - * @typedef {object} RemoveFromGroupEvent - * @property {"removeFromGroup"} t The history event type - * @property {Group} group The group in which add nodes - * @property {Array | Node} [nodes] An array of nodes or one node - * to add to the group - * @property {boolean} [reparent] Either to re-add to parent group - * @memberof RED.history - * * @param {RemoveFromGroupEvent} ev The history event to undo * @returns {AddToGroupEvent} The generated history event to redo */ @@ -837,19 +923,6 @@ RED.history = (function () { /** * Called on the `edit` history event * - * @typedef {object} EditEvent - * @property {"edit"} t The history event type - * @property {boolean} changed The changed node state before modifications - * @property {object} changes An object with previous node properties value - * @property {Array} [createdLinks] An array with links to create (redo) - * @property {Array} [links] An array with removed links - * @property {Node} node The current node/subflow - * @property {object} [outputMap] - * @property {{ instances: Array | undefined, - * inputCount: number | undefined, outputCount: number | undefined, - * status: string | undefined }} [subflow] Subflow properties - * @memberof RED.history - * * @param {EditEvent} ev The history event to undo * @returns {EditEvent} The generated history event to redo */ @@ -1096,30 +1169,6 @@ RED.history = (function () { /** * Called on the `replace` history event * - * @typedef {object} CompleteReplaceEvent - * @property {"replace"} t The history event type - * @property {Record} changed An object with a node id - * as key and the node changed property as value - * @property {true} complete Either the {@link ReplaceEvent.config} - * property contains the complete flows - * @property {Array} config An array with the complete flows - * @property {boolean} dirty The dirty state before replacement - * @property {Record} moved An object with a node id - * as key and the node moved property as value - * @property {string} rev A revision version - * @memberof RED.history - * - * @typedef {object} IncompleteReplaceEvent - * @property {"replace"} t The history event type - * @property {false} [complete] Either the {@link ReplaceEvent.config} - * property contains the complete flows - * @property {Array} config An array with config nodes and/or - * subflow definitions to replace. - * @memberof RED.history - * - * @typedef {CompleteReplaceEvent|IncompleteReplaceEvent} ReplaceEvent - * @memberof RED.history - * * @param {ReplaceEvent} ev The history event to undo * @returns {ReplaceEvent} The generated history event to redo */ @@ -1199,11 +1248,6 @@ RED.history = (function () { /** * Called on the `multi` history event * - * @typedef {object} MultiEvent - * @property {"multi"} t The history event type - * @property {Array} events An array with events - * @memberof RED.history - * * @param {MultiEvent} ev The history event to undo * @returns {MultiEvent} The generated history event to redo */ @@ -1403,3 +1447,5 @@ RED.history = (function () { } }; })(); + +module.exports = {}