Created
November 18, 2019 15:56
-
-
Save simonrelet/ffe4d77e43ece0ac8fd97744accc3724 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import throttle from 'lodash.throttle' | |
const DEFAULT_HISTORY_CAPACITY = 50 | |
const DEFAULT_PUSH_THROTTLE_TIMEOUT = 500 | |
/** | |
* The object defining the internal state of a History returned by | |
* `History.getState`. | |
* | |
* @template T | |
* @typedef {object} HistoryState | |
* @property {number} index The index of the current value. | |
* @property {T[]} values The values pushed so far. | |
*/ | |
/** | |
* The History object itself. | |
* | |
* @template T | |
* @typedef {object} History | |
* | |
* @property {function(): HistoryState<T>} getState Get the current state of | |
* the History. | |
* | |
* @property {function(T): void} push Push a value in the History. | |
* This new value will also become the current one. | |
* All values that comes after the current index will be lost. | |
* If the capacity is exceeded the oldest value will be lost. | |
* | |
* @property {function(): T} previous Move one value backward and return it. | |
* If the current value is the first one, this function is a nop. | |
* | |
* @property {function(): T} next Move one value forward and return it. | |
* If the current value is the last one, this function is a nop. | |
*/ | |
/** | |
* Create a new History object. | |
* | |
* @template T | |
* @param {T} initialValue The value to use to initialize the History. | |
* @param {object} options | |
* | |
* @param {number} [options.capacity=DEFAULT_HISTORY_CAPACITY] The capacity of | |
* the History (i.e. the maximum number of values it can keep). | |
* | |
* @param {number} [options.pushThrottle=DEFAULT_PUSH_THROTTLE_TIMEOUT] The | |
* minimum amount of time between to values are pushed. | |
* The pushed values are throttled to avoid spamming the History. | |
* | |
* @returns {History<T>} The History object. | |
*/ | |
export function createHistory( | |
initialValue, | |
{ | |
capacity = DEFAULT_HISTORY_CAPACITY, | |
pushThrottle = DEFAULT_PUSH_THROTTLE_TIMEOUT, | |
} = {}, | |
) { | |
let values = [initialValue] | |
let index = 0 | |
const push = throttle(value => { | |
// The previously known futur is now lost. | |
// We don't keep track of parallel universes. | |
// GREAT SCOTT! | |
if (index < values.length - 1) { | |
values = values.slice(0, index + 1) | |
} | |
values = values.concat([value]) | |
index = index + 1 | |
if (values.length > capacity) { | |
values = values.slice(1) | |
index = index - 1 | |
} | |
}, pushThrottle) | |
function previous() { | |
// Make sure any previous `push` are ran before. | |
push.flush() | |
if (index > 0) { | |
index = index - 1 | |
} | |
return values[index] | |
} | |
function next() { | |
// Make sure any previous `push` are ran before. | |
push.flush() | |
if (index < values.length - 1) { | |
index = index + 1 | |
} | |
return values[index] | |
} | |
function getState() { | |
return { index, values } | |
} | |
return { push, previous, next, getState } | |
} | |
const MAC = | |
typeof navigator !== `undefined` | |
? navigator.appVersion.indexOf('Mac') !== -1 | |
: true | |
const SHORTCUT_KEY = MAC ? 'metaKey' : 'ctrlKey' | |
/** | |
* Test whether a keyboard event is an undo event. | |
* | |
* @param {KeyboardEvent} event | |
* @returns {boolean} | |
*/ | |
export function isUndoEvent(event) { | |
// cmd-z / ctrl-z | |
return ( | |
event[SHORTCUT_KEY] && event.key.toLowerCase() === 'z' && !event.shiftKey | |
) | |
} | |
/** | |
* Test whether a keyboard event is a redo event. | |
* | |
* @param {KeyboardEvent} event | |
* @returns {boolean} | |
*/ | |
export function isRedoEvent(event) { | |
const key = event.key.toLowerCase() | |
return ( | |
// cmd-shift-z / ctrl-shift-z | |
(event[SHORTCUT_KEY] && event.shiftKey && key === 'z') || | |
// cmd-y / ctrl-y | |
(event[SHORTCUT_KEY] && key === 'y' && !event.shiftKey) | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment