smartstate/ts/smartstate.classes.statepart.ts

158 lines
4.5 KiB
TypeScript
Raw Normal View History

2022-03-25 12:31:21 +00:00
import * as plugins from './smartstate.plugins.js';
2023-07-27 13:20:24 +00:00
import { StateAction, type IActionDef } from './smartstate.classes.stateaction.js';
2020-11-29 23:51:05 +00:00
export class StatePart<TStatePartName, TStatePayload> {
public name: TStatePartName;
public state = new plugins.smartrx.rxjs.Subject<TStatePayload>();
public stateStore: TStatePayload;
2023-04-04 18:59:45 +00:00
private cumulativeDeferred = plugins.smartpromise.cumulativeDefer();
2020-11-29 23:51:05 +00:00
2023-10-03 17:19:54 +00:00
private webStoreOptions: plugins.webstore.IWebStoreOptions;
2023-10-03 05:53:28 +00:00
private webStore: plugins.webstore.WebStore<TStatePayload> | null = null; // Add WebStore instance
2023-10-03 17:19:54 +00:00
constructor(nameArg: TStatePartName, webStoreOptionsArg?: plugins.webstore.IWebStoreOptions) {
2020-11-29 23:51:05 +00:00
this.name = nameArg;
2023-10-03 05:53:28 +00:00
// Initialize WebStore if webStoreOptions are provided
2023-10-03 17:19:54 +00:00
if (webStoreOptionsArg) {
this.webStoreOptions = webStoreOptionsArg;
2023-10-03 05:53:28 +00:00
}
}
/**
* initializes the webstore
*/
2023-10-03 17:19:54 +00:00
public async init() {
if (this.webStoreOptions) {
this.webStore = new plugins.webstore.WebStore<TStatePayload>(this.webStoreOptions);
2023-10-03 05:53:28 +00:00
await this.webStore.init();
const storedState = await this.webStore.get(String(this.name));
if (storedState) {
this.stateStore = storedState;
this.notifyChange();
}
}
2020-11-29 23:51:05 +00:00
}
/**
* gets the state from the state store
*/
public getState(): TStatePayload {
return this.stateStore;
}
/**
* sets the stateStore to the new state
* @param newStateArg
*/
2023-10-03 05:53:28 +00:00
public async setState(newStateArg: TStatePayload) {
2020-11-29 23:51:05 +00:00
this.stateStore = newStateArg;
this.notifyChange();
2023-10-03 05:53:28 +00:00
// Save state to WebStore if initialized
if (this.webStore) {
await this.webStore.set(String(this.name), newStateArg);
}
2023-10-07 10:23:03 +00:00
return this.stateStore;
2020-11-29 23:51:05 +00:00
}
/**
* notifies of a change on the state
*/
public notifyChange() {
2022-01-24 05:39:36 +00:00
const createStateHash = (stateArg: any) => {
return plugins.isohash.sha256FromString(plugins.smartjson.stringify(stateArg));
};
2023-04-04 19:44:16 +00:00
if (
this.stateStore &&
this.lastStateNotificationPayloadHash &&
2023-04-13 12:22:31 +00:00
createStateHash(this.stateStore) === this.lastStateNotificationPayloadHash
2023-04-04 19:44:16 +00:00
) {
2022-01-24 05:39:36 +00:00
return;
} else {
this.lastStateNotificationPayloadHash = this.stateStore;
}
2020-11-29 23:51:05 +00:00
this.state.next(this.stateStore);
}
2022-01-24 05:39:36 +00:00
private lastStateNotificationPayloadHash: any;
2020-11-29 23:51:05 +00:00
2023-04-13 12:22:31 +00:00
/**
* creates a cumulative notification by adding a change notification at the end of the call stack;
*/
public notifyChangeCumulative() {
// TODO: check viability
setTimeout(() => this.state.next(this.stateStore), 0);
}
2020-11-29 23:51:05 +00:00
/**
* selects a state or a substate
*/
public select<T = TStatePayload>(
selectorFn?: (state: TStatePayload) => T
): plugins.smartrx.rxjs.Observable<T> {
if (!selectorFn) {
selectorFn = (state: TStatePayload) => <T>(<any>state);
}
const mapped = this.state.pipe(
plugins.smartrx.rxjs.ops.startWith(this.getState()),
plugins.smartrx.rxjs.ops.map((stateArg) => {
try {
return selectorFn(stateArg);
} catch (e) {
// Nothing here
}
})
);
return mapped;
}
/**
* creates an action capable of modifying the state
*/
public createAction<TActionPayload>(
actionDef: IActionDef<TStatePayload, TActionPayload>
): StateAction<TStatePayload, TActionPayload> {
return new StateAction(this, actionDef);
}
/**
* dispatches an action on the statepart level
*/
2023-10-03 10:47:12 +00:00
public async dispatchAction<T>(stateAction: StateAction<TStatePayload, T>, actionPayload: T): Promise<TStatePayload> {
2023-04-04 18:59:45 +00:00
await this.cumulativeDeferred.promise;
2020-11-29 23:51:05 +00:00
const newState = await stateAction.actionDef(this, actionPayload);
2023-10-03 10:47:12 +00:00
await this.setState(newState);
return this.getState();
2020-11-29 23:51:05 +00:00
}
/**
* waits until a certain part of the state becomes available
* @param selectorFn
*/
public async waitUntilPresent<T = TStatePayload>(
selectorFn?: (state: TStatePayload) => T
): Promise<T> {
const done = plugins.smartpromise.defer<T>();
const selectedObservable = this.select(selectorFn);
const subscription = selectedObservable.subscribe(async (value) => {
if (value) {
done.resolve(value);
}
});
const result = await done.promise;
subscription.unsubscribe();
return result;
}
2023-04-04 18:59:45 +00:00
/**
2023-04-04 19:44:16 +00:00
* is executed
2023-04-04 18:59:45 +00:00
*/
2023-04-12 18:34:34 +00:00
public async stateSetup(
funcArg: (statePartArg?: StatePart<any, TStatePayload>) => Promise<TStatePayload>
2023-04-04 19:44:16 +00:00
) {
2023-04-12 18:34:34 +00:00
const resultPromise = funcArg(this);
this.cumulativeDeferred.addPromise(resultPromise);
this.setState(await resultPromise);
2023-04-04 18:59:45 +00:00
}
2020-11-29 23:51:05 +00:00
}