diff --git a/src/apps/viewer/embedded.html b/src/apps/viewer/embedded.html new file mode 100644 index 0000000000000000000000000000000000000000..b444fe406c84ef491433b03ff1e8286cc2462415 --- /dev/null +++ b/src/apps/viewer/embedded.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> + <link rel="icon" href="./favicon.ico" type="image/x-icon"> + <title>Embedded Mol* Viewer</title> + <style> + #app { + position: absolute; + left: 100px; + top: 100px; + width: 800px; + height: 600px; + } + </style> + <link rel="stylesheet" type="text/css" href="molstar.css" /> + </head> + <body> + <div id="app"></div> + <script type="text/javascript" src="./molstar.js"></script> + <script type="text/javascript"> + var viewer = new molstar.Viewer('app', { + extensions: [], + + layoutIsExpanded: false, + layoutShowControls: false, + layoutShowRemoteState: false, + layoutShowSequence: true, + layoutShowLog: false, + layoutShowLeftPanel: true, + + viewportShowExpand: true, + viewportShowSelectionMode: false, + viewportShowAnimation: false, + + pdbProvider: 'rcsb', + emdbProvider: 'rcsb', + }); + viewer.loadPdb('7bv2'); + viewer.loadEmdb('EMD-30210'); + + // TODO add Volume.customProperty and load suggested isoValue via custom property + var sub = viewer.plugin.managers.volume.hierarchy.behaviors.selection.subscribe(function (value) { + if (value.volume?.representations[0]) { + var ref = value.volume.representations[0].cell; + var tree = viewer.plugin.state.data.build().to(ref).update({ + type: { + name: 'isosurface', + params: { + isoValue: { + kind: 'relative', + relativeValue: 6 + } + } + }, + colorTheme: ref.transform.params?.colorTheme + }); + viewer.plugin.runTask(viewer.plugin.state.data.updateTree(tree)); + if (typeof sub !== 'undefined') sub.unsubscribe(); + } + }); + </script> + </body> +</html> \ No newline at end of file diff --git a/src/apps/viewer/index.html b/src/apps/viewer/index.html index 09d060d8854cdb4c98af6e342a4f1a0d7ef89a5a..0b95a7ae2e192452457bc5dfa849028adc67f81f 100644 --- a/src/apps/viewer/index.html +++ b/src/apps/viewer/index.html @@ -34,10 +34,43 @@ height: 600px; } </style> - <link rel="stylesheet" type="text/css" href="app.css" /> + <link rel="stylesheet" type="text/css" href="molstar.css" /> </head> <body> <div id="app"></div> - <script type="text/javascript" src="./index.js"></script> + <script type="text/javascript" src="./molstar.js"></script> + <script type="text/javascript"> + function getParam(name, regex) { + var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i'); + return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || ''); + } + + var hideControls = getParam('hide-controls', '[^&]+').trim() === '1'; + var viewer = new molstar.Viewer('app', { + layoutShowControls: !hideControls, + viewportShowExpand: false, + }); + + var snapshotId = getParam('snapshot-id', '[^&]+').trim(); + if (snapshotId) viewer.setRemoteSnapshot(snapshotId); + + var snapshotUrl = getParam('snapshot-url', '[^&]+').trim(); + var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim(); + if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType); + + var structureUrl = getParam('structure-url', '[^&]+').trim(); + var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim(); + var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1'; + if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary); + + var pdb = getParam('pdb', '[^&]+').trim(); + if (pdb) viewer.loadPdb(pdb); + + var pdbDev = getParam('pdb-dev', '[^&]+').trim(); + if (pdbDev) viewer.loadPdbDev(pdbDev); + + var emdb = getParam('emdb', '[^&]+').trim(); + if (emdb) viewer.loadEmdb(emdb); + </script> </body> </html> \ No newline at end of file diff --git a/src/apps/viewer/index.ts b/src/apps/viewer/index.ts index 719b18d1bb49e9097000d12f29d4eec5ac3158be..ac345b63459109411885701703c3804982008fca 100644 --- a/src/apps/viewer/index.ts +++ b/src/apps/viewer/index.ts @@ -8,84 +8,110 @@ import '../../mol-util/polyfill'; import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; import './index.html'; +import './embedded.html'; import './favicon.ico'; import { PluginContext } from '../../mol-plugin/context'; import { PluginCommands } from '../../mol-plugin/commands'; import { PluginSpec } from '../../mol-plugin/spec'; -import { DownloadStructure } from '../../mol-plugin-state/actions/structure'; +import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure'; import { PluginConfig } from '../../mol-plugin/config'; import { CellPack } from '../../extensions/cellpack'; import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb'; import { PDBeStructureQualityReport } from '../../extensions/pdbe'; import { Asset } from '../../mol-util/assets'; +import { ObjectKeys } from '../../mol-util/type-helpers'; +import { PluginState } from '../../mol-plugin/state'; +import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; +import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout'; require('mol-plugin-ui/skin/light.scss'); -function getParam(name: string, regex: string): string { - let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); - return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || ''); -} +const Extensions = { + 'cellpack': PluginSpec.Behavior(CellPack), + 'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport), + 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry), + 'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport) +}; -const hideControls = getParam('hide-controls', `[^&]+`) === '1'; +const DefaultViewerOptions = { + extensions: ObjectKeys(Extensions), + layoutIsExpanded: true, + layoutShowControls: true, + layoutShowRemoteState: true, + layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay, + layoutShowSequence: true, + layoutShowLog: true, + layoutShowLeftPanel: true, -function init() { - const spec: PluginSpec = { - actions: [...DefaultPluginSpec.actions], - behaviors: [ - ...DefaultPluginSpec.behaviors, - PluginSpec.Behavior(CellPack), - PluginSpec.Behavior(PDBeStructureQualityReport), - PluginSpec.Behavior(RCSBAssemblySymmetry), - PluginSpec.Behavior(RCSBValidationReport), - ], - animations: [...DefaultPluginSpec.animations || []], - customParamEditors: DefaultPluginSpec.customParamEditors, - layout: { - initial: { - isExpanded: true, - showControls: !hideControls + viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue, + viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue, + viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue, + pluginStateServer: PluginConfig.State.DefaultServer.defaultValue, + volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue, + pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue, + emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue, +}; +type ViewerOptions = typeof DefaultViewerOptions; + +export class Viewer { + plugin: PluginContext + + constructor(elementId: string, options: Partial<ViewerOptions> = {}) { + const o = { ...DefaultViewerOptions, ...options }; + + const spec: PluginSpec = { + actions: [...DefaultPluginSpec.actions], + behaviors: [ + ...DefaultPluginSpec.behaviors, + ...o.extensions.map(e => Extensions[e]), + ], + animations: [...DefaultPluginSpec.animations || []], + customParamEditors: DefaultPluginSpec.customParamEditors, + layout: { + initial: { + isExpanded: o.layoutIsExpanded, + showControls: o.layoutShowControls, + controlsDisplay: o.layoutControlsDisplay, + }, + controls: { + ...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls, + top: o.layoutShowSequence ? undefined : 'none', + bottom: o.layoutShowLog ? undefined : 'none', + left: o.layoutShowLeftPanel ? undefined : 'none', + } }, - controls: { - ...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls - } - }, - config: DefaultPluginSpec.config - }; - spec.config?.set(PluginConfig.Viewport.ShowExpand, false); - const plugin = createPlugin(document.getElementById('app')!, spec); - trySetSnapshot(plugin); - tryLoadFromUrl(plugin); -} + components: { + ...DefaultPluginSpec.components, + remoteState: o.layoutShowRemoteState ? 'default' : 'none', + }, + config: DefaultPluginSpec.config + }; -async function trySetSnapshot(ctx: PluginContext) { - try { - const snapshotUrl = getParam('snapshot-url', `[^&]+`); - const snapshotId = getParam('snapshot-id', `[^&]+`); - if (!snapshotUrl && !snapshotId) return; - // TODO parametrize the server - const url = snapshotId - ? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}` - : snapshotUrl; - await PluginCommands.State.Snapshots.Fetch(ctx, { url }); - } catch (e) { - ctx.log.error('Failed to load snapshot.'); - console.warn('Failed to load snapshot', e); - } -} + spec.config?.set(PluginConfig.Viewport.ShowExpand, o.viewportShowExpand); + spec.config?.set(PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode); + spec.config?.set(PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation); + spec.config?.set(PluginConfig.State.DefaultServer, o.pluginStateServer); + spec.config?.set(PluginConfig.State.CurrentServer, o.pluginStateServer); + spec.config?.set(PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer); + spec.config?.set(PluginConfig.Download.DefaultPdbProvider, o.pdbProvider); + spec.config?.set(PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider); -async function tryLoadFromUrl(ctx: PluginContext) { - const url = getParam('loadFromURL', '[^&]+').trim(); - try { - if (!url) return; + const element = document.getElementById(elementId); + if (!element) throw new Error(`Could not get element with id '${elementId}'`); + this.plugin = createPlugin(element, spec); + } - let format = 'cif', isBinary = false; - switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) { - case 'pdb': format = 'pdb'; break; - case 'mmbcif': isBinary = true; break; - } + async setRemoteSnapshot(id: string) { + const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`; + await PluginCommands.State.Snapshots.Fetch(this.plugin, { url }); + } - const params = DownloadStructure.createDefaultParams(void 0 as any, ctx); + async loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) { + await PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type }); + } - return ctx.runTask(ctx.state.data.applyAction(DownloadStructure, { + async loadStructureFromUrl(url: string, format = 'cif', isBinary = false) { + const params = DownloadStructure.createDefaultParams(undefined, this.plugin); + return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { source: { name: 'url', params: { @@ -96,10 +122,54 @@ async function tryLoadFromUrl(ctx: PluginContext) { } } })); - } catch (e) { - ctx.log.error(`Failed to load from URL (${url})`); - console.warn(`Failed to load from URL (${url})`, e); } -} -init(); \ No newline at end of file + async loadPdb(pdb: string) { + const params = DownloadStructure.createDefaultParams(undefined, this.plugin); + const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!; + return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { + source: { + name: 'pdb' as const, + params: { + provider: { + id: pdb, + server: { + name: provider, + params: PdbDownloadProvider[provider].defaultValue as any + } + }, + options: params.source.params.options, + } + } + })); + } + + async loadPdbDev(pdbDev: string) { + const params = DownloadStructure.createDefaultParams(undefined, this.plugin); + return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { + source: { + name: 'pdb-dev' as const, + params: { + id: pdbDev, + options: params.source.params.options, + } + } + })); + } + + async loadEmdb(emdb: string) { + const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!; + return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, { + source: { + name: 'pdb-emd-ds' as const, + params: { + provider: { + id: emdb, + server: provider, + }, + detail: 3, + } + } + })); + } +} \ No newline at end of file diff --git a/src/examples/basic-wrapper/index.html b/src/examples/basic-wrapper/index.html index e803d34244d39482e3fc7c96de5e6e2078312e5d..9e936e50fea4c88d8cecac511a30674d3319d258 100644 --- a/src/examples/basic-wrapper/index.html +++ b/src/examples/basic-wrapper/index.html @@ -41,7 +41,7 @@ display: block; } </style> - <link rel="stylesheet" type="text/css" href="app.css" /> + <link rel="stylesheet" type="text/css" href="molstar.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> @@ -55,13 +55,13 @@ </select> </div> <div id="app"></div> - <script> + <script> function $(id) { return document.getElementById(id); } - + var pdbId = '1grm', assemblyId= '1'; var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; var format = 'mmcif'; - + $('url').value = url; $('url').onchange = function (e) { url = e.target.value; } $('assemblyId').value = assemblyId; @@ -86,7 +86,7 @@ addHeader('Camera'); addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin()); - + addSeparator(); addHeader('Animation'); @@ -115,7 +115,7 @@ addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition()); addControl('Dynamic Superposition', () => BasicMolStarWrapper.tests.dynamicSuperposition()); addControl('Validation Tooltip', () => BasicMolStarWrapper.tests.toggleValidationTooltip()); - + addControl('Show Toasts', () => BasicMolStarWrapper.tests.showToasts()); addControl('Hide Toasts', () => BasicMolStarWrapper.tests.hideToasts()); diff --git a/src/examples/lighting/index.html b/src/examples/lighting/index.html index 97439a81da380ba504d71b0363f7c0869d28143a..e3602a78de291daa73f9355b40ff19e13d3594b4 100644 --- a/src/examples/lighting/index.html +++ b/src/examples/lighting/index.html @@ -38,16 +38,16 @@ display: block; } </style> - <link rel="stylesheet" type="text/css" href="app.css" /> + <link rel="stylesheet" type="text/css" href="molstar.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> <div id='controls'></div> <div id="app"></div> - <script> + <script> LightingDemo.init('app') LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }) - + addHeader('Example PDB IDs'); addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })); addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' })); diff --git a/src/examples/proteopedia-wrapper/index.html b/src/examples/proteopedia-wrapper/index.html index 058a41c2ce84a21ffd9fa31410ffaea7cc2a871b..48cc5f8117a580f069e6af63812266ff5bfed165 100644 --- a/src/examples/proteopedia-wrapper/index.html +++ b/src/examples/proteopedia-wrapper/index.html @@ -48,7 +48,7 @@ width: 300px; } </style> - <link rel="stylesheet" type="text/css" href="app.css" /> + <link rel="stylesheet" type="text/css" href="molstar.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> @@ -65,7 +65,7 @@ <div id="app"></div> <div id="volume-streaming-wrapper"></div> <script> - // it might be a good idea to define these colors in a separate script file + // it might be a good idea to define these colors in a separate script file var CustomColors = [0x00ff00, 0x0000ff]; // create an instance of the plugin @@ -74,11 +74,11 @@ console.log('Wrapper version', MolStarProteopediaWrapper.VERSION_MAJOR, MolStarProteopediaWrapper.VERSION_MINOR); function $(id) { return document.getElementById(id); } - + var pdbId = '1cbs', assemblyId= 'preferred', isBinary = true; var url = 'https://www.ebi.ac.uk/pdbe/entry-files/download/' + pdbId + '.bcif' var format = 'cif'; - + $('url').value = url; $('url').onchange = function (e) { url = e.target.value; } $('assemblyId').value = assemblyId; @@ -144,7 +144,7 @@ // Same as "wheel icon" and Viewport options // addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] })); // addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] })); - + addSeparator(); addHeader('Animation'); @@ -177,7 +177,7 @@ addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper'))); addControl('Remove', () => PluginWrapper.experimentalData.remove()); - addSeparator(); + addSeparator(); addHeader('State'); var snapshot; diff --git a/webpack.config.common.js b/webpack.config.common.js index f062c5b85650410c96e66352dfb00e6661a3d1da..b97efd4fa7d8ff61fe5599214e3da3dc22b28c47 100644 --- a/webpack.config.common.js +++ b/webpack.config.common.js @@ -43,7 +43,7 @@ const sharedConfig = { // __VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG) }), - new MiniCssExtractPlugin({ filename: 'app.css' }), + new MiniCssExtractPlugin({ filename: 'molstar.css', }), new VersionFile({ extras: { timestamp: `${new Date().valueOf()}` }, packageFile: path.resolve(__dirname, 'package.json'), @@ -73,11 +73,11 @@ function createEntry(src, outFolder, outFilename, isNode) { } } -function createEntryPoint(name, dir, out) { +function createEntryPoint(name, dir, out, library) { return { node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts entry: path.resolve(__dirname, `lib/${dir}/${name}.js`), - output: { filename: `${name}.js`, path: path.resolve(__dirname, `build/${out}`) }, + output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' }, ...sharedConfig } } @@ -97,7 +97,7 @@ function createNodeEntryPoint(name, dir, out) { } } -function createApp(name) { return createEntryPoint('index', `apps/${name}`, name) } +function createApp(name, library) { return createEntryPoint('index', `apps/${name}`, name, library) } function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index') } function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests') } function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name) } diff --git a/webpack.config.viewer.js b/webpack.config.viewer.js index 7286ed61aec21c9f77a79683da3ffa288fbd6ab0..9aa128f23bc8a926ff8f036368440ef87f8ee01d 100644 --- a/webpack.config.viewer.js +++ b/webpack.config.viewer.js @@ -1,5 +1,5 @@ const common = require('./webpack.config.common.js'); const createApp = common.createApp; module.exports = [ - createApp('viewer') + createApp('viewer', 'molstar') ] \ No newline at end of file