-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathcanvasDatasetMutationsQueue.ts
158 lines (142 loc) · 6.01 KB
/
canvasDatasetMutationsQueue.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import cloneDeep from 'lodash.clonedeep';
import merge from 'lodash.merge';
import remove from 'lodash.remove';
import { SetterOrUpdater } from 'recoil';
import BaseEdgeData from '../types/BaseEdgeData';
import BaseNodeData from '../types/BaseNodeData';
import { CanvasDataset } from '../types/CanvasDataset';
import { CanvasDatasetMutation } from '../types/CanvasDatasetMutation';
export type ApplyPendingMutationsArgs = {
nodes: BaseNodeData[];
edges: BaseEdgeData[];
mutationsCounter: number;
setCanvasDataset: SetterOrUpdater<CanvasDataset>;
}
export type ApplyPendingMutations = (
{
nodes,
edges,
mutationsCounter,
setCanvasDataset,
}: ApplyPendingMutationsArgs,
) => void;
/**
* Global object initialized once per page.
*
* Contains the queue of mutations that have been done, or are to be done.
*/
export const mutationsQueue: CanvasDatasetMutation[] = [];
/**
* Applies all pending mutations.
*
* Loops over all mutations, only consider those in "pending" state.
* Consolidate all mutations as one update by merging (in order) all mutations together.
* Then, update the canvas dataset (which will refresh the UI).
*
* @param nodes
* @param edges
* @param mutationsCounter
* @param setCanvasDataset
*/
export const applyPendingMutations: ApplyPendingMutations = ({ nodes, edges, mutationsCounter, setCanvasDataset }) => {
// Only consider mutations that are pending
const mutationsToApply = mutationsQueue.filter((mutation: CanvasDatasetMutation) => mutation.status === 'pending');
console.log(`mutationsToApply (${mutationsToApply?.length})`, cloneDeep(mutationsToApply), 'queue:', cloneDeep(mutationsQueue));
if (mutationsToApply?.length > 0) {
const newNodes: BaseNodeData[] = cloneDeep(nodes);
const newEdges: BaseEdgeData[] = cloneDeep(edges);
// Mark all patches as being processed
mutationsToApply.map((mutation: CanvasDatasetMutation) => {
const { id } = mutation;
const patchIndex: number = mutationsQueue.findIndex((mutation: CanvasDatasetMutation) => mutation.id === id);
if (patchIndex >= 0) {
mutationsQueue[patchIndex].status = 'processing';
}
});
console.log(`mutationsToApply (processing (${mutationsToApply?.length}))`, cloneDeep(mutationsToApply));
// Processing all pending patches into one consolidated update
mutationsToApply.map((mutation: CanvasDatasetMutation) => {
const {
elementId,
elementType,
operationType,
changes,
status,
} = mutation;
if (status === 'processing') {
if (elementType === 'node') {
if (operationType === 'patch') {
const nodeToUpdateIndex: number = newNodes.findIndex((node: BaseNodeData) => node.id === elementId);
const existingNode: BaseNodeData | undefined = newNodes.find((node: BaseNodeData) => node?.id === elementId);
const patchedNode: BaseNodeData = {} as BaseNodeData;
if (typeof existingNode !== 'undefined') {
merge(patchedNode, existingNode, changes);
console.log(`Applying patch N°${mutationsCounter}:`, changes, 'to node:', existingNode, 'result:', patchedNode);
newNodes[nodeToUpdateIndex] = patchedNode;
} else {
console.log(`Couldn't find node to patch with id "${nodeToUpdateIndex}".`);
}
} else if (operationType === 'add') {
newNodes.push(changes as BaseNodeData);
} else if (operationType === 'delete') {
remove(newNodes, (node: BaseNodeData) => node?.id === elementId);
} else {
console.error(`Not implemented ${operationType}`);
}
} else if (elementType === 'edge') {
if (operationType === 'patch') {
const edgeToUpdateIndex: number = newEdges.findIndex((edge: BaseEdgeData) => edge.id === elementId);
const existingEdge: BaseEdgeData | undefined = newEdges.find((edge: BaseEdgeData) => edge?.id === elementId);
const patchedEdge: BaseEdgeData = {} as BaseEdgeData;
if (typeof existingEdge !== 'undefined') {
merge(patchedEdge, existingEdge, changes);
console.log(`Applying patch N°${mutationsCounter}:`, changes, 'to edge:', existingEdge, 'result:', patchedEdge);
newEdges[edgeToUpdateIndex] = patchedEdge;
} else {
console.log(`Couldn't find edge to patch with id "${edgeToUpdateIndex}".`);
}
} else if (operationType === 'add') {
newEdges.push(changes as BaseEdgeData);
} else if (operationType === 'delete') {
remove(newEdges, (edges: BaseEdgeData) => edges?.id === elementId);
} else {
console.error(`Not implemented ${elementType}`);
}
} else {
console.error(`Not implemented ${elementType}`);
}
} else {
console.log('The mutation must not be processed. (status !== "processing")', mutation);
}
});
console.log('Saving new dataset (mutations batch)', {
nodes: newNodes,
edges: newEdges,
});
setCanvasDataset({
nodes: newNodes,
edges: newEdges,
});
// Mark all processed patches as having been applied
mutationsToApply.map((mutation: CanvasDatasetMutation) => {
const { id } = mutation;
const patchIndex: number = mutationsQueue.findIndex((mutation: CanvasDatasetMutation) => mutation.id === id);
if (patchIndex >= 0) {
mutationsQueue[patchIndex].status = 'applied';
}
});
// Cleanup mutations that have been applied to avoid memory leak for long-running sessions
setTimeout(() => {
cleanupAppliedPatches();
}, 10000);
}
};
/**
* Remove all mutations that have been applied.
*/
export const cleanupAppliedPatches = () => {
const removedMutations = remove<CanvasDatasetMutation>(mutationsQueue, (mutation: CanvasDatasetMutation) => {
return mutation.status === 'applied';
});
console.debug('Mutations have been purged', removedMutations, 'queue:', mutationsQueue);
};