index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. * @author Joan Segura <joan.segura@rcsb.org>
  6. * @author Yana Rose <yana.rose@rcsb.org>
  7. * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  8. */
  9. import { BehaviorSubject } from 'rxjs';
  10. import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
  11. import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
  12. import { ViewerState as ViewerState, CollapsedState, ModelUrlProvider } from './types';
  13. import { PluginSpec } from 'molstar/lib/mol-plugin/spec';
  14. import { ColorNames } from 'molstar/lib/mol-util/color/names';
  15. import * as React from 'react';
  16. import * as ReactDOM from 'react-dom';
  17. import { ModelLoader } from './helpers/model';
  18. import { PresetProps } from './helpers/preset';
  19. import { ControlsWrapper } from './ui/controls';
  20. import { PluginConfig } from 'molstar/lib/mol-plugin/config';
  21. import { RCSBAssemblySymmetry } from 'molstar/lib/extensions/rcsb/assembly-symmetry/behavior';
  22. import { RCSBValidationReport } from 'molstar/lib/extensions/rcsb/validation-report/behavior';
  23. import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
  24. import { PluginState } from 'molstar/lib/mol-plugin/state';
  25. import { BuiltInTrajectoryFormat } from 'molstar/lib/mol-plugin-state/formats/trajectory';
  26. import { ObjectKeys } from 'molstar/lib/mol-util/type-helpers';
  27. import { PluginLayoutControlsDisplay } from 'molstar/lib/mol-plugin/layout';
  28. import { SuperposeColorThemeProvider } from './helpers/superpose/color';
  29. import { encodeStructureData, downloadAsZipFile } from './helpers/export';
  30. import { setFocusFromRange, removeComponent, clearSelection, createComponent, select } from './helpers/viewer';
  31. import { SelectBase, SelectRange, SelectTarget, Target } from './helpers/selection';
  32. import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/registry';
  33. import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
  34. import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
  35. import { ANVILMembraneOrientation, MembraneOrientationPreset } from 'molstar/lib/extensions/anvil/behavior';
  36. import { MembraneOrientationRepresentationProvider } from 'molstar/lib/extensions/anvil/representation';
  37. import { PLDDTConfidenceScore } from './helpers/plddt-confidence/behavior';
  38. import { PluginContext } from 'molstar/lib/mol-plugin/context';
  39. import { TrajectoryHierarchyPresetProvider } from 'molstar/lib/mol-plugin-state/builder/structure/hierarchy-preset';
  40. import { AnimateStateSnapshots } from 'molstar/lib/mol-plugin-state/animation/built-in/state-snapshots';
  41. import { PluginFeatureDetection } from 'molstar/lib/mol-plugin/features';
  42. /** package version, filled in at bundle build time */
  43. declare const __RCSB_MOLSTAR_VERSION__: string;
  44. export const RCSB_MOLSTAR_VERSION = typeof __RCSB_MOLSTAR_VERSION__ != 'undefined' ? __RCSB_MOLSTAR_VERSION__ : 'none';
  45. /** unix time stamp, to be filled in at bundle build time */
  46. declare const __BUILD_TIMESTAMP__: number;
  47. export const BUILD_TIMESTAMP = typeof __BUILD_TIMESTAMP__ != 'undefined' ? __BUILD_TIMESTAMP__ : 'none';
  48. export const BUILD_DATE = new Date(BUILD_TIMESTAMP);
  49. const Extensions = {
  50. 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
  51. 'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
  52. 'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
  53. 'plddt-confidence': PluginSpec.Behavior(PLDDTConfidenceScore)
  54. };
  55. const DefaultViewerProps = {
  56. showImportControls: false,
  57. showExportControls: false,
  58. showSessionControls: false,
  59. showStructureSourceControls: true,
  60. showSuperpositionControls: true,
  61. showMembraneOrientationPreset: false,
  62. showValidationReportControls: true,
  63. /**
  64. * Needed when running outside of sierra. If set to true, the strucmotif UI will use an absolute URL to sierra-prod.
  65. * Otherwise, the link will be relative on the current host.
  66. */
  67. detachedFromSierra: false,
  68. modelUrlProviders: [
  69. (pdbId: string) => ({
  70. url: `https://models.rcsb.org/${pdbId.toLowerCase()}.bcif`,
  71. format: 'mmcif',
  72. isBinary: true
  73. }),
  74. (pdbId: string) => ({
  75. url: `https://files.rcsb.org/download/${pdbId.toLowerCase()}.cif`,
  76. format: 'mmcif',
  77. isBinary: false
  78. })
  79. ] as ModelUrlProvider[],
  80. extensions: ObjectKeys(Extensions),
  81. layoutIsExpanded: false,
  82. layoutShowControls: true,
  83. layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
  84. layoutShowSequence: true,
  85. layoutShowLog: false,
  86. viewportShowExpand: true,
  87. viewportShowSelectionMode: true,
  88. volumeStreamingServer: 'https://maps.rcsb.org/',
  89. backgroundColor: ColorNames.white,
  90. showWelcomeToast: true
  91. };
  92. export type ViewerProps = typeof DefaultViewerProps
  93. export class Viewer {
  94. private readonly _plugin: PluginUIContext;
  95. private readonly modelUrlProviders: ModelUrlProvider[];
  96. private prevExpanded: boolean;
  97. constructor(elementOrId: string | HTMLElement, props: Partial<ViewerProps> = {}) {
  98. const element = typeof elementOrId === 'string' ? document.getElementById(elementOrId)! : elementOrId;
  99. if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
  100. const o = { ...DefaultViewerProps, ...props };
  101. const defaultSpec = DefaultPluginUISpec();
  102. const spec: PluginUISpec = {
  103. ...defaultSpec,
  104. actions: defaultSpec.actions,
  105. behaviors: [
  106. ...defaultSpec.behaviors,
  107. ...o.extensions.map(e => Extensions[e]),
  108. ],
  109. animations: [...defaultSpec.animations?.filter(a => a.name !== AnimateStateSnapshots.name) || []],
  110. layout: {
  111. initial: {
  112. isExpanded: o.layoutIsExpanded,
  113. showControls: o.layoutShowControls,
  114. controlsDisplay: o.layoutControlsDisplay,
  115. },
  116. },
  117. components: {
  118. ...defaultSpec.components,
  119. controls: {
  120. ...defaultSpec.components?.controls,
  121. top: o.layoutShowSequence ? undefined : 'none',
  122. bottom: o.layoutShowLog ? undefined : 'none',
  123. left: 'none',
  124. right: ControlsWrapper,
  125. },
  126. remoteState: 'none',
  127. },
  128. config: [
  129. [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
  130. [PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
  131. [PluginConfig.Viewport.ShowAnimation, false],
  132. [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
  133. [PluginConfig.Download.DefaultPdbProvider, 'rcsb'],
  134. [PluginConfig.Download.DefaultEmdbProvider, 'rcsb'],
  135. // wboit & webgl1 checks are needed to work properly on recent Safari versions
  136. [PluginConfig.General.EnableWboit, PluginFeatureDetection.wboit],
  137. [PluginConfig.General.PreferWebGl1, PluginFeatureDetection.preferWebGl1]
  138. ]
  139. };
  140. this._plugin = new PluginUIContext(spec);
  141. this.modelUrlProviders = o.modelUrlProviders;
  142. (this._plugin.customState as ViewerState) = {
  143. showImportControls: o.showImportControls,
  144. showExportControls: o.showExportControls,
  145. showSessionControls: o.showSessionControls,
  146. showStructureSourceControls: o.showStructureSourceControls,
  147. showSuperpositionControls: o.showSuperpositionControls,
  148. showValidationReportControls: o.showValidationReportControls,
  149. modelLoader: new ModelLoader(this._plugin),
  150. collapsed: new BehaviorSubject<CollapsedState>({
  151. selection: true,
  152. strucmotifSubmit: true,
  153. measurements: true,
  154. superposition: true,
  155. component: false,
  156. volume: true,
  157. custom: true,
  158. // this must be set to true as the Mp4Controls depends on the canvas which will be undefined at init() time
  159. mp4export: true,
  160. validationReport: true
  161. }),
  162. detachedFromSierra: o.detachedFromSierra
  163. };
  164. this._plugin.init()
  165. .then(async () => {
  166. // hide 'Membrane Orientation' preset from UI - has to happen 'before' react render, apparently
  167. // the corresponding behavior must be registered either way, because the 3d-view uses it (even without appearing in the UI)
  168. if (!o.showMembraneOrientationPreset) {
  169. this._plugin.builders.structure.representation.unregisterPreset(MembraneOrientationPreset);
  170. this._plugin.representation.structure.registry.remove(MembraneOrientationRepresentationProvider);
  171. }
  172. ReactDOM.render(React.createElement(Plugin, { plugin: this._plugin }), element);
  173. const renderer = this._plugin.canvas3d!.props.renderer;
  174. await PluginCommands.Canvas3D.SetSettings(this._plugin, { settings: { renderer: { ...renderer, backgroundColor: o.backgroundColor } } });
  175. this._plugin.representation.structure.themes.colorThemeRegistry.add(SuperposeColorThemeProvider);
  176. if (o.showWelcomeToast) {
  177. await PluginCommands.Toast.Show(this._plugin, {
  178. title: 'Welcome',
  179. message: `RCSB PDB Mol* Viewer ${RCSB_MOLSTAR_VERSION} [${BUILD_DATE.toLocaleString()}]`,
  180. key: 'toast-welcome',
  181. timeoutMs: 5000
  182. });
  183. }
  184. this.prevExpanded = this._plugin.layout.state.isExpanded;
  185. this._plugin.layout.events.updated.subscribe(() => this.toggleControls());
  186. });
  187. }
  188. get plugin() {
  189. return this._plugin;
  190. }
  191. pluginCall(f: (plugin: PluginContext) => void) {
  192. f(this.plugin);
  193. }
  194. private get customState() {
  195. return this._plugin.customState as ViewerState;
  196. }
  197. private toggleControls(): void {
  198. const currExpanded = this._plugin.layout.state.isExpanded;
  199. const expandedChanged = (this.prevExpanded !== currExpanded);
  200. if (!expandedChanged) return;
  201. if (currExpanded && !this._plugin.layout.state.showControls) {
  202. this._plugin.layout.setProps({ showControls: true });
  203. } else if (!currExpanded && this._plugin.layout.state.showControls) {
  204. this._plugin.layout.setProps({ showControls: false });
  205. }
  206. this.prevExpanded = this._plugin.layout.state.isExpanded;
  207. }
  208. resetCamera(durationMs?: number) {
  209. this._plugin.managers.camera.reset(undefined, durationMs);
  210. }
  211. clear() {
  212. const state = this._plugin.state.data;
  213. return PluginCommands.State.RemoveObject(this._plugin, { state, ref: state.tree.root.ref });
  214. }
  215. async loadPdbId<P>(pdbId: string, config?: { props?: PresetProps; matrix?: Mat4; reprProvider?: TrajectoryHierarchyPresetProvider, params?: P }) {
  216. for (const provider of this.modelUrlProviders) {
  217. try {
  218. const p = provider(pdbId);
  219. await this.customState.modelLoader.load({ fileOrUrl: p.url, format: p.format, isBinary: p.isBinary }, config?.props, config?.matrix, config?.reprProvider, config?.params);
  220. break;
  221. } catch (e) {
  222. console.warn(`loading '${pdbId}' failed with '${e}', trying next model-loader-provider`);
  223. }
  224. }
  225. }
  226. async loadPdbIds<P>(args: { pdbId: string, config?: {props?: PresetProps; matrix?: Mat4; reprProvider?: TrajectoryHierarchyPresetProvider, params?: P} }[]) {
  227. for (const { pdbId, config } of args) {
  228. await this.loadPdbId(pdbId, config);
  229. }
  230. this.resetCamera(0);
  231. }
  232. loadStructureFromUrl<P>(url: string, format: BuiltInTrajectoryFormat, isBinary: boolean, config?: {props?: PresetProps; matrix?: Mat4; reprProvider?: TrajectoryHierarchyPresetProvider, params?: P}) {
  233. return this.customState.modelLoader.load({ fileOrUrl: url, format, isBinary }, config?.props, config?.matrix, config?.reprProvider, config?.params);
  234. }
  235. loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
  236. return PluginCommands.State.Snapshots.OpenUrl(this._plugin, { url, type });
  237. }
  238. loadStructureFromData<P>(data: string | number[], format: BuiltInTrajectoryFormat, isBinary: boolean, config?: {props?: PresetProps & { dataLabel?: string }; matrix?: Mat4; reprProvider?: TrajectoryHierarchyPresetProvider, params?: P}) {
  239. return this.customState.modelLoader.parse({ data, format, isBinary }, config?.props, config?.matrix, config?.reprProvider, config?.params);
  240. }
  241. handleResize() {
  242. this._plugin.layout.events.updated.next(void 0);
  243. }
  244. exportLoadedStructures() {
  245. const content = encodeStructureData(this._plugin);
  246. return downloadAsZipFile(this._plugin, content);
  247. }
  248. setFocus(target: SelectRange) {
  249. setFocusFromRange(this._plugin, target);
  250. }
  251. clearFocus(): void {
  252. this._plugin.managers.structure.focus.clear();
  253. }
  254. select(targets: SelectTarget | SelectTarget[], mode: 'select' | 'hover', modifier: 'add' | 'set') {
  255. select(this._plugin, targets, mode, modifier);
  256. }
  257. clearSelection(mode: 'select' | 'hover', target?: { modelId: string; } & Target) {
  258. clearSelection(this._plugin, mode, target);
  259. }
  260. async createComponent(label: string, targets: SelectBase | SelectTarget | SelectTarget[], representationType: StructureRepresentationRegistry.BuiltIn) {
  261. await createComponent(this._plugin, label, targets, representationType);
  262. }
  263. async removeComponent(componentLabel: string) {
  264. await removeComponent(this._plugin, componentLabel);
  265. }
  266. }