react-phylogeny-tree-gl
Version:
React integration of phylocanvas.gl. Component and hook for phylogenetic tree visualistion.
179 lines • 6.15 kB
JavaScript
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