state.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { State, StateTransform, StateTransformer } from '../mol-state';
  8. import { PluginStateObject as SO } from '../mol-plugin-state/objects';
  9. import { Camera } from '../mol-canvas3d/camera';
  10. import { PluginBehavior } from './behavior';
  11. import { Canvas3DParams, Canvas3DProps } from '../mol-canvas3d/canvas3d';
  12. import { PluginCommands } from './commands';
  13. import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
  14. import { ParamDefinition as PD } from '../mol-util/param-definition';
  15. import { UUID } from '../mol-util';
  16. import { InteractivityManager } from '../mol-plugin-state/manager/interactivity';
  17. import { produce } from 'immer';
  18. import { StructureFocusSnapshot } from '../mol-plugin-state/manager/structure/focus';
  19. import { merge } from 'rxjs';
  20. import { PluginContext } from './context';
  21. import { PluginComponent } from '../mol-plugin-state/component';
  22. import { PluginConfig } from './config';
  23. import { StructureComponentManager } from '../mol-plugin-state/manager/structure/component';
  24. import { StructureSelectionSnapshot } from '../mol-plugin-state/manager/structure/selection';
  25. export { PluginState };
  26. class PluginState extends PluginComponent {
  27. private get animation() { return this.plugin.managers.animation; }
  28. readonly data = State.create(new SO.Root({ }), { runTask: this.plugin.runTask, globalContext: this.plugin, historyCapacity: this.plugin.config.get(PluginConfig.State.HistoryCapacity) });
  29. readonly behaviors = State.create(new PluginBehavior.Root({ }), { runTask: this.plugin.runTask, globalContext: this.plugin, rootState: { isLocked: true } });
  30. readonly events = {
  31. cell: {
  32. stateUpdated: merge(this.data.events.cell.stateUpdated, this.behaviors.events.cell.stateUpdated),
  33. created: merge(this.data.events.cell.created, this.behaviors.events.cell.created),
  34. removed: merge(this.data.events.cell.removed, this.behaviors.events.cell.removed),
  35. },
  36. object: {
  37. created: merge(this.data.events.object.created, this.behaviors.events.object.created),
  38. removed: merge(this.data.events.object.removed, this.behaviors.events.object.removed),
  39. updated: merge(this.data.events.object.updated, this.behaviors.events.object.updated)
  40. }
  41. } as const;
  42. readonly snapshotParams = this.ev.behavior<PluginState.SnapshotParams>(PluginState.DefaultSnapshotParams);
  43. setSnapshotParams = (params?: PluginState.SnapshotParams) => {
  44. this.snapshotParams.next({ ...PluginState.DefaultSnapshotParams, ...params });
  45. };
  46. getSnapshot(params?: PluginState.SnapshotParams): PluginState.Snapshot {
  47. const p = { ...this.snapshotParams.value, ...params };
  48. return {
  49. id: UUID.create22(),
  50. data: p.data ? this.data.getSnapshot() : void 0,
  51. behaviour: p.behavior ? this.behaviors.getSnapshot() : void 0,
  52. animation: p.animation ? this.animation.getSnapshot() : void 0,
  53. startAnimation: p.startAnimation ? !!p.startAnimation : void 0,
  54. camera: p.camera ? {
  55. current: this.plugin.canvas3d!.camera.getSnapshot(),
  56. transitionStyle: p.cameraTransition!.name,
  57. transitionDurationInMs: p?.cameraTransition?.name === 'animate' ? p.cameraTransition.params.durationInMs : void 0
  58. } : void 0,
  59. canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
  60. interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0,
  61. structureFocus: this.plugin.managers.structure.focus.getSnapshot(),
  62. structureSelection: p.structureSelection ? this.plugin.managers.structure.selection.getSnapshot() : void 0,
  63. structureComponentManager: p.componentManager ? {
  64. options: this.plugin.managers.structure.component.state.options
  65. } : void 0,
  66. durationInMs: p?.durationInMs
  67. };
  68. }
  69. async setSnapshot(snapshot: PluginState.Snapshot) {
  70. await this.animation.stop();
  71. // this needs to go 1st since these changes are already baked into the behavior and data state
  72. if (snapshot.structureComponentManager?.options) this.plugin.managers.structure.component._setSnapshotState(snapshot.structureComponentManager?.options);
  73. if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
  74. if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
  75. if (snapshot.canvas3d?.props) {
  76. const settings = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
  77. await PluginCommands.Canvas3D.SetSettings(this.plugin, { settings });
  78. }
  79. if (snapshot.interactivity) {
  80. if (snapshot.interactivity.props) this.plugin.managers.interactivity.setProps(snapshot.interactivity.props);
  81. }
  82. if (snapshot.structureFocus) {
  83. this.plugin.managers.structure.focus.setSnapshot(snapshot.structureFocus);
  84. }
  85. if (snapshot.structureSelection) {
  86. this.plugin.managers.structure.selection.setSnapshot(snapshot.structureSelection);
  87. }
  88. if (snapshot.animation) {
  89. this.animation.setSnapshot(snapshot.animation);
  90. }
  91. if (snapshot.camera) {
  92. PluginCommands.Camera.Reset(this.plugin, {
  93. snapshot: snapshot.camera.current,
  94. durationMs: snapshot.camera.transitionStyle === 'animate'
  95. ? snapshot.camera.transitionDurationInMs
  96. : void 0
  97. });
  98. }
  99. if (snapshot.startAnimation) {
  100. this.animation.start();
  101. }
  102. }
  103. updateTransform(state: State, a: StateTransform.Ref, params: any, canUndo?: string | boolean) {
  104. const tree = state.build().to(a).update(params);
  105. return PluginCommands.State.Update(this.plugin, { state, tree, options: { canUndo } });
  106. }
  107. hasBehavior(behavior: StateTransformer) {
  108. return this.behaviors.tree.transforms.has(behavior.id);
  109. }
  110. updateBehavior<T extends StateTransformer>(behavior: T, params: (old: StateTransformer.Params<T>) => (void | StateTransformer.Params<T>)) {
  111. const tree = this.behaviors.build();
  112. if (!this.behaviors.tree.transforms.has(behavior.id)) {
  113. const defaultParams = behavior.createDefaultParams(void 0 as any, this.plugin);
  114. tree.to(PluginBehavior.getCategoryId(behavior)).apply(behavior, produce(defaultParams, params) as any, { ref: behavior.id });
  115. } else {
  116. tree.to(behavior.id).update(params);
  117. }
  118. return this.plugin.runTask(this.behaviors.updateTree(tree));
  119. }
  120. dispose() {
  121. this.behaviors.cells.forEach(cell => {
  122. if (PluginBehavior.Behavior.is(cell.obj)) {
  123. cell.obj.data.unregister?.();
  124. cell.obj.data.dispose?.();
  125. }
  126. });
  127. super.dispose();
  128. this.data.dispose();
  129. this.behaviors.dispose();
  130. this.animation.dispose();
  131. }
  132. constructor(private plugin: PluginContext) {
  133. super();
  134. }
  135. }
  136. namespace PluginState {
  137. export type CameraTransitionStyle = 'instant' | 'animate'
  138. export const SnapshotParams = {
  139. durationInMs: PD.Numeric(1500, { min: 100, max: 15000, step: 100 }, { label: 'Duration in ms' }),
  140. data: PD.Boolean(true),
  141. behavior: PD.Boolean(false),
  142. structureSelection: PD.Boolean(false),
  143. componentManager: PD.Boolean(true),
  144. animation: PD.Boolean(true),
  145. startAnimation: PD.Boolean(false),
  146. canvas3d: PD.Boolean(true),
  147. interactivity: PD.Boolean(true),
  148. camera: PD.Boolean(true),
  149. cameraTransition: PD.MappedStatic('animate', {
  150. animate: PD.Group({
  151. durationInMs: PD.Numeric(250, { min: 100, max: 5000, step: 500 }, { label: 'Duration in ms' }),
  152. }),
  153. instant: PD.Group({ })
  154. }, { options: [['animate', 'Animate'], ['instant', 'Instant']] }),
  155. image: PD.Boolean(false),
  156. };
  157. export type SnapshotParams = Partial<PD.Values<typeof SnapshotParams>>
  158. export const DefaultSnapshotParams = PD.getDefaultValues(SnapshotParams);
  159. export interface Snapshot {
  160. id: UUID,
  161. data?: State.Snapshot,
  162. behaviour?: State.Snapshot,
  163. animation?: PluginAnimationManager.Snapshot,
  164. startAnimation?: boolean,
  165. camera?: {
  166. current: Camera.Snapshot,
  167. transitionStyle: CameraTransitionStyle,
  168. transitionDurationInMs?: number
  169. },
  170. canvas3d?: {
  171. props?: Canvas3DProps
  172. },
  173. interactivity?: {
  174. props?: InteractivityManager.Props
  175. },
  176. structureFocus?: StructureFocusSnapshot,
  177. structureSelection?: StructureSelectionSnapshot,
  178. structureComponentManager?: {
  179. options?: StructureComponentManager.Options
  180. },
  181. durationInMs?: number
  182. }
  183. export type SnapshotType = 'json' | 'molj' | 'zip' | 'molx'
  184. }