state.ts 7.4 KB

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