This is an early attempt to create a simple and comprehensible way to record history of datas or state in Vue applications. It allows to easily undo, redo and travel in time in component or Vuex states.
The goal is to easily provide a way for users to use mistake-friendly web apps (as they're used to use classic softwares), and also to offer them what's so called "optimistic UI" for a better UX.
See below 4 different usage examples.
This is the simplest usage. Eg. in a component, wrap your data mutation in the record
function:
const patch = await record(this.todos, () => {
this.todos.shift(); // Any sync mutation
});
const patch = await record(this.todos, async () => {
this.todos = await api.removeFirstTodo(); // Any async mutation
});
Then revert
this patch:
this.todos = revert(this.todos, patch);
Or reapply
it:
this.todos = reapply(this.todos, patch);
Beware in this case that you may have to manually manage new datas, as todo ids could conflict for example. It's up to the developer to manage it from here. This usage is not limited to reactive datas.
✅ Allow to fine tune the process, flexible enough to use it in complex cases.
🚫 Could be too verbose for simple cases.
Simply import the DejaVue
component and wrap your code in its slot.
<DejaVue v-slot="{ getHistory, getCursor, undo, redo, canUndo, canRedo }" :record.sync="todos" @cursorChanged="anything">
<!-- Use computed and methods as you wish -->
</DejaVue>
✅ Simplest way to manage the history.
🚫 Can't manage how the recorded state is recorded, undone or redone.
Code (example store) & Code (example component)
The DejaVuex
plugin creates a store that subscribes to mutations. It's possible to filter those with the shouldInclude
callback which returns true
or false
given the actual mutation. The namespace
can be used to suffix the created module identifier, like history-todos
. The objectHash
helps the differ to identify objects instead of relying on their index.
export default new Vuex.Store({
plugins: [
DejaVuex({
namespace: 'todos',
limit: 10,
shouldInclude: ({ type }) => type.match('todos/'),
objectHash: (obj) => obj.id,
}),
],
});
✅ Better option if Vuex is already used in the app, flexible enough to record only some mutation
🚫 May be a performance bottleneck on huge states, but can be avoided by using the stop
action and the limit
option.
This component uses DejaVuex internally, but eases its use by automatically creating, registering in the store when created, and unregistering it when destroyed.
<DejaVuex
v-slot="{ getHistory, getCursor, undo, redo, canUndo, canRedo, travel }"
:objectHash="(obj) => obj.id"
:include="({ type }) => type.match('addTodo|removeTodo')"
@cursorChanged="anything"
>
<!-- Use getters and actions as you wish -->
</DejaVuex>
✅ Useful when history is needed only in one place
🚫 As every component it may not be as flexible as using the lib directly
npm install
npm run serve
npm run build
npm run lint