transformer.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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 } from './object';
  8. import { Transform } from './transform';
  9. import { ParamDefinition as PD } from 'mol-util/param-definition';
  10. export interface Transformer<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
  11. apply(params?: P, props?: Partial<Transform.Options>): Transform<A, B, P>,
  12. readonly namespace: string,
  13. readonly id: Transformer.Id,
  14. readonly definition: Transformer.Definition<A, B, P>
  15. }
  16. export namespace Transformer {
  17. export type Id = string & { '@type': 'transformer-id' }
  18. export type Params<T extends Transformer<any, any, any>> = T extends Transformer<any, any, infer P> ? P : unknown;
  19. export type To<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? B : unknown;
  20. export type ControlsFor<A extends StateObject, Props> = { [P in keyof Props]?: PD.Any }
  21. export interface ApplyParams<A extends StateObject = StateObject, P = unknown> {
  22. a: A,
  23. params: P,
  24. /** A cache object that is purged each time the corresponding StateObject is removed or recreated. */
  25. cache: unknown
  26. }
  27. export interface UpdateParams<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
  28. a: A,
  29. b: B,
  30. oldParams: P,
  31. newParams: P,
  32. /** A cache object that is purged each time the corresponding StateObject is removed or recreated. */
  33. cache: unknown
  34. }
  35. export enum UpdateResult { Unchanged, Updated, Recreate }
  36. export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
  37. readonly name: string,
  38. readonly from: { type: StateObject.Type }[],
  39. readonly to: { type: StateObject.Type }[],
  40. readonly display?: { readonly name: string, readonly description?: string },
  41. /**
  42. * Apply the actual transformation. It must be pure (i.e. with no side effects).
  43. * Returns a task that produces the result of the result directly.
  44. */
  45. apply(params: ApplyParams<A, P>, globalCtx: unknown): Task<B> | B,
  46. /**
  47. * Attempts to update the entity in a non-destructive way.
  48. * For example changing a color scheme of a visual does not require computing new geometry.
  49. * Return/resolve to undefined if the update is not possible.
  50. */
  51. update?(params: UpdateParams<A, B, P>, globalCtx: unknown): Task<UpdateResult> | UpdateResult,
  52. params?: {
  53. /** Check the parameters and return a list of errors if the are not valid. */
  54. default?(a: A, globalCtx: unknown): P,
  55. /** Specify default control descriptors for the parameters */
  56. controls?(a: A, globalCtx: unknown): ControlsFor<A, P>,
  57. /** Check the parameters and return a list of errors if the are not valid. */
  58. validate?(a: A, params: P, globalCtx: unknown): string[] | undefined,
  59. /** Optional custom parameter equality. Use deep structural equal by default. */
  60. areEqual?(oldParams: P, newParams: P): boolean
  61. }
  62. /** Test if the transform can be applied to a given node */
  63. isApplicable?(a: A, globalCtx: unknown): boolean,
  64. /** By default, returns true */
  65. isSerializable?(params: P): { isSerializable: true } | { isSerializable: false; reason: string },
  66. /** Custom conversion to and from JSON */
  67. customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P }
  68. }
  69. const registry = new Map<Id, Transformer<any, any>>();
  70. export function get(id: string): Transformer {
  71. const t = registry.get(id as Id);
  72. if (!t) {
  73. throw new Error(`A transformer with signature '${id}' is not registered.`);
  74. }
  75. return t;
  76. }
  77. export function create<A extends StateObject, B extends StateObject, P>(namespace: string, definition: Definition<A, B, P>) {
  78. const { name } = definition;
  79. const id = `${namespace}.${name}` as Id;
  80. if (registry.has(id)) {
  81. 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.`);
  82. }
  83. const t: Transformer<A, B, P> = {
  84. apply(params, props) { return Transform.create<A, B, P>(t as any, params, props); },
  85. namespace,
  86. id,
  87. definition
  88. };
  89. registry.set(id, t);
  90. return t;
  91. }
  92. export function factory(namespace: string) {
  93. return <A extends StateObject, B extends StateObject, P>(definition: Definition<A, B, P>) => create(namespace, definition);
  94. }
  95. export const ROOT = create<any, any, any>('build-in', {
  96. name: 'root',
  97. from: [],
  98. to: [],
  99. apply() { throw new Error('should never be applied'); }
  100. })
  101. }