state.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 { PluginCommands } from '../../command';
  7. import { PluginContext } from '../../context';
  8. import { StateTree, StateTransform, State } from 'mol-state';
  9. import { PluginStateSnapshotManager } from 'mol-plugin/state/snapshots';
  10. import { PluginStateObject as SO } from '../../state/objects';
  11. import { getFormattedTime } from 'mol-util/date';
  12. import { readFromFile } from 'mol-util/data-source';
  13. export function registerDefault(ctx: PluginContext) {
  14. SyncBehaviors(ctx);
  15. SetCurrentObject(ctx);
  16. Update(ctx);
  17. ApplyAction(ctx);
  18. RemoveObject(ctx);
  19. ToggleExpanded(ctx);
  20. ToggleVisibility(ctx);
  21. Highlight(ctx);
  22. ClearHighlight(ctx);
  23. Snapshots(ctx);
  24. }
  25. export function SyncBehaviors(ctx: PluginContext) {
  26. ctx.events.state.object.created.subscribe(o => {
  27. if (!SO.isBehavior(o.obj)) return;
  28. o.obj.data.register(o.ref);
  29. });
  30. ctx.events.state.object.removed.subscribe(o => {
  31. if (!SO.isBehavior(o.obj)) return;
  32. o.obj.data.unregister();
  33. });
  34. ctx.events.state.object.updated.subscribe(o => {
  35. if (o.action === 'recreate') {
  36. if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister();
  37. if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register(o.ref);
  38. }
  39. });
  40. }
  41. export function SetCurrentObject(ctx: PluginContext) {
  42. PluginCommands.State.SetCurrentObject.subscribe(ctx, ({ state, ref }) => state.setCurrent(ref));
  43. }
  44. export function Update(ctx: PluginContext) {
  45. PluginCommands.State.Update.subscribe(ctx, ({ state, tree, doNotLogTiming }) => ctx.runTask(state.updateTree(tree, doNotLogTiming)));
  46. }
  47. export function ApplyAction(ctx: PluginContext) {
  48. PluginCommands.State.ApplyAction.subscribe(ctx, ({ state, action, ref }) => ctx.runTask(state.applyAction(action.action, action.params, ref)));
  49. }
  50. export function RemoveObject(ctx: PluginContext) {
  51. function remove(state: State, ref: string) {
  52. const tree = state.build().delete(ref).getTree();
  53. return ctx.runTask(state.updateTree(tree));
  54. }
  55. PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref, removeParentGhosts }) => {
  56. if (removeParentGhosts) {
  57. const tree = state.tree;
  58. let curr = tree.transforms.get(ref);
  59. if (curr.parent === ref) return remove(state, ref);
  60. while (true) {
  61. const children = tree.children.get(curr.parent);
  62. if (curr.parent === curr.ref || children.size > 1) return remove(state, curr.ref);
  63. const parent = tree.transforms.get(curr.parent);
  64. if (!parent.props || !parent.props.isGhost) return remove(state, curr.ref);
  65. curr = parent;
  66. }
  67. } else {
  68. remove(state, ref);
  69. }
  70. });
  71. }
  72. export function ToggleExpanded(ctx: PluginContext) {
  73. PluginCommands.State.ToggleExpanded.subscribe(ctx, ({ state, ref }) => state.updateCellState(ref, ({ isCollapsed }) => ({ isCollapsed: !isCollapsed })));
  74. }
  75. export function ToggleVisibility(ctx: PluginContext) {
  76. PluginCommands.State.ToggleVisibility.subscribe(ctx, ({ state, ref }) => setVisibility(state, ref, !state.cellStates.get(ref).isHidden));
  77. }
  78. function setVisibility(state: State, root: StateTransform.Ref, value: boolean) {
  79. StateTree.doPreOrder(state.tree, state.transforms.get(root), { state, value }, setVisibilityVisitor);
  80. }
  81. function setVisibilityVisitor(t: StateTransform, tree: StateTree, ctx: { state: State, value: boolean }) {
  82. ctx.state.updateCellState(t.ref, { isHidden: ctx.value });
  83. }
  84. // TODO make isHighlighted and isSelect part of StateObjectCell.State and subscribe from there???
  85. // TODO select structures of subtree
  86. // TODO should also work for volumes and shapes
  87. export function Highlight(ctx: PluginContext) {
  88. PluginCommands.State.Highlight.subscribe(ctx, ({ state, ref }) => {
  89. // const cell = state.select(ref)[0]
  90. // const repr = cell && SO.isRepresentation3D(cell.obj) ? cell.obj.data : undefined
  91. // if (cell && cell.obj && cell.obj.type === PluginStateObject.Molecule.Structure.type) {
  92. // ctx.events.canvas3d.highlight.next({ current: { loci: Structure.Loci(cell.obj.data) } });
  93. // } else if (repr) {
  94. // ctx.events.canvas3d.highlight.next({ current: { loci: EveryLoci, repr } });
  95. // }
  96. });
  97. }
  98. export function ClearHighlight(ctx: PluginContext) {
  99. PluginCommands.State.ClearHighlight.subscribe(ctx, ({ state, ref }) => {
  100. // ctx.events.canvas3d.highlight.next({ current: { loci: EmptyLoci } });
  101. });
  102. }
  103. export function Snapshots(ctx: PluginContext) {
  104. PluginCommands.State.Snapshots.Clear.subscribe(ctx, () => {
  105. ctx.state.snapshots.clear();
  106. });
  107. PluginCommands.State.Snapshots.Remove.subscribe(ctx, ({ id }) => {
  108. ctx.state.snapshots.remove(id);
  109. });
  110. PluginCommands.State.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
  111. const entry = PluginStateSnapshotManager.Entry(ctx.state.getSnapshot(), name, description);
  112. ctx.state.snapshots.add(entry);
  113. });
  114. PluginCommands.State.Snapshots.Apply.subscribe(ctx, ({ id }) => {
  115. const e = ctx.state.snapshots.getEntry(id);
  116. return ctx.state.setSnapshot(e.snapshot);
  117. });
  118. PluginCommands.State.Snapshots.Upload.subscribe(ctx, ({ name, description, serverUrl }) => {
  119. return fetch(`${serverUrl}/set?name=${encodeURIComponent(name || '')}&description=${encodeURIComponent(description || '')}`, {
  120. method: 'POST',
  121. mode: 'cors',
  122. referrer: 'no-referrer',
  123. headers: { 'Content-Type': 'application/json; charset=utf-8' },
  124. body: JSON.stringify(ctx.state.getSnapshot())
  125. }) as any as Promise<void>;
  126. });
  127. PluginCommands.State.Snapshots.Fetch.subscribe(ctx, async ({ url }) => {
  128. const req = await fetch(url, { referrer: 'no-referrer' });
  129. const json = await req.json();
  130. return ctx.state.setSnapshot(json.data);
  131. });
  132. PluginCommands.State.Snapshots.DownloadToFile.subscribe(ctx, ({ name }) => {
  133. const element = document.createElement('a');
  134. const json = JSON.stringify(ctx.state.getSnapshot(), null, 2);
  135. element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(json));
  136. element.setAttribute('download', `mol-star_state_${(name || getFormattedTime())}.json`);
  137. element.style.display = 'none';
  138. document.body.appendChild(element);
  139. element.click();
  140. document.body.removeChild(element);
  141. });
  142. PluginCommands.State.Snapshots.OpenFile.subscribe(ctx, async ({ file }) => {
  143. try {
  144. const data = await readFromFile(file, 'string').run();
  145. const json = JSON.parse(data as string);
  146. await ctx.state.setSnapshot(json);
  147. } catch (e) {
  148. ctx.log.error(`Reading JSON state: ${e}`);
  149. }
  150. });
  151. }