object.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { UUID } from '../mol-util';
  7. import { StateTransform } from './transform';
  8. import { ParamDefinition } from '../mol-util/param-definition';
  9. import { State } from './state';
  10. import { StateSelection, StateTransformer } from '../mol-state';
  11. import { StateBuilder } from './state/builder';
  12. export { StateObject, StateObjectCell };
  13. interface StateObject<D = any, T extends StateObject.Type = StateObject.Type<any>> {
  14. readonly id: UUID,
  15. readonly type: T,
  16. readonly data: D,
  17. readonly label: string,
  18. readonly description?: string,
  19. // assigned by reconciler to be StateTransform.props.tag
  20. readonly tags?: string[]
  21. }
  22. namespace StateObject {
  23. export function factory<T extends Type>() {
  24. return <D = { }>(type: T) => create<D, T>(type);
  25. }
  26. export type Type<Cls extends string = string> = { name: string, typeClass: Cls }
  27. export type Ctor<T extends StateObject = StateObject> = { new(...args: any[]): T, is(obj?: StateObject): boolean, type: any }
  28. export type From<C extends Ctor> = C extends Ctor<infer T> ? T : never
  29. export function create<Data, T extends Type>(type: T) {
  30. return class O implements StateObject<Data, T> {
  31. static type = type;
  32. static is(obj?: StateObject): obj is O { return !!obj && type === obj.type; }
  33. id = UUID.create22();
  34. type = type;
  35. label: string;
  36. description?: string;
  37. constructor(public data: Data, props?: { label: string, description?: string }) {
  38. this.label = props && props.label || type.name;
  39. this.description = props && props.description;
  40. }
  41. };
  42. }
  43. export function hasTag(o: StateObject, t: string) {
  44. if (!o.tags) return false;
  45. for (const s of o.tags) {
  46. if (s === t) return true;
  47. }
  48. return false;
  49. }
  50. /** A special object indicating a transformer result has no value. */
  51. export const Null: StateObject<any, any> = {
  52. id: UUID.create22(),
  53. type: { name: 'Null', typeClass: 'Null' },
  54. data: void 0,
  55. label: 'Null'
  56. };
  57. }
  58. interface StateObjectCell<T extends StateObject = StateObject, F extends StateTransform = StateTransform> {
  59. parent?: State,
  60. transform: F,
  61. // Which object was used as a parent to create data in this cell
  62. sourceRef: StateTransform.Ref | undefined,
  63. status: StateObjectCell.Status,
  64. state: StateTransform.State,
  65. params: {
  66. definition: ParamDefinition.Params,
  67. values: any
  68. } | undefined,
  69. paramsNormalizedVersion: string,
  70. dependencies: {
  71. dependentBy: StateObjectCell[],
  72. dependsOn: StateObjectCell[]
  73. },
  74. errorText?: string,
  75. obj?: T,
  76. cache: unknown | undefined
  77. }
  78. namespace StateObjectCell {
  79. export type Status = 'ok' | 'error' | 'pending' | 'processing'
  80. export type Obj<C extends StateObjectCell> = C extends StateObjectCell<infer T> ? T : never
  81. export type Transform<C extends StateObjectCell> = C extends StateObjectCell<any, infer T> ? T : never
  82. export type Transformer<C extends StateObjectCell> = C extends StateObjectCell<any, StateTransform<infer T>> ? T : never
  83. export function is(o: any): o is StateObjectCell {
  84. const c: StateObjectCell = o;
  85. return !!c && !!c.transform && !!c.parent && !!c.status;
  86. }
  87. export type Ref = StateTransform.Ref | StateObjectCell | StateObjectSelector
  88. export function resolve(state: State, refOrCellOrSelector: StateTransform.Ref | StateObjectCell | StateObjectSelector) {
  89. const ref = typeof refOrCellOrSelector === 'string'
  90. ? refOrCellOrSelector
  91. : StateObjectCell.is(refOrCellOrSelector)
  92. ? refOrCellOrSelector.transform.ref
  93. : refOrCellOrSelector.ref;
  94. return state.cells.get(ref);
  95. }
  96. }
  97. // TODO: improve the API?
  98. export class StateObjectTracker<T extends StateObject> {
  99. private query: StateSelection.Query;
  100. private version: string = '';
  101. cell: StateObjectCell | undefined;
  102. data: T['data'] | undefined;
  103. setQuery(sel: StateSelection.Selector) {
  104. this.query = StateSelection.compile(sel);
  105. }
  106. update() {
  107. const cell = this.state.select(this.query)[0];
  108. const version = cell ? cell.transform.version : void 0;
  109. const changed = this.cell !== cell || this.version !== version;
  110. this.cell = cell;
  111. this.version = version || '';
  112. this.data = cell && cell.obj ? cell.obj.data as T : void 0 as any;
  113. return changed;
  114. }
  115. constructor(private state: State) { }
  116. }
  117. export class StateObjectSelector<S extends StateObject = StateObject, T extends StateTransformer = StateTransformer> {
  118. get cell(): StateObjectCell<S, StateTransform<T>> | undefined {
  119. return this.state?.cells.get(this.ref) as StateObjectCell<S, StateTransform<T>> | undefined;
  120. }
  121. get obj(): S | undefined {
  122. return this.state?.cells.get(this.ref)?.obj as S | undefined;
  123. }
  124. get data(): S['data'] | undefined {
  125. return this.obj?.data;
  126. }
  127. /** Create a new build and apply update or use the provided one. */
  128. update(params: StateTransformer.Params<T>, builder?: StateBuilder.Root | StateBuilder.To<any>): StateBuilder
  129. update(params: (old: StateTransformer.Params<T>) => StateTransformer.Params<T> | void, builder?: StateBuilder.Root | StateBuilder.To<any>): StateBuilder
  130. update(params: ((old: StateTransformer.Params<T>) => StateTransformer.Params<T> | void) | StateTransformer.Params<T>, builder?: StateBuilder.Root | StateBuilder.To<any>): StateBuilder {
  131. if (!this.state) throw new Error(`To use update() from StateObjectSelector, 'state' must be defined.`);
  132. if (!builder) builder = this.state.build();
  133. (builder || this.state.build()).to(this).update(params);
  134. return builder;
  135. }
  136. /** Checks if the object exists. If not throw an error. */
  137. checkValid() {
  138. if (!this.state) {
  139. throw new Error('Unassigned State.');
  140. }
  141. const cell = this.cell;
  142. if (!cell) {
  143. throw new Error(`Not created at all. Did you await/then the corresponding state update?`);
  144. }
  145. if (cell.status === 'ok') return true;
  146. if (cell.status === 'error') throw new Error(cell.errorText);
  147. if (cell.obj === StateObject.Null) throw new Error('The object is Null.');
  148. throw new Error(`Unresolved. Did you await/then the corresponding state update?`);
  149. }
  150. get isOk() {
  151. const cell = this.cell;
  152. return cell && cell.status === 'ok' && cell.obj !== StateObject.Null;
  153. }
  154. constructor(public ref: StateTransform.Ref, public state?: State) {
  155. }
  156. }
  157. export namespace StateObjectSelector {
  158. export type Obj<S extends StateObjectSelector> = S extends StateObjectSelector<infer A> ? A : never
  159. export type Transformer<S extends StateObjectSelector> = S extends StateObjectSelector<any, infer T> ? T : never
  160. }
  161. export type StateObjectRef<S extends StateObject = StateObject> = StateObjectSelector<S> | StateObjectCell<S> | StateTransform.Ref
  162. export namespace StateObjectRef {
  163. export function resolveRef<S extends StateObject>(ref?: StateObjectRef<S>): StateTransform.Ref | undefined {
  164. if (!ref) return;
  165. if (typeof ref === 'string') return ref;
  166. if (StateObjectCell.is(ref)) return ref.transform.ref;
  167. return ref.cell?.transform.ref;
  168. }
  169. export function resolve<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
  170. if (!ref) return;
  171. if (StateObjectCell.is(ref)) return ref;
  172. if (typeof ref === 'string') return state.cells.get(ref) as StateObjectCell<S> | undefined;
  173. return ref.cell;
  174. }
  175. export function resolveAndCheck<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
  176. const cell = resolve(state, ref);
  177. if (!cell || !cell.obj || cell.status !== 'ok') return;
  178. return cell;
  179. }
  180. }