/* * Squidex Headless CMS * * @license * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { ResourceLinks } from './utils/hateos'; import { Types } from './utils/types'; export type Mutable = { -readonly [P in keyof T ]: T[P] }; export class Model { public with(value: Partial, validOnly = false): T { return this.clone(value, validOnly); } protected clone(update: ((v: any) => V) | Partial, validOnly = false): V { let values: Partial; if (Types.isFunction(update)) { values = update(this); } else { values = update; } const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this); for (const key in values) { if (values.hasOwnProperty(key)) { const value = values[key]; if (value || !validOnly) { clone[key] = value; } } } if (Types.isFunction(clone.onCloned)) { clone.onCloned(); } return clone; } } export class ResultSet { public readonly _links: ResourceLinks; constructor( public readonly total: number, public readonly items: ReadonlyArray, links?: ResourceLinks ) { this._links = links || {}; } } export class State { private readonly state: BehaviorSubject>; public get changes(): Observable> { return this.state; } public get snapshot(): Readonly { return this.state.value; } public project(project: (value: T) => M, compare?: (x: M, y: M) => boolean) { return this.changes.pipe( map(x => project(x)), distinctUntilChanged(compare), shareReplay(1)); } public projectFrom(source: Observable, project: (value: M) => N, compare?: (x: N, y: N) => boolean) { return source.pipe( map(x => project(x)), distinctUntilChanged(compare), shareReplay(1)); } public projectFrom2(lhs: Observable, rhs: Observable, project: (l: M, r: N) => O, compare?: (x: O, y: O) => boolean) { return combineLatest([lhs, rhs]).pipe( map(([x, y]) => project(x, y)), distinctUntilChanged(compare), shareReplay(1)); } constructor( private readonly initialState: Readonly ) { this.state = new BehaviorSubject(initialState); } public resetState(update?: ((v: T) => Readonly) | Partial) { return this.updateState(this.initialState, update); } public next(update: ((v: T) => Readonly) | Partial) { return this.updateState(this.state.value, update); } private updateState(state: T, update?: ((v: T) => Readonly) | Partial) { let newState = state; if (update) { if (Types.isFunction(update)) { newState = update(state); } else { newState = { ...state, ...update }; } } let isChanged = false; const newKeys = Object.keys(newState); if (newKeys.length !== Object.keys(this.snapshot).length) { isChanged = true; } else { for (const key of newKeys) { if (newState[key] !== this.snapshot[key]) { isChanged = true; break; } } } if (isChanged) { this.state.next(newState); } return isChanged; } }