Closed xuhongxu96 closed 4 years ago
I have almost done this feat. However, I found the render performance is poor with a growing number of nodes. (I suppose it may result from too many re-renders). I will then try to find the solution :(
I have almost done this feat. However, I found the render performance is poor with a growing number of nodes. (I suppose it may result from too many re-renders). I will then try to find the solution :(
You can try useTraceUpdate
to see which props/states are changing for every rendering.
@SerinaJingyiLu I think you are saving full histories of FlowState in a stack, right? This is straightforward.
But I think it may consume much memory and I'm not sure if it could have good perf.
Actually, my initial idea is to record the action
history instead of full FlowState
and write rollback
function for each action
.
As you already started implementation, I think we'd better try your design first and see if the perf is good enough. If not, my design could be a fallback and we can set a lower priority for it.
After I upgrading the immer
, the performance has regressed. It seems immer
fixed some issues but the fix adds up the diff cost.
So, I switched to use immutable
, which is also adopted by draft.js.
Though using immutable
data structures will be so different with vanilla JS data structures, I believe it will provide a more stable and efficient solution.
However, I have to change our codes a lot, and unfortunately, your current undo/redo solution cannot be used then. Really sorry for this. I will write a basic framework for undo/redo features sooner.
I think, in the future, contributors should have more discussions together before implementation.
I've finished the basic framework for undo/redo feature.
Now, I've only added undo/redo support for setNodeLayout
action.
const reducers = {
// ...
stopMovingNodes: (flow: FlowState, action: { cancel: boolean }): ReducerReturnType => {
if (flow.draftNodeLayout.isEmpty()) return;
if (action.cancel) {
flow.set('draftNodeLayout', Map());
return;
}
const undo = reducers.setNodeLayout(flow, {
layouts: Array.from(flow.draftNodeLayout.entries()),
});
flow.set('draftNodeLayout', Map());
return undo;
},
setNodeLayout: (
flow: FlowState,
action: { layouts: [string, Basic.IRect][] }
): ReducerReturnType => {
const oldLayouts = action.layouts.map(([id, _]) => {
return [id, flow.raw.nodes[id].layout] as [string, Basic.IRect];
});
action.layouts.forEach(o => {
const [id, layout] = o;
flow.update('nodeIdQuadTree', u => u.remove(flow.raw.nodes[id].layout, id));
flow.setIn(['raw', 'nodes', id, 'layout'], layout);
flow.update('nodeIdQuadTree', u => u.insert(layout, id));
flow.set('nodeBound', Basic.makeRect(expandRectToContain(flow.nodeBound, layout)));
reducers.updateEdgeStates(flow, { nodeId: id });
});
return {
action: { type: 'setNodeLayout', ...action },
undoFn: undoFlow => {
reducers.setNodeLayout(undoFlow, { layouts: oldLayouts });
},
};
},
}
Reducer action handlers should return a UndoAction
object, which contains the current reducer action
, and an undoFn
method.
Also add undo/redo support for add/delete node/edge. I think we can close this issue.
Support redo and undo. Support keyboard shortcuts.