All files / src undo.ts

100% Statements 32/32
83.33% Branches 15/18
100% Functions 5/5
100% Lines 31/31
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 1095x 5x                           5x           15x 15x   15x             15x                         51x   15x   15x 36x   20x   16x 16x     16x   16x 16x     51x                   24x       15x   15x 15x 15x 15x 27x               27x 27x         15x 15x 15x           15x      
import { EMPTY_STATE } from "./constants";
import {
  getConfig,
  pipeActions,
  setConfig,
  updateCanUndoRedo
} from "./utils-undo-redo";
 
/**
 * The Undo function - pushes the latest done mutation to the top of the undone
 * stack by popping the done stack and 'replays' all mutations in the done stack
 *
 * @module store/plugins/undoRedo:undo
 * @function
 */
export default ({
  paths,
  store
}: {
  paths: UndoRedoOptions[];
  store: any;
}) => async (namespace: string) => {
  const config = getConfig(paths)(namespace);
 
  Eif (Object.keys(config).length) {
    /**
     * @var {Array} done - The updated done stack
     * @var {Array} commits - The list of mutations which are undone
     * NB: The reduceRight operation is used to identify the mutation(s) from the
     * top of the done stack to be undone
     */
    const { done, commits } = config.done.reduceRight(
      (
        {
          commits,
          done,
          proceed
        }: {
          commits: Array<Mutation> | [];
          done: Array<Mutation> | [];
          proceed: boolean;
        },
        m: Mutation
      ) => {
        if (!commits.length) {
          // The "topmost" mutation from the done stack
          commits = [m];
          // Do not find more mutations if the mutations does not belong to a group
          proceed = !!m.payload.actionGroup;
        } else if (!proceed) {
          // Unshift the mutation to the done stack
          done = [m, ...done];
        } else {
          const lastUndone = commits[commits.length - 1];
          const { actionGroup } = lastUndone.payload;
          // Unshift to commits if mutation belongs to the same actionGroup,
          // otherwise unshift to the done stack
          proceed =
            m.payload.actionGroup && m.payload.actionGroup === actionGroup;
          commits = [...(proceed ? [m] : []), ...commits];
          done = [...(proceed ? [] : [m]), ...done];
        }
 
        return { done, commits, proceed };
      },
      {
        done: [],
        commits: [],
        proceed: true
      }
    );
 
    // Check if there are any undo callback actions
    const undoCallbacks = commits.map(({ payload }: { payload: any }) => ({
      action: payload.undoCallback ? `${namespace}${payload.undoCallback}` : "",
      payload
    }));
    await pipeActions(store)(undoCallbacks);
 
    const undone = [...config.undone, ...commits];
    config.newMutation = false;
    store.commit(`${namespace}${EMPTY_STATE}`);
    const redoCallbacks = done.map(async (mutation: Mutation) => {
      store.commit(
        mutation.type,
        Array.isArray(mutation.payload)
          ? [...mutation.payload]
          : mutation.payload.constructor(mutation.payload)
      );
 
      // Check if there is an undo callback action
      const { redoCallback } = mutation.payload;
      return {
        action: redoCallback ? `${namespace}${redoCallback}` : "",
        payload: mutation.payload
      };
    });
    await pipeActions(store)(await Promise.all(redoCallbacks));
    config.newMutation = true;
    setConfig(paths)(namespace, {
      ...config,
      done,
      undone
    });
 
    updateCanUndoRedo({ paths, store })(namespace);
  }
};