Skip to content
Snippets Groups Projects
Commit 7c18e5eb authored by Alexander Rose's avatar Alexander Rose
Browse files

refactored viewer app to make it usable for simple embedded use-cases

parent 2a7d2587
No related branches found
No related tags found
No related merge requests found
<!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
...@@ -34,10 +34,43 @@ ...@@ -34,10 +34,43 @@
height: 600px; height: 600px;
} }
</style> </style>
<link rel="stylesheet" type="text/css" href="app.css" /> <link rel="stylesheet" type="text/css" href="molstar.css" />
</head> </head>
<body> <body>
<div id="app"></div> <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> </body>
</html> </html>
\ No newline at end of file
...@@ -8,84 +8,110 @@ ...@@ -8,84 +8,110 @@
import '../../mol-util/polyfill'; import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html'; import './index.html';
import './embedded.html';
import './favicon.ico'; import './favicon.ico';
import { PluginContext } from '../../mol-plugin/context'; import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands'; import { PluginCommands } from '../../mol-plugin/commands';
import { PluginSpec } from '../../mol-plugin/spec'; 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 { PluginConfig } from '../../mol-plugin/config';
import { CellPack } from '../../extensions/cellpack'; import { CellPack } from '../../extensions/cellpack';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb'; import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { PDBeStructureQualityReport } from '../../extensions/pdbe'; import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { Asset } from '../../mol-util/assets'; 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'); require('mol-plugin-ui/skin/light.scss');
function getParam(name: string, regex: string): string { const Extensions = {
let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); 'cellpack': PluginSpec.Behavior(CellPack),
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || ''); 'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
} 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport)
};
const DefaultViewerOptions = {
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
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;
const hideControls = getParam('hide-controls', `[^&]+`) === '1'; export class Viewer {
plugin: PluginContext
constructor(elementId: string, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
function init() {
const spec: PluginSpec = { const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions], actions: [...DefaultPluginSpec.actions],
behaviors: [ behaviors: [
...DefaultPluginSpec.behaviors, ...DefaultPluginSpec.behaviors,
PluginSpec.Behavior(CellPack), ...o.extensions.map(e => Extensions[e]),
PluginSpec.Behavior(PDBeStructureQualityReport),
PluginSpec.Behavior(RCSBAssemblySymmetry),
PluginSpec.Behavior(RCSBValidationReport),
], ],
animations: [...DefaultPluginSpec.animations || []], animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors, customParamEditors: DefaultPluginSpec.customParamEditors,
layout: { layout: {
initial: { initial: {
isExpanded: true, isExpanded: o.layoutIsExpanded,
showControls: !hideControls showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
}, },
controls: { controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls ...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
} }
}, },
components: {
...DefaultPluginSpec.components,
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: DefaultPluginSpec.config config: DefaultPluginSpec.config
}; };
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
const plugin = createPlugin(document.getElementById('app')!, spec);
trySetSnapshot(plugin);
tryLoadFromUrl(plugin);
}
async function trySetSnapshot(ctx: PluginContext) { spec.config?.set(PluginConfig.Viewport.ShowExpand, o.viewportShowExpand);
try { spec.config?.set(PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode);
const snapshotUrl = getParam('snapshot-url', `[^&]+`); spec.config?.set(PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation);
const snapshotId = getParam('snapshot-id', `[^&]+`); spec.config?.set(PluginConfig.State.DefaultServer, o.pluginStateServer);
if (!snapshotUrl && !snapshotId) return; spec.config?.set(PluginConfig.State.CurrentServer, o.pluginStateServer);
// TODO parametrize the server spec.config?.set(PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer);
const url = snapshotId spec.config?.set(PluginConfig.Download.DefaultPdbProvider, o.pdbProvider);
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}` spec.config?.set(PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider);
: snapshotUrl;
await PluginCommands.State.Snapshots.Fetch(ctx, { url });
} catch (e) {
ctx.log.error('Failed to load snapshot.');
console.warn('Failed to load snapshot', e);
}
}
async function tryLoadFromUrl(ctx: PluginContext) { const element = document.getElementById(elementId);
const url = getParam('loadFromURL', '[^&]+').trim(); if (!element) throw new Error(`Could not get element with id '${elementId}'`);
try { this.plugin = createPlugin(element, spec);
if (!url) return; }
let format = 'cif', isBinary = false; async setRemoteSnapshot(id: string) {
switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) { const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
case 'pdb': format = 'pdb'; break; await PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
case 'mmbcif': isBinary = true; break;
} }
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: { source: {
name: 'url', name: 'url',
params: { params: {
...@@ -96,10 +122,54 @@ async function tryLoadFromUrl(ctx: PluginContext) { ...@@ -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); 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,
}
}
}));
}
init(); async loadEmdb(emdb: string) {
\ No newline at end of file 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
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
display: block; display: block;
} }
</style> </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> <script type="text/javascript" src="./index.js"></script>
</head> </head>
<body> <body>
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
display: block; display: block;
} }
</style> </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> <script type="text/javascript" src="./index.js"></script>
</head> </head>
<body> <body>
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
width: 300px; width: 300px;
} }
</style> </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> <script type="text/javascript" src="./index.js"></script>
</head> </head>
<body> <body>
......
...@@ -43,7 +43,7 @@ const sharedConfig = { ...@@ -43,7 +43,7 @@ const sharedConfig = {
// __VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true), // __VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG) 'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
}), }),
new MiniCssExtractPlugin({ filename: 'app.css' }), new MiniCssExtractPlugin({ filename: 'molstar.css', }),
new VersionFile({ new VersionFile({
extras: { timestamp: `${new Date().valueOf()}` }, extras: { timestamp: `${new Date().valueOf()}` },
packageFile: path.resolve(__dirname, 'package.json'), packageFile: path.resolve(__dirname, 'package.json'),
...@@ -73,11 +73,11 @@ function createEntry(src, outFolder, outFilename, isNode) { ...@@ -73,11 +73,11 @@ function createEntry(src, outFolder, outFilename, isNode) {
} }
} }
function createEntryPoint(name, dir, out) { function createEntryPoint(name, dir, out, library) {
return { return {
node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
entry: path.resolve(__dirname, `lib/${dir}/${name}.js`), 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 ...sharedConfig
} }
} }
...@@ -97,7 +97,7 @@ function createNodeEntryPoint(name, dir, out) { ...@@ -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 createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index') }
function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests') } function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests') }
function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name) } function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name) }
......
const common = require('./webpack.config.common.js'); const common = require('./webpack.config.common.js');
const createApp = common.createApp; const createApp = common.createApp;
module.exports = [ module.exports = [
createApp('viewer') createApp('viewer', 'molstar')
] ]
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment