index.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { BehaviorSubject } from 'rxjs';
  7. import { DefaultPluginSpec } from 'molstar/lib/mol-plugin';
  8. import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin'
  9. import './index.html'
  10. import './favicon.ico'
  11. import { PluginContext } from 'molstar/lib/mol-plugin/context';
  12. import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
  13. import { ViewerState as ViewerState, CollapsedState, ModelUrlProvider } from './types';
  14. import { PluginSpec } from 'molstar/lib/mol-plugin/spec';
  15. import { ColorNames } from 'molstar/lib/mol-util/color/names';
  16. import ReactDOM = require('react-dom');
  17. import React = require('react');
  18. import { ModelLoader } from './helpers/model';
  19. import { PresetProps, RcsbSuperpositionRepresentationPreset } from './helpers/preset';
  20. import { ControlsWrapper } from './ui/controls';
  21. import { PluginConfig } from 'molstar/lib/mol-plugin/config';
  22. import { RCSBAssemblySymmetry } from 'molstar/lib/extensions/rcsb/assembly-symmetry/behavior';
  23. import { RCSBValidationReport } from 'molstar/lib/extensions/rcsb/validation-report/behavior';
  24. import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
  25. import { PluginState } from 'molstar/lib/mol-plugin/state';
  26. import { BuiltInTrajectoryFormat } from 'molstar/lib/mol-plugin-state/formats/trajectory';
  27. import { ObjectKeys } from 'molstar/lib/mol-util/type-helpers';
  28. import { PluginLayoutControlsDisplay } from 'molstar/lib/mol-plugin/layout';
  29. import { SuperposeColorThemeProvider } from './helpers/superpose/color';
  30. import { encodeStructureData, downloadAsZipFile } from './helpers/export';
  31. require('./skin/rcsb.scss')
  32. /** package version, filled in at bundle build time */
  33. declare const __RCSB_MOLSTAR_VERSION__: string
  34. export const RCSB_MOLSTAR_VERSION = __RCSB_MOLSTAR_VERSION__;
  35. /** unix time stamp, to be filled in at bundle build time */
  36. declare const __BUILD_TIMESTAMP__: number
  37. export const BUILD_TIMESTAMP = __BUILD_TIMESTAMP__;
  38. export const BUILD_DATE = new Date(BUILD_TIMESTAMP);
  39. const Extensions = {
  40. 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
  41. 'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport)
  42. };
  43. const DefaultViewerProps = {
  44. showImportControls: false,
  45. showExportControls: false,
  46. showSessionControls: false,
  47. modelUrlProviders: [
  48. (pdbId: string) => ({
  49. url: `//models.rcsb.org/${pdbId.toLowerCase()}.bcif`,
  50. format: 'mmcif',
  51. isBinary: true
  52. }),
  53. (pdbId: string) => ({
  54. url: `//files.rcsb.org/download/${pdbId.toLowerCase()}.cif`,
  55. format: 'mmcif',
  56. isBinary: false
  57. })
  58. ] as ModelUrlProvider[],
  59. extensions: ObjectKeys(Extensions),
  60. layoutIsExpanded: false,
  61. layoutShowControls: true,
  62. layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
  63. layoutShowSequence: true,
  64. layoutShowLog: false,
  65. viewportShowExpand: true,
  66. viewportShowSelectionMode: true,
  67. volumeStreamingServer: '//maps.rcsb.org/',
  68. backgroundColor: ColorNames.white,
  69. showWelcomeToast: true,
  70. panelVisibility: {
  71. selection: false,
  72. measurements: true,
  73. superposition: false,
  74. component: true,
  75. volume: false,
  76. custom: false
  77. }
  78. };
  79. type ViewerProps = typeof DefaultViewerProps
  80. export class Viewer {
  81. private readonly plugin: PluginContext;
  82. private readonly modelUrlProviders: ModelUrlProvider[];
  83. private get customState() {
  84. return this.plugin.customState as ViewerState
  85. }
  86. constructor(target: string | HTMLElement, props: Partial<ViewerProps> = {}) {
  87. target = typeof target === 'string' ? document.getElementById(target)! : target
  88. const o = { ...DefaultViewerProps, ...props }
  89. const spec: PluginSpec = {
  90. actions: [...DefaultPluginSpec.actions],
  91. behaviors: [
  92. ...DefaultPluginSpec.behaviors,
  93. ...o.extensions.map(e => Extensions[e]),
  94. ],
  95. animations: [...DefaultPluginSpec.animations || []],
  96. customParamEditors: DefaultPluginSpec.customParamEditors,
  97. layout: {
  98. initial: {
  99. isExpanded: o.layoutIsExpanded,
  100. showControls: o.layoutShowControls,
  101. controlsDisplay: o.layoutControlsDisplay,
  102. },
  103. controls: {
  104. ...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
  105. top: o.layoutShowSequence ? undefined : 'none',
  106. bottom: o.layoutShowLog ? undefined : 'none',
  107. // left: 'none',
  108. right: ControlsWrapper,
  109. }
  110. },
  111. components: {
  112. ...DefaultPluginSpec.components,
  113. remoteState: 'none',
  114. },
  115. config: [
  116. [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
  117. [PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
  118. [PluginConfig.Viewport.ShowAnimation, false],
  119. [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
  120. [PluginConfig.Download.DefaultPdbProvider, 'rcsb'],
  121. [PluginConfig.Download.DefaultEmdbProvider, 'rcsb']
  122. ]
  123. };
  124. this.plugin = new PluginContext(spec);
  125. this.modelUrlProviders = o.modelUrlProviders;
  126. (this.plugin.customState as ViewerState) = {
  127. showImportControls: o.showImportControls,
  128. showExportControls: o.showExportControls,
  129. showSessionControls: o.showSessionControls,
  130. modelLoader: new ModelLoader(this.plugin),
  131. collapsed: new BehaviorSubject<CollapsedState>({
  132. selection: true,
  133. measurements: true,
  134. superposition: true,
  135. component: false,
  136. volume: true,
  137. custom: true,
  138. }),
  139. visibility: new BehaviorSubject<CollapsedState>({ ...o.panelVisibility })
  140. }
  141. this.plugin.init();
  142. ReactDOM.render(React.createElement(Plugin, { plugin: this.plugin }), target)
  143. const renderer = this.plugin.canvas3d!.props.renderer;
  144. PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: o.backgroundColor } } });
  145. this.plugin.representation.structure.themes.colorThemeRegistry.add(SuperposeColorThemeProvider);
  146. // this.plugin.builders.structure.representation.registerPreset(RcsbSuperpositionRepresentationPreset);
  147. if (o.showWelcomeToast) {
  148. PluginCommands.Toast.Show(this.plugin, {
  149. title: 'Welcome',
  150. message: `RCSB PDB Mol* Viewer ${RCSB_MOLSTAR_VERSION} [${BUILD_DATE.toLocaleString()}]`,
  151. key: 'toast-welcome',
  152. timeoutMs: 5000
  153. })
  154. }
  155. this.prevExpanded = this.plugin.layout.state.isExpanded;
  156. this.plugin.layout.events.updated.subscribe(() => this.toggleControls());
  157. }
  158. private prevExpanded: boolean;
  159. private toggleControls(): void {
  160. const currExpanded = this.plugin.layout.state.isExpanded;
  161. const expanedChanged = (this.prevExpanded !== currExpanded);
  162. if (!expanedChanged) return;
  163. if (currExpanded && !this.plugin.layout.state.showControls) {
  164. this.plugin.layout.setProps({showControls: true});
  165. } else if (!currExpanded && this.plugin.layout.state.showControls) {
  166. this.plugin.layout.setProps({showControls: false});
  167. }
  168. this.prevExpanded = this.plugin.layout.state.isExpanded;
  169. }
  170. //
  171. resetCamera(durationMs?: number) {
  172. this.plugin.managers.camera.reset(undefined, durationMs);
  173. }
  174. clear() {
  175. const state = this.plugin.state.data;
  176. return PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref })
  177. }
  178. async loadPdbId(pdbId: string, props?: PresetProps, matrix?: Mat4) {
  179. for (const provider of this.modelUrlProviders) {
  180. try {
  181. const p = provider(pdbId)
  182. await this.customState.modelLoader.load({ fileOrUrl: p.url, format: p.format, isBinary: p.isBinary }, props, matrix)
  183. break
  184. } catch (e) {
  185. console.warn(`loading '${pdbId}' failed with '${e}', trying next model-loader-provider`)
  186. }
  187. }
  188. }
  189. async loadPdbIds(args: { pdbId: string, props?: PresetProps, matrix?: Mat4 }[]) {
  190. for (const { pdbId, props, matrix } of args) {
  191. await this.loadPdbId(pdbId, props, matrix);
  192. }
  193. this.resetCamera(0);
  194. }
  195. loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat, isBinary: boolean, props?: PresetProps, matrix?: Mat4) {
  196. return this.customState.modelLoader.load({ fileOrUrl: url, format, isBinary }, props, matrix)
  197. }
  198. loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
  199. return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
  200. }
  201. async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, isBinary: boolean, props?: PresetProps & { dataLabel?: string }, matrix?: Mat4) {
  202. return this.customState.modelLoader.parse({ data, format, isBinary }, props, matrix);
  203. }
  204. handleResize() {
  205. this.plugin.layout.events.updated.next();
  206. }
  207. async exportLoadedStructures() {
  208. const content = encodeStructureData(this.plugin);
  209. downloadAsZipFile(content);
  210. }
  211. }