UNPKG

react-phylogeny-tree-gl

Version:

React integration of phylocanvas.gl. Component and hook for phylogenetic tree visualistion.

179 lines 6.15 kB
import { Defaults } from '@phylocanvas/phylocanvas.gl'; const defaultTrackedProps = []; export function createRedoUndoPlugin(trackedProps = defaultTrackedProps) { return function undoRedoPlugin(tree, decorate) { decorate('init', (delegate, args) => { delegate(...args); initUndoRedo(tree); }); decorate('setProps', (delegate, args) => { const [updater, eventOrigin] = args; if (eventOrigin === undefined || eventOrigin !== 'undoRedo') { const updaterKeys = new Set(Object.keys(updater)); if (trackedProps.some((key) => updaterKeys.has(key))) { addUndo(tree, 'setProps', args, delegate); return; } } delegate(...args); }); decorate('setRoot', (delegate, args) => { const [nodeOrId, props, isUndoRedo] = args; if (isUndoRedo) { delegate(nodeOrId, props); return; } addUndo(tree, 'setRoot', args, delegate); }); decorate('setSource', (delegate, args) => { const [data, original, isUndoRedo] = args; if (isUndoRedo) { delegate(data, original); return; } addUndo(tree, 'setSource', args, delegate); }); }; } function initUndoRedo(tree) { tree.canUndo = canUndo.bind(null, tree); tree.canRedo = canRedo.bind(null, tree); tree.undo = undo.bind(null, tree); tree.redo = redo.bind(null, tree); tree.setProps({ history: initialState }, 'undoRedo'); } const initialState = { past: [], future: [], }; const addUndo = (tree, method, args, delegate) => { const past = tree.props.history.past; const newVal = { method, args }; const current = getPresent(tree.props, newVal); const newHistory = { future: [], past: [...past, current], }; save(tree, newVal, newHistory, delegate); }; const canUndo = (tree) => { return tree.props.history.past.length > 0; }; const canRedo = (tree) => { return tree.props.history.future.length > 0; }; const undo = (tree) => { if (!tree.canUndo) return; const { past, future } = tree.props.history; const previous = past[past.length - 1]; const present = getPresent(tree.props, previous); const newHistory = { past: past.slice(0, past.length - 1), future: [present, ...future], }; save(tree, previous, newHistory); }; const redo = (tree) => { if (!tree.canRedo) return; const { past, future } = tree.props.history; const next = future[0]; const present = getPresent(tree.props, next); const newHistory = { past: [...past, present], future: future.slice(1), }; save(tree, next, newHistory); }; const save = (tree, newVal, newHistory, method) => { callFn(newVal, newHistory, method !== null && method !== void 0 ? method : tree[newVal.method]); if (isMethodVal('setSource', newVal)) { if (newVal.props !== undefined) tree.setProps(Object.assign(Object.assign({}, newVal.props), { history: newHistory }), 'undoRedo'); else setPropsWithourRender(tree, { history: newHistory }); } }; const callFn = (newVal, newHistory, method) => { const args = getArguments(newVal, newHistory); method(...args); }; function setPropsWithourRender(tree, updater) { if (updater) { const newProps = Object.freeze(Object.assign(Object.assign({}, tree.props), updater)); tree.props = newProps; } } function getArguments(newVal, history) { if (isMethodVal('setProps', newVal)) { const [updater] = newVal.args; const args = [Object.assign(Object.assign({}, updater), { history }), 'undoRedo']; return args; } else if (isMethodVal('setRoot', newVal)) { const [nodeOrId, props] = newVal.args; const args = [nodeOrId, Object.assign(Object.assign({}, props), { history }), true]; return args; } else if (isMethodVal('setSource', newVal)) { const [data, original] = newVal.args; const args = [data, original, true]; return args; } throw Error(`getArguments received arg present.method with unsupported value: ${newVal.method}`); } function isMethodVal(methodName, value) { if (value.method === methodName) return true; return false; } function getPresent(props, newVal) { if (isMethodVal('setProps', newVal)) { return { method: newVal.method, args: getSetPropsPresent(props, newVal.args), }; } else if (isMethodVal('setRoot', newVal)) { return { method: newVal.method, args: getSetRootPresent(props, newVal.args), }; } else if (isMethodVal('setSource', newVal)) { const [historyProps, ...args] = getSetSourcePresent(props, newVal.args); return { method: newVal.method, args, props: historyProps, }; } throw Error(`getPresent received argument 'value' with unsupported method: ${newVal.method}`); } function getSetPropsPresent(props, [updater, eventOrigin]) { const present = pick(props, Object.keys(updater)); return [present, eventOrigin]; } function getSetRootPresent(props, [, argsProps]) { var _a; const presentProps = argsProps !== undefined ? pick(props, Object.keys(argsProps)) : undefined; const node = (_a = props.rootId) !== null && _a !== void 0 ? _a : undefined; return [node, presentProps]; } function getSetSourcePresent(props, _args) { const historyProps = pick(props, ['rootId', 'collapsedIds', 'rotatedIds']); return [historyProps, props.source]; } function pick(obj, propNames) { return propNames.reduce((acc, propName) => { if (propName in obj) { acc[propName] = obj[propName]; } else if (propName in Defaults) { acc[propName] = Defaults[propName]; } return acc; }, {}); } //# sourceMappingURL=redoUndo.js.map