Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Kelin2025/effector-history
Utility library that implements undo/redo feature for you
https://github.com/Kelin2025/effector-history
Last synced: about 2 months ago
JSON representation
Utility library that implements undo/redo feature for you
- Host: GitHub
- URL: https://github.com/Kelin2025/effector-history
- Owner: Kelin2025
- License: mit
- Created: 2021-12-23T16:06:04.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-03-13T12:26:38.000Z (almost 2 years ago)
- Last Synced: 2024-10-28T10:23:15.903Z (about 2 months ago)
- Language: TypeScript
- Size: 2.86 MB
- Stars: 27
- Watchers: 2
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome - effector-history - Utility library that implements undo/redo feature for you. (Packages)
README
# Effector History
Utility library that implements undo/redo feature for you
## Installation
```bash
pnpm add effector-history
```## Usage
```ts
import { createHistory } from "effector-history";const right = createEvent();
const up = createEvent();const $x = createStore(0);
const $y = createStore(0);$x.on(right, (x) => x + 1);
$y.on(up, (y) => y + 1);
const history = createHistory({
source: {
x: $x,
y: $y,
},
maxLength: 20,
});combine([$x, $y]).watch(console.log);
up(); // [0, 1]
up(); // [0, 2]
right(); // [1, 2]
up(); // [1, 3]history.undo(); // [1, 2]
history.undo(); // [0, 2]
history.redo(); // [1, 2]
```## Customization
### `clock` property
Sometimes you need to add records only on a specific event.
For example, you have draggable blocks, and you want to push to history only whenever drag is ended.
For such cases you can use `clock` property:```ts
createHistory({
source: {
x: $x,
y: $y,
},
// Only these events will trigger history
clock: [dragEnded],
maxLength: 20,
});
```Keep in mind that store updates **won't** push to history in this case, so it can cause data incosistency
### `serialize` property
If you don't want to serialize history (e.g. you store custom instances and use SSR), you can add `serialize` property:
```ts
createHistory({
source: {
socketInstance: $socketInstance,
},
serialize: "ignore",
});
```### Strategies
Imagine that you have a custom editor with its own history logic. And you want to treat typing in text field as a single history record.
You can setup custom strategy for a certain `clock`
```ts
import { createHistory, replaceRepetitiveStrategy } from "effector-history";const history = createHistory({
source: {
text: $text,
attachments: $attachments,
},
clock: [textChanged, attachmentAdded, attachmentRemoved],
strategies: new Map().set(textChanged, replaceRepetitiveStrategy),
});attachmentAdded(file);
textChanged("f");
textChanged("fo");
textChanged("foo");history.$history.getState();
/*
[
{ text: '', file: null },
{ text: '', file: file },
{ text: 'foo', file: file } // <- Batched in one
]
*/
```Built-in strategies:
- `pushStrategy` (default) - always pushes to history, no matter what
- `replaceRepetitiveStrategy` - if the current record came from the same trigger, the current record will be replaced. Otherwise, the new one will be pushed
- `skipDuplicatesStrategy` - if the trigger & payload are the same, don't do anything. Push new record, otherwiseIf you want to make a custom strategy, you can use `customStrategy` helper
```ts
import { createHistory, customStrategy } from "effector-history";const buttonTextChanged = createEvent<{ idx: number; text: string }>();
// List of parameters
// trigger - Unit that caused history trigger
// payload - Payload of `trigger`
// curTrigger - Unit that caused currently active history record
// curPayload - Payload of `curTrigger`
// curRecord - Currently active history record
const buttonTextChangedStrategy = customStrategy<{ idx: number; text: string }>({
check: ({ trigger, curTrigger, payload, curPayload }) => {
// If trigger is different, push a new record
if (trigger !== curTrigger) {
return "push";
}
// If changing different button, push a new record
if (payload.idx !== curPayload.idx) {
return "push";
}
// Otherwise replace the current one
return "replace";
},
});const history = createHistory({
source: {
buttons: $buttons,
},
clock: [buttonTextChanged],
strategies: new Map().set(butonTextChanged, buttonTextChangedStrategy),
});
```If you want to merge multiple maps of strategies, you can use `mergeStrategiesMaps` helper:
```ts
import { pushStrategy, skipDuplicatesStrategy, mergeStrategiesMaps } from "effector-history";const textFieldStrategies = new Map().set(textFieldBlurred, skipDuplicatesStrategy);
const buttonsStrategies = new Map().set(buttonAdded, pushStrategy).set(buttonRemoved, pushStrategy);
const optionsStrategy = mergeStrategiesMaps([textFieldStrategies, buttonsStrategies]);
```## API Reference
```ts
history.$history; // All history records
history.$canUndo; // `true` if can undo, `false` otherwise
history.$canRedo; // `true` if can redo, `false` otherwise
history.$curIndex; // Index of currently active history index
history.$curRecord; // Current history record
history.$length; // Amount of records in history (min. 1)history.undo(); // Undo
history.redo(); // Redo
history.clear(); // Clear history
history.push(data); // Manually push something to history
history.replace(data); // Manually replace something in history
```## TODO
- [ ] Support `createHistory` without `source`