Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/BeardScript/history-actions
Action manager framework with undo/redo
https://github.com/BeardScript/history-actions
Last synced: 16 days ago
JSON representation
Action manager framework with undo/redo
- Host: GitHub
- URL: https://github.com/BeardScript/history-actions
- Owner: BeardScript
- License: mit
- Created: 2019-01-01T22:23:24.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2019-03-10T09:34:52.000Z (over 5 years ago)
- Last Synced: 2024-10-19T09:18:26.535Z (26 days ago)
- Language: TypeScript
- Size: 14.5 MB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# history-actions
Action manager framework with undo/redo.This lightweight package provides a simple and flexible pattern to define user actions that can be done and undone through the **historyManager**, while letting you keep track of instances within your state, no matter how complex they could be. You can record multiple actions within a single **ChangeLog** and undo them all in one call.
- Simple.
- Lightweight.
- Flexible.# Instructions
## Install
```bash
npm install history-actions --save
```
## Define an Action
An **Action** defines the way a mutation is done and undone. For a better result, you should granulate your actions as much as possible. An **Action** should do one thing only.
An **Action** should not mutate the state of an object, but trigger the mutation within it. In OOP an object should mutate its own state, so even when you set a property from an **Action**, you should be triggering the setter on that property's definition. So it's fair to say that an **Action** is by all means a controller.
Here's an example of an **Action**:
```typescript
import { Action } from 'history-actions';
import { myTestState } from './MyTestState';export class SetHeight extends Action {
private _value: number;
private _undoValue: number;constructor( value: number ) {
super();
this._value = value;
this._undoValue = myTestState.height;
}do() {
myTestState.height = this._value;
}
undo() {
myTestState.height = this._undoValue;
}
// OPTIONAL: by default is defined in the superclass as follows
redo() {
this.do();
}
}```
Let's look at the above code more in-depth:
### The class
Create a class with the name of the action you are defining and make sure it extends Action.
```typescript
import { Action } from 'history-actions';
export class MyAction extends Action {
...
}```
If you are using typescript, which is highly recommended, it will force the implementation of the abstract **do()** and **undo()** methods.
### Properties and constructor
These will vary depending on the **Action**. You are free to provide whatever properties your class will need in order to **do()**, **undo()** and if necessary, to **redo()** an **Action**.
### The *action.do()* method
Here is where you should define how to mutate whatever it is you are mutating.
### The *action.undo()* method
Here is where you define how to reverse whatever was done in the **do()** method.
CAUTION: you should never call this method. Use **historyManager.undo()** instead.
### The *action.redo()* method
Normally you won't need to define this method, since by default it's defined to call **do()** but in case you need additional boilerplate to redo whatever was undone, you should define it.
CAUTION: you should never call this method. Use **historyManager.redo()** instead.
## historyManager
The **historyManager** gives you a simple interface to **record()** multiple actions within a **ChangeLog** that you can **save()** in order to **undo()** it and **redo()** it.
### Example
Now to the fun part.
Once your Actions are defined you should use them, right? Otherwise they'll be sitting there bored, and feeling useless.
Let's see them in action.```typescript
import { historyManager } from 'history-actions';
import { MyAction } from './Actions/MyAction';// Instantiate an
const action = new MyAction();// Record it
historyManager.record(action);// Run it
action.do();
// or action.asyncDo()// Save the you are recording.
historyManager.save();```
After calling **save()**, the next recorded action will be pushed into a new **ChangeLog**.
Alternatively to calling **action.do()** you can call **action.asyncDo()** which is simply an asynchronous wrapper that calls **do()**.
You can record multiple actions in a single **ChangeLog**. That is where this pattern shines. You can record a series of actions and undo them all in one call.
```typescript
import { historyManager } from 'history-actions';
import { MyAction } from './Actions/MyAction';// Instantiate one or more actions
const action1 = new MyAction();
const action2 = new MyAction();// Record them
historyManager.record(action1);
historyManager.record(action2);// Run them
action1.do();
action2.do();// Save the you are recording.
historyManager.save();```
### undo() | redo() | clear()
```typescript
import { historyManager } from 'history-actions';
// Undo the last saved
historyManager.undo()// Redo the last undone
historyManager.redo()// Clear all change logs and reset
historyManager.clear()```
### Utilities
```typescript
import { historyManager } from 'history-actions';
// Returns the last recorded in the current being recorded.
historyManager.getLastRecordedAction();// Returns the current being recorded.
historyManager.getRecording();// Set/Get the max number of undo's allowed (default: 20).
historyManager.maxLogs = 20;// Returns true if an Action has been pushed to the current .
const isRecording: boolean = historyManager.isRecording();```
### What is a *ChangeLog*?
A *ChangeLog* consists on one or more actions that trigger mutations within the state of your app. It's a perceived change for the user which they might want to undo.
The **historyManager** records change logs.
When you **historyManager.record( action )** you are basically adding the action to the current **ChangeLog** being recorded.
When you **historyManager.save()** you are closing the **ChangeLog** being recorded, so that a new one can be created.
**Why not just undoing/redoing a single action?**
Well, let's say you want to **Add( objectInstance )**. Then, you simply create an Action **AddObjectInstance** and call the **Add( objectInstance )** method inside. Let's also asume that you do the same for **Remove()**, and now you want to perform a **Switch()**. So... you could define a **SwitchObjectInstance** Action, or you could reutilize the **Add** and **Remove** you already have, and record them in a single change log. This is just a very simple example of how this pattern scales.