OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/frontend/app/helpers/reactive-fassade.ts

165 lines
3.9 KiB

import {scopedObservable} from "./angular-rx-utils";
import Observable = Rx.Observable;
import IScope = angular.IScope;
import IPromise = Rx.IPromise;
export abstract class StoreElement {
public pathInStore: string = null;
8 years ago
public logFn: (msg: any) => any = null;
log(msg: string, reason?: string) {
reason = reason === undefined ? "" : " // " + reason;
8 years ago
if (this.pathInStore && this.logFn) {
this.logFn("[" + this.pathInStore + "] " + msg + reason);
}
}
}
interface PromiseLike<T> {
then(successCallback: (value: T) => any, errorCallback: (value: T) => any): any;
}
export class State<T> extends StoreElement {
8 years ago
private timestampOfLastValue = -1;
private timestampOfLastPromise = -1;
private subject = new Rx.BehaviorSubject<T>(null);
private cleared = new Rx.Subject();
private observable: Observable<T>;
constructor() {
super();
this.observable = this.subject.filter(val => val !== null);
}
/**
* Returns true if this state either has a value of if
* a value is awaited from a promise (via putFromPromise).
*/
public isPristine(): boolean {
return this.timestampOfLastValue === -1 && this.timestampOfLastPromise === -1;
}
public isValueOrPromiseOlderThan(timeoutInMs: number) {
const ageValue = Date.now() - this.timestampOfLastValue;
const agePromise = Date.now() - this.timestampOfLastPromise;
return ageValue > timeoutInMs && agePromise > timeoutInMs;
}
public clear(reason?: string): this {
this.log("State#clear()", reason);
this.setState(null);
8 years ago
return this;
}
public put(value: T, reason?: string): this {
this.log("State#put(...)", reason);
this.setState(value);
8 years ago
return this;
}
8 years ago
public putFromPromise(promise: PromiseLike<T>): this {
this.clear();
this.timestampOfLastPromise = Date.now();
promise.then(
// success
(value: T) => {
this.log("State#putFromPromise(...)");
this.setState(value);
},
// error
() => {
this.log("State#putFromPromise ERROR");
this.timestampOfLastPromise = -1;
}
);
8 years ago
return this;
}
public putFromPromiseIfPristine(calledIfPristine: () => PromiseLike<T>): this {
8 years ago
if (this.isPristine()) {
this.putFromPromise(calledIfPristine());
8 years ago
}
return this;
}
public get(): IPromise<T> {
return this.observable.take(1).toPromise();
}
public observe(scope: IScope): Observable<T> {
return this.scopedObservable(scope);
}
public observeCleared(scope: IScope): Observable<any> {
return this.cleared.asObservable();
}
private setState(val: T) {
8 years ago
this.timestampOfLastValue = val !== null && val !== undefined ? Date.now() : -1;
this.subject.onNext(val);
if (val === null || val === undefined) {
this.cleared.onNext(null);
}
}
private scopedObservable(scope: IScope): Observable<T> {
return scope ? scopedObservable(scope, this.observable) : this.observable;
}
}
export class MultiState<T> extends StoreElement {
private states: {[id: string]: State<T>} = {};
constructor() {
super();
}
put(id: string, value: T): State<T> {
8 years ago
this.log("MultiState#put(" + id + ")");
const state = this.get(id);
state.put(value);
return state;
}
get(id: string): State<T> {
if (this.states[id] === undefined) {
this.states[id] = new State<T>();
}
return this.states[id];
}
}
8 years ago
function traverse(elem: any, path: string, logFn: (msg: any) => any) {
for (const key in elem) {
if (!elem.hasOwnProperty(key)) {
continue;
}
const value = elem[key];
let location = path.length > 0 ? path + "." + key : key;
if (value instanceof StoreElement) {
value.pathInStore = location;
8 years ago
value.logFn = logFn;
} else {
8 years ago
traverse(value, location, logFn);
}
}
8 years ago
}
8 years ago
export function initStates(states: any, logFn?: (msg: any) => any) {
return traverse(states, "", logFn);
}