Skip to content

Commit b8d3d85

Browse files
committed
feat(history): add time-traveling feature
1 parent b614603 commit b8d3d85

File tree

2 files changed

+201
-120
lines changed

2 files changed

+201
-120
lines changed

README.md

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class App {
9292
// this is the single point of data subscription, the state inside the component will be automatically updated
9393
// no need to take care of manually handling that. This will also update all subcomponents
9494
this.store.state.subscribe(
95-
state => this.state = state
95+
(state: State) => this.state = state
9696
);
9797
}
9898
}
@@ -127,9 +127,90 @@ const greetingAction = (state: State, greetingTarget: string) => {
127127
this.store.dispatch(greetingAction, "zewa666");
128128
```
129129

130+
## Undo / Redo support
131+
If you need to keep track of the history of states you can pass a third parameter to the Store initialization with the value of `true` to setup the store to work on a `StateHistory` vs `State` model.
132+
133+
```typescript
134+
export function configure(aurelia: Aurelia) {
135+
aurelia.use
136+
.standardConfiguration()
137+
.feature('resources');
138+
139+
...
140+
141+
const initialState: State = {
142+
frameworks: ["Aurelia", "React", "Angular"]
143+
};
144+
145+
aurelia.use.plugin("aurelia-store", initialState, true); // <----- REGISTER THE PLUGIN WITH HISTORY SUPPORT
146+
147+
aurelia.start().then(() => aurelia.setRoot());
148+
}
149+
```
150+
151+
Now when you subscribe to new state changes instead of a simple State you'll get a StateHistory<State> object returned:
152+
153+
```typescript
154+
attached() {
155+
this.store.state.subscribe(
156+
(state: StateHistory<State>) => this.state = state
157+
);
158+
}
159+
```
160+
161+
A state history is an interface defining all past and future states as arrays of these plus a currently present one.
162+
```typescript
163+
// aurelia-store -> history.ts
164+
export interface StateHistory<T> {
165+
past: T[];
166+
present: T;
167+
future: T[];
168+
}
169+
```
170+
171+
Now keep in mind that every action will receive a `StateHistory<T>` as input and should return a new `StateHistory<T>`:
172+
```typescript
173+
const greetingAction = (currentState: StateHistory<State>, greetingTarget: string) => {
174+
return Object.assign(
175+
{},
176+
currentState,
177+
{
178+
past: [...currentState.past, currentState.present],
179+
present: { target: greetingTarget },
180+
future: []
181+
}
182+
);
183+
}
184+
```
185+
186+
### Next StateHistory creation helper
187+
Looking back at how you have to create the next StateHistory, you can reduce the work by using the `nextStateHistory` helper function. This will simply move the currently present state to the past, place you new one and remove the future states.
188+
189+
```typescript
190+
import { nextStateHistory } from "aurelia-store";
191+
192+
const greetingAction = (currentState: StateHistory<State>, greetingTarget: string) => {
193+
return nextStateHistory(currentState, { target: greetingTarget });
194+
}
195+
```
196+
197+
### Navigating through history
198+
In order to do state time-travelling you can import the pre-registered action `jump` and pass it either a positive number for traveling into the future or a negative for travelling to past states.
199+
200+
```typescript
201+
import { jump } from "aurelia-store";
202+
203+
...
204+
// Go back one step in time
205+
store.dispatch(jump, -1);
206+
207+
// Move forward one step to future
208+
store.dispatch(jump, 1);
209+
210+
```
130211

131212
## Async actions
132-
You may also register actions which resolve the newly created state with a promise.
213+
You may also register actions which resolve the newly created state with a promise. Same applies for history enhanced Stores. Just make sure the all past/present/future states by themselves are synchronous values.
133214

134215

135216
## Acknowledgement

package.json

Lines changed: 118 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,118 @@
1-
{
2-
"name": "aurelia-store",
3-
"version": "0.3.0",
4-
"description": "Aurelia single state store based on RxJS",
5-
"keywords": [
6-
"rxjs",
7-
"store",
8-
"behaviorsubject",
9-
"aurelia"
10-
],
11-
"main": "dist/commonjs/aurelia-store.js",
12-
"typings": "dist/commonjs/aurelia-store.d.ts",
13-
"scripts": {
14-
"lint": "cross-env tslint --project tsconfig.json",
15-
"pretest": "cross-env npm run lint",
16-
"test": "cross-env jest",
17-
"test-watch": "concurrently \"tsc --watch\" \"jest --watch\"",
18-
"build:amd": "cross-env tsc --outDir dist/amd --module amd",
19-
"build:commonjs": "cross-env tsc --outDir dist/commonjs --module commonjs",
20-
"build:es2015": "cross-env tsc --outDir dist/es2015 --module es2015",
21-
"build:native-modules": "cross-env tsc --outDir dist/native-modules --module es2015",
22-
"build:system": "cross-env tsc --outDir dist/system --module system",
23-
"prebuild": "cross-env rimraf dist",
24-
"postbuild": "gulp",
25-
"build": "concurrently \"npm run build:amd\" \"npm run build:commonjs\" \"npm run build:es2015\" \"npm run build:native-modules\" \"npm run build:system\"",
26-
"prepare-release": "cross-env npm run build && semantic-release pre && npm publish && semantic-release post"
27-
},
28-
"jest": {
29-
"modulePaths": [
30-
"<rootDir>/src",
31-
"<rootDir>/node_modules"
32-
],
33-
"moduleFileExtensions": [
34-
"js",
35-
"json",
36-
"ts"
37-
],
38-
"transform": {
39-
"^.+\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js",
40-
"^.+\\.(scss|css)$": "<rootDir>/node_modules/jest-css-modules"
41-
},
42-
"testRegex": "\\.spec\\.(ts|js)x?$",
43-
"setupFiles": [
44-
"<rootDir>/test/jest-pretest.ts"
45-
],
46-
"testEnvironment": "node",
47-
"moduleNameMapper": {
48-
"aurelia-(.*)": "<rootDir>/node_modules/aurelia-$1",
49-
"^.+\\.(css)$": "<rootDir>/test/jest-css-stub.js"
50-
},
51-
"collectCoverage": true,
52-
"collectCoverageFrom": [
53-
"src/**/*.{js,ts}",
54-
"!**/*.spec.{js,ts}",
55-
"!**/node_modules/**",
56-
"!**/test/**"
57-
],
58-
"coverageDirectory": "<rootDir>/test/coverage-jest",
59-
"coveragePathIgnorePatterns": [
60-
"/node_modules/"
61-
],
62-
"coverageReporters": [
63-
"json",
64-
"lcov",
65-
"text",
66-
"html"
67-
],
68-
"mapCoverage": true
69-
},
70-
"repository": {
71-
"type": "git",
72-
"url": "git+https://github.com/zewa666/aurelia-store.git"
73-
},
74-
"author": "Vildan Softic <zewa666@gmail.com>",
75-
"license": "MIT",
76-
"homepage": "https://github.com/zewa666/aurelia-store#readme",
77-
"bugs": {
78-
"url": "https://github.com/zewa666/aurelia-store/issues"
79-
},
80-
"jspm": {
81-
"registry": "npm",
82-
"jspmPackage": true,
83-
"main": "aurelia-store",
84-
"format": "cjs",
85-
"directories": {
86-
"dist": "dist/commonjs"
87-
}
88-
},
89-
"dependencies": {
90-
"aurelia-dependency-injection": "^1.3.1",
91-
"aurelia-framework": "^1.1.2",
92-
"aurelia-logging": "^1.3.1",
93-
"aurelia-pal": "^1.3.0",
94-
"rxjs": "^5.5.2"
95-
},
96-
"devDependencies": {
97-
"@types/jest": "^21.1.4",
98-
"@types/node": "^8.0.0",
99-
"aurelia-bootstrapper": "^2.1.1",
100-
"aurelia-loader-nodejs": "^1.0.1",
101-
"aurelia-pal-browser": "^1.2.1",
102-
"aurelia-pal-nodejs": "^1.0.0-beta.2.0.0",
103-
"aurelia-polyfills": "^1.2.1",
104-
"aurelia-testing": "^1.0.0-beta.3.0.1",
105-
"concurrently": "^3.4.0",
106-
"cross-env": "^5.0.1",
107-
"gulp": "^3.9.1",
108-
"gulp-sass": "^3.1.0",
109-
"jest": "^21.2.1",
110-
"jest-css-modules": "^1.1.0",
111-
"node-sass": "^4.5.3",
112-
"rimraf": "^2.6.1",
113-
"semantic-release": "^6.3.6",
114-
"ts-jest": "^21.1.3",
115-
"tslint": "^5.4.3",
116-
"typescript": "^2.6.1"
117-
}
118-
}
1+
{
2+
"name": "aurelia-store",
3+
"version": "0.4.0",
4+
"description": "Aurelia single state store based on RxJS",
5+
"keywords": [
6+
"rxjs",
7+
"store",
8+
"behaviorsubject",
9+
"aurelia"
10+
],
11+
"main": "dist/commonjs/aurelia-store.js",
12+
"typings": "dist/commonjs/aurelia-store.d.ts",
13+
"scripts": {
14+
"lint": "cross-env tslint --project tsconfig.json",
15+
"pretest": "cross-env npm run lint",
16+
"test": "cross-env jest",
17+
"test-watch": "concurrently \"tsc --watch\" \"jest --watch\"",
18+
"build:amd": "cross-env tsc --outDir dist/amd --module amd",
19+
"build:commonjs": "cross-env tsc --outDir dist/commonjs --module commonjs",
20+
"build:es2015": "cross-env tsc --outDir dist/es2015 --module es2015",
21+
"build:native-modules": "cross-env tsc --outDir dist/native-modules --module es2015",
22+
"build:system": "cross-env tsc --outDir dist/system --module system",
23+
"prebuild": "cross-env rimraf dist",
24+
"postbuild": "gulp",
25+
"build": "concurrently \"npm run build:amd\" \"npm run build:commonjs\" \"npm run build:es2015\" \"npm run build:native-modules\" \"npm run build:system\"",
26+
"prepare-release": "cross-env npm run build && semantic-release pre && npm publish && semantic-release post"
27+
},
28+
"jest": {
29+
"modulePaths": [
30+
"<rootDir>/src",
31+
"<rootDir>/node_modules"
32+
],
33+
"moduleFileExtensions": [
34+
"js",
35+
"json",
36+
"ts"
37+
],
38+
"transform": {
39+
"^.+\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js",
40+
"^.+\\.(scss|css)$": "<rootDir>/node_modules/jest-css-modules"
41+
},
42+
"testRegex": "\\.spec\\.(ts|js)x?$",
43+
"setupFiles": [
44+
"<rootDir>/test/jest-pretest.ts"
45+
],
46+
"testEnvironment": "node",
47+
"moduleNameMapper": {
48+
"aurelia-(.*)": "<rootDir>/node_modules/aurelia-$1",
49+
"^.+\\.(css)$": "<rootDir>/test/jest-css-stub.js"
50+
},
51+
"collectCoverage": true,
52+
"collectCoverageFrom": [
53+
"src/**/*.{js,ts}",
54+
"!**/*.spec.{js,ts}",
55+
"!**/node_modules/**",
56+
"!**/test/**"
57+
],
58+
"coverageDirectory": "<rootDir>/test/coverage-jest",
59+
"coveragePathIgnorePatterns": [
60+
"/node_modules/"
61+
],
62+
"coverageReporters": [
63+
"json",
64+
"lcov",
65+
"text",
66+
"html"
67+
],
68+
"mapCoverage": true
69+
},
70+
"repository": {
71+
"type": "git",
72+
"url": "git+https://github.com/zewa666/aurelia-store.git"
73+
},
74+
"author": "Vildan Softic <zewa666@gmail.com>",
75+
"license": "MIT",
76+
"homepage": "https://github.com/zewa666/aurelia-store#readme",
77+
"bugs": {
78+
"url": "https://github.com/zewa666/aurelia-store/issues"
79+
},
80+
"jspm": {
81+
"registry": "npm",
82+
"jspmPackage": true,
83+
"main": "aurelia-store",
84+
"format": "cjs",
85+
"directories": {
86+
"dist": "dist/commonjs"
87+
}
88+
},
89+
"dependencies": {
90+
"aurelia-dependency-injection": "^1.3.1",
91+
"aurelia-framework": "^1.1.2",
92+
"aurelia-logging": "^1.3.1",
93+
"aurelia-pal": "^1.3.0",
94+
"rxjs": "^5.5.2"
95+
},
96+
"devDependencies": {
97+
"@types/jest": "^21.1.4",
98+
"@types/node": "^8.0.0",
99+
"aurelia-bootstrapper": "^2.1.1",
100+
"aurelia-loader-nodejs": "^1.0.1",
101+
"aurelia-pal-browser": "^1.2.1",
102+
"aurelia-pal-nodejs": "^1.0.0-beta.2.0.0",
103+
"aurelia-polyfills": "^1.2.1",
104+
"aurelia-testing": "^1.0.0-beta.3.0.1",
105+
"concurrently": "^3.4.0",
106+
"cross-env": "^5.0.1",
107+
"gulp": "^3.9.1",
108+
"gulp-sass": "^3.1.0",
109+
"jest": "^21.2.1",
110+
"jest-css-modules": "^1.1.0",
111+
"node-sass": "^4.5.3",
112+
"rimraf": "^2.6.1",
113+
"semantic-release": "^6.3.6",
114+
"ts-jest": "^21.1.3",
115+
"tslint": "^5.4.3",
116+
"typescript": "^2.6.1"
117+
}
118+
}

0 commit comments

Comments
 (0)