-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(with-history): creat withHistory Function
- Loading branch information
Showing
11 changed files
with
285 additions
and
165 deletions.
There are no files selected for viewing
This file contains 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
6 changes: 1 addition & 5 deletions
6
projects/ngrx-extension/src/lib/patch-state-with-immer/patch-state-with-immer.spec.ts
This file contains 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
112 changes: 112 additions & 0 deletions
112
projects/ngrx-extension/src/lib/with-history/with-history.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { effect } from '@angular/core'; | ||
import { getState, patchState, signalStoreFeature, withHooks, withMethods } from '@ngrx/signals'; | ||
|
||
export const STATE_HISTORY = Symbol('STATE_HISTORY'); | ||
|
||
export type TStateHistory<State> = { | ||
stateVersions: State[]; | ||
currentVersionIndex: number; | ||
}; | ||
|
||
export type Config = { | ||
maxLength?: number; | ||
sync?: boolean; | ||
}; | ||
|
||
// todo next reference | ||
// historiesChangeDetFn?: ( | ||
// store: Prettify<StateSignals<Input['state']> & Input['props'] & Input['methods'] & WritableSignal<Input['state']>>, | ||
// histories: State[], | ||
// ) => void; | ||
|
||
export function withHistory<State extends object>({ maxLength = 100, sync = true }: Config) { | ||
/** このオブジェクトにstateの変更履歴を保存する */ | ||
const stateHistory: { [STATE_HISTORY]: TStateHistory<State> } = { | ||
[STATE_HISTORY]: { | ||
stateVersions: [], | ||
currentVersionIndex: 0, | ||
}, | ||
}; | ||
|
||
/** この関数内でstateを書き換え場合trueとする */ | ||
let dirty = false; | ||
|
||
return signalStoreFeature( | ||
withMethods((store) => ({ | ||
/** この関数を呼び出すことでstateの変更履歴を一つ前に戻す */ | ||
undo() { | ||
// currentVersionIndexが1の時は何もしない | ||
if (stateHistory[STATE_HISTORY].currentVersionIndex <= 1) { | ||
return; | ||
} | ||
|
||
// 現在のバージョンのインデックスを一つ前に戻す | ||
stateHistory[STATE_HISTORY].currentVersionIndex--; | ||
|
||
const { stateVersions, currentVersionIndex } = stateHistory[STATE_HISTORY]; | ||
|
||
// ストアの更新 | ||
dirty = true; | ||
|
||
patchState(store, stateVersions[currentVersionIndex - 1]); | ||
}, | ||
|
||
/** この関数を呼び出すことでstateの変更履歴を一つ進める */ | ||
redo() { | ||
// currentVersionIndexがstateVersionsの長さと同等なら最新なため何もしない | ||
if (stateHistory[STATE_HISTORY].currentVersionIndex >= stateHistory[STATE_HISTORY].stateVersions.length) { | ||
return; | ||
} | ||
|
||
stateHistory[STATE_HISTORY].currentVersionIndex++; | ||
|
||
const { stateVersions, currentVersionIndex } = stateHistory[STATE_HISTORY]; | ||
|
||
// ストアの更新 | ||
dirty = true; | ||
|
||
patchState(store, stateVersions[currentVersionIndex - 1]); | ||
}, | ||
|
||
/** 履歴を削除する */ | ||
clearHistories() { | ||
stateHistory[STATE_HISTORY].stateVersions = stateHistory[STATE_HISTORY].stateVersions.filter( | ||
(_, index) => index + 1 === stateHistory[STATE_HISTORY].currentVersionIndex, | ||
); | ||
stateHistory[STATE_HISTORY].currentVersionIndex = 1; | ||
}, | ||
})), | ||
|
||
withHooks({ | ||
onInit(store) { | ||
if (sync) { | ||
effect(() => | ||
((state) => { | ||
// | ||
if (dirty) { | ||
dirty = false; | ||
return; | ||
} | ||
|
||
// バージョンの管理を行う。 | ||
const { stateVersions, currentVersionIndex } = stateHistory[STATE_HISTORY]; | ||
|
||
// currentVersionIndexが末尾でない場合は、currentVersionIndex以降の履歴を削除し新たに追加を行う | ||
if (stateVersions.length !== currentVersionIndex) { | ||
stateHistory[STATE_HISTORY].stateVersions.splice(currentVersionIndex, stateVersions.length - 1); | ||
} | ||
|
||
// 最大長を超えた場合は先頭を削除 | ||
if (currentVersionIndex >= maxLength) { | ||
stateHistory[STATE_HISTORY].stateVersions.shift(); | ||
} | ||
|
||
stateHistory[STATE_HISTORY].stateVersions.push(state as State); | ||
stateHistory[STATE_HISTORY].currentVersionIndex = stateHistory[STATE_HISTORY].stateVersions.length; | ||
})(getState(store)), | ||
); | ||
} | ||
}, | ||
}), | ||
); | ||
} |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,9 @@ | ||
import { | ||
type ApplicationConfig, | ||
provideZoneChangeDetection, | ||
} from '@angular/core'; | ||
import { type ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; | ||
import { provideRouter } from '@angular/router'; | ||
|
||
import { provideStore } from '@ngrx/store'; | ||
import { routes } from './app.routes'; | ||
|
||
export const appConfig: ApplicationConfig = { | ||
providers: [ | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes), | ||
provideStore(), | ||
], | ||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideStore()], | ||
}; |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { patchStateWithImmer } from '@/projects/ngrx-extension/src/lib/patch-state-with-immer/patch-state-with-immer'; | ||
import { withHistory } from '@/projects/ngrx-extension/src/lib/with-history/with-history'; | ||
import { Component, effect, inject } from '@angular/core'; | ||
import { faker } from '@faker-js/faker'; | ||
import { signalStore, withMethods, withState } from '@ngrx/signals'; | ||
|
||
export type TUser = { | ||
name: string; | ||
age: number; | ||
}; | ||
|
||
export type TUserState = { | ||
user: TUser; | ||
}; | ||
|
||
export const UserSignalStore = signalStore( | ||
withState<TUserState>({ user: { name: 'John Doe', age: 30 } }), | ||
withHistory({}), | ||
withMethods((store) => ({ | ||
editName(name: string): void { | ||
patchStateWithImmer(store, (state) => { | ||
state.user.name = name; | ||
}); | ||
}, | ||
})), | ||
); | ||
|
||
@Component({ | ||
selector: 'app-with-history', | ||
providers: [UserSignalStore], | ||
template: ` | ||
<div> | ||
<h1 class="text-3xl">With History</h1> | ||
<p>{{ userSignalStore.user.name() }}</p> | ||
<p>{{ userSignalStore.user.age() }}</p> | ||
<div class="flex flex-row gap-3 "> | ||
<button (click)="editName()" class="bg-green-500 rounded-lg p-2 text-white">change name</button> | ||
<button (click)="userSignalStore.undo()" class="bg-red-500 rounded-lg p-2 text-white">undo</button> | ||
<button (click)="userSignalStore.redo()" class="bg-blue-500 rounded-lg p-2 text-white">redo</button> | ||
<button (click)="userSignalStore.clearHistories()" class="bg-purple-500 rounded-lg p-2 text-white">clear | ||
</button> | ||
</div> | ||
</div> | ||
`, | ||
}) | ||
export class WithHistoryComponent { | ||
userSignalStore = inject(UserSignalStore); | ||
|
||
constructor() { | ||
effect(() => { | ||
// console.log(this.userSignalStore.) | ||
}); | ||
} | ||
|
||
editName(): void { | ||
this.userSignalStore.editName(faker.person.firstName()); | ||
} | ||
} |
This file contains 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
Oops, something went wrong.