transformer.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 { Task } from '../mol-task';
  7. import { StateObject, StateObjectCell } from './object';
  8. import { StateTransform } from './transform';
  9. import { ParamDefinition as PD } from '../mol-util/param-definition';
  10. import { StateAction } from './action';
  11. import { capitalize } from '../mol-util/string';
  12. import { StateTreeSpine } from './tree/spine';
  13. export { Transformer as StateTransformer };
  14. interface Transformer<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = any> {
  15. apply(parent: StateTransform.Ref, params?: P, props?: Partial<StateTransform.Options>): StateTransform<this>,
  16. toAction(): StateAction<A, void, P>,
  17. readonly namespace: string,
  18. readonly id: Transformer.Id,
  19. readonly definition: Transformer.Definition<A, B, P>,
  20. /** create a fresh copy of the params which can be edited in place */
  21. createDefaultParams(a: A, globalCtx: unknown): P
  22. }
  23. namespace Transformer {
  24. export type Id = string & { '@type': 'transformer-id' }
  25. export type Params<T extends Transformer<any, any, any>> = T extends Transformer<any, any, infer P> ? P : unknown;
  26. export type From<T extends Transformer<any, any, any>> = T extends Transformer<infer A, any, any> ? A : unknown;
  27. export type To<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? B : unknown;
  28. export type Cell<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? StateObjectCell<B> : unknown;
  29. export function getParamDefinition<T extends Transformer>(t: T, a: From<T> | undefined, globalCtx: unknown): PD.For<Params<T>> {
  30. return t.definition.params ? t.definition.params(a, globalCtx) as any : { } as any;
  31. }
  32. export function is(obj: any): obj is Transformer {
  33. return !!obj && typeof (obj as Transformer).toAction === 'function' && typeof (obj as Transformer).apply === 'function';
  34. }
  35. export interface ApplyParams<A extends StateObject = StateObject, P extends {} = {}> {
  36. a: A,
  37. params: P,
  38. /** A cache object that is purged each time the corresponding StateObject is removed or recreated. */
  39. cache: unknown,
  40. spine: StateTreeSpine,
  41. dependencies?: { [k: string]: StateObject<unknown> }
  42. }
  43. export interface UpdateParams<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
  44. a: A,
  45. b: B,
  46. oldParams: P,
  47. newParams: P,
  48. /** A cache object that is purged each time the corresponding StateObject is removed or recreated. */
  49. cache: unknown,
  50. spine: StateTreeSpine,
  51. dependencies?: { [k: string]: StateObject<unknown> }
  52. }
  53. export interface AutoUpdateParams<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
  54. a: A,
  55. b: B,
  56. oldParams: P,
  57. newParams: P
  58. }
  59. export interface DisposeParams<B extends StateObject = StateObject, P extends {} = {}> {
  60. b: B | undefined,
  61. params: P | undefined,
  62. cache: unknown
  63. }
  64. export enum UpdateResult { Unchanged, Updated, Recreate, Null }
  65. /** Specify default control descriptors for the parameters */
  66. // export type ParamsDefinition<A extends StateObject = StateObject, P = any> = (a: A, globalCtx: unknown) => { [K in keyof P]: PD.Any }
  67. export interface DefinitionBase<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
  68. /**
  69. * Apply the actual transformation. It must be pure (i.e. with no side effects).
  70. * Returns a task that produces the result of the result directly.
  71. */
  72. apply(params: ApplyParams<A, P>, globalCtx: unknown): Task<B> | B,
  73. /**
  74. * Attempts to update the entity in a non-destructive way.
  75. * For example changing a color scheme of a visual does not require computing new geometry.
  76. * Return/resolve to undefined if the update is not possible.
  77. */
  78. update?(params: UpdateParams<A, B, P>, globalCtx: unknown): Task<UpdateResult> | UpdateResult,
  79. /** Determine if the transformer can be applied automatically on UI change. Default is false. */
  80. canAutoUpdate?(params: AutoUpdateParams<A, B, P>, globalCtx: unknown): boolean,
  81. /** Test if the transform can be applied to a given node */
  82. isApplicable?(a: A, globalCtx: unknown): boolean,
  83. /** By default, returns true */
  84. isSerializable?(params: P): { isSerializable: true } | { isSerializable: false; reason: string },
  85. /** Parameter interpolation */
  86. interpolate?(src: P, target: P, t: number, globalCtx: unknown): P
  87. /**
  88. * Cleanup resources
  89. *
  90. * Automatically called on deleting an object and on recreating it
  91. * (i.e. when update returns UpdateResult.Recreate or UpdateResult.Null)
  92. *
  93. * Not called on UpdateResult.Updated because the resources might not
  94. * have been invalidated. In this case, the possible cleanup has to be handled
  95. * manually.
  96. */
  97. dispose?(params: DisposeParams<B, P>, globalCtx: unknown): void
  98. /** Custom conversion to and from JSON */
  99. readonly customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P }
  100. }
  101. export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> extends DefinitionBase<A, B, P> {
  102. readonly name: string,
  103. readonly from: StateObject.Ctor[],
  104. readonly to: StateObject.Ctor[],
  105. readonly display: { readonly name: string, readonly description?: string },
  106. params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any },
  107. /**
  108. * Decorators are special Transformers mapping the object to the same type.
  109. *
  110. * Special rules apply:
  111. * - applying decorator always "inserts" it instead
  112. * - applying to a decorated Transform is applied to the decorator instead (transitive)
  113. */
  114. readonly isDecorator?: boolean
  115. }
  116. const registry = new Map<Id, Transformer<any, any>>();
  117. const fromTypeIndex: Map<StateObject.Type, Transformer[]> = new Map();
  118. function _index(tr: Transformer) {
  119. for (const t of tr.definition.from) {
  120. if (fromTypeIndex.has(t.type)) {
  121. fromTypeIndex.get(t.type)!.push(tr);
  122. } else {
  123. fromTypeIndex.set(t.type, [tr]);
  124. }
  125. }
  126. }
  127. export function getAll() {
  128. return Array.from(registry.values());
  129. }
  130. export function get(id: string): Transformer {
  131. const t = registry.get(id as Id);
  132. if (!t) {
  133. throw new Error(`A transformer with signature '${id}' is not registered.`);
  134. }
  135. return t;
  136. }
  137. export function fromType(type: StateObject.Type): ReadonlyArray<Transformer> {
  138. return fromTypeIndex.get(type) || [];
  139. }
  140. export function create<A extends StateObject, B extends StateObject, P extends {} = {}>(namespace: string, definition: Definition<A, B, P>) {
  141. const { name } = definition;
  142. const id = `${namespace}.${name}` as Id;
  143. if (registry.has(id)) {
  144. throw new Error(`A transform with id '${name}' is already registered. Please pick a unique identifier for your transforms and/or register them only once. This is to ensure that transforms can be serialized and replayed.`);
  145. }
  146. const t: Transformer<A, B, P> = {
  147. apply(parent, params, props) { return StateTransform.create<Transformer<A, B, P>>(parent, t, params, props); },
  148. toAction() { return StateAction.fromTransformer(t); },
  149. namespace,
  150. id,
  151. definition,
  152. createDefaultParams(a, globalCtx) { return definition.params ? PD.getDefaultValues( definition.params(a, globalCtx)) : {} as any; }
  153. };
  154. registry.set(id, t);
  155. _index(t);
  156. return t;
  157. }
  158. export function factory(namespace: string) {
  159. return <A extends StateObject, B extends StateObject, P extends {} = {}>(definition: Definition<A, B, P>) => create(namespace, definition);
  160. }
  161. export function builderFactory(namespace: string) {
  162. return Builder.build(namespace);
  163. }
  164. export namespace Builder {
  165. export interface Type<A extends StateObject.Ctor, B extends StateObject.Ctor, P extends { }> {
  166. name: string,
  167. from: A | A[],
  168. to: B | B[],
  169. /** The source StateObject can be undefined: used for generating docs. */
  170. params?: PD.For<P> | ((a: StateObject.From<A> | undefined, globalCtx: any) => PD.For<P>),
  171. display?: string | { name: string, description?: string },
  172. isDecorator?: boolean
  173. }
  174. export interface Root {
  175. <A extends StateObject.Ctor, B extends StateObject.Ctor, P extends { }>(info: Type<A, B, P>): Define<StateObject.From<A>, StateObject.From<B>, PD.Normalize<P>>
  176. }
  177. export interface Define<A extends StateObject, B extends StateObject, P> {
  178. (def: DefinitionBase<A, B, P>): Transformer<A, B, P>
  179. }
  180. function root(namespace: string, info: Type<any, any, any>): Define<any, any, any> {
  181. return def => create(namespace, {
  182. name: info.name,
  183. from: info.from instanceof Array ? info.from : [info.from],
  184. to: info.to instanceof Array ? info.to : [info.to],
  185. display: typeof info.display === 'string'
  186. ? { name: info.display }
  187. : !!info.display
  188. ? info.display
  189. : { name: capitalize(info.name.replace(/[-]/g, ' ')) },
  190. params: typeof info.params === 'object'
  191. ? () => info.params as any
  192. : !!info.params
  193. ? info.params as any
  194. : void 0,
  195. isDecorator: info.isDecorator,
  196. ...def
  197. });
  198. }
  199. export function build(namespace: string): Root {
  200. return (info: any) => root(namespace, info);
  201. }
  202. }
  203. export function build(namespace: string): Builder.Root {
  204. return Builder.build(namespace);
  205. }
  206. export const ROOT = create<any, any, {}>('build-in', {
  207. name: 'root',
  208. from: [],
  209. to: [],
  210. display: { name: 'Root', description: 'For internal use.' },
  211. apply() { throw new Error('should never be applied'); },
  212. update() { return UpdateResult.Unchanged; }
  213. });
  214. }