xuhongxu96 / editflow

DAG workflow editor
4 stars 0 forks source link

Undo and Redo #7

Closed xuhongxu96 closed 4 years ago

xuhongxu96 commented 4 years ago

Support redo and undo. Support keyboard shortcuts.

SerinaJingyiLu commented 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 :(

xuhongxu96 commented 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 :(

You can try useTraceUpdate to see which props/states are changing for every rendering.

xuhongxu96 commented 4 years ago

@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.

xuhongxu96 commented 4 years ago

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.

xuhongxu96 commented 4 years ago

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.

xuhongxu96 commented 4 years ago

Also add undo/redo support for add/delete node/edge. I think we can close this issue.