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 05:53:28 +00:00
|
|
|
private webStore: plugins.webstore.WebStore<TStatePayload> | null = null; // Add WebStore instance
|
|
|
|
|
|
|
|
constructor(nameArg: TStatePartName, webStoreOptions?: 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
|
|
|
|
if (webStoreOptions) {
|
|
|
|
this.webStore = new plugins.webstore.WebStore<TStatePayload>(webStoreOptions);
|
|
|
|
this.initWebStore();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* initializes the webstore
|
|
|
|
*/
|
|
|
|
private async initWebStore() {
|
|
|
|
if (this.webStore) {
|
|
|
|
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);
|
|
|
|
}
|
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
|
|
|
|
*/
|
|
|
|
public async dispatchAction<T>(stateAction: StateAction<TStatePayload, T>, actionPayload: T) {
|
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);
|
|
|
|
this.setState(newState);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
}
|