diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c2556fb983ce2989cdb35a33d63861ab3078df76 --- /dev/null +++ b/src/apps/basic-wrapper/index.html @@ -0,0 +1,44 @@ +<!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"> + <title>Mol* Viewer</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + #app { + position: absolute; + left: 100px; + top: 100px; + width: 600px; + height: 600px; + border: 1px solid #ccc; + } + </style> + <link rel="stylesheet" type="text/css" href="app.css" /> + <script type="text/javascript" src="./index.js"></script> + </head> + <body> + <button id='spin'>Toggle Spin</button> + <button id='asym'>Load Asym Unit</button> + <button id='asm'>Load Assemly 1</button> + <div id="app"></div> + <script> + var pdbId = '5ire', assemblyId= '1'; + var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; + + BasicMolStarWrapper.init('app' /** or document.getElementById('app') */); + BasicMolStarWrapper.setBackground(0xffffff); + BasicMolStarWrapper.loadCif(url, assemblyId); + BasicMolStarWrapper.toggleSpin(); + + document.getElementById('spin').onclick = () => BasicMolStarWrapper.toggleSpin(); + document.getElementById('asym').onclick = () => BasicMolStarWrapper.loadCif(url); + document.getElementById('asm').onclick = () => BasicMolStarWrapper.loadCif(url, assemblyId); + </script> + </body> +</html> \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a4cca600a8226afda02b5ec155e417adc970c75 --- /dev/null +++ b/src/apps/basic-wrapper/index.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { createPlugin, DefaultPluginSpec } from 'mol-plugin'; +import './index.html' +import { PluginContext } from 'mol-plugin/context'; +import { PluginCommands } from 'mol-plugin/command'; +import { StateTransforms } from 'mol-plugin/state/transforms'; +import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation'; +import { StateTree } from 'mol-state'; +import { Color } from 'mol-util/color'; +require('mol-plugin/skin/light.scss') + +class BasicWrapper { + plugin: PluginContext; + stateTemplate: StateTree; + + init(target: string | HTMLElement) { + this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { + ...DefaultPluginSpec, + initialLayout: { + isExpanded: false, + showControls: false + } + }); + + const state = this.plugin.state.dataState.build(); + const visualRoot = state.toRoot() + .apply(StateTransforms.Data.Download, { url: '', isBinary: false }, { ref: 'url' }) + .apply(StateTransforms.Data.ParseCif) + .apply(StateTransforms.Model.TrajectoryFromMmCif) + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) + .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: '' }, { ref: 'asm' }) + + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon')); + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick')); + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick', { alpha: 0.51 })); + visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill')); + + this.stateTemplate = state.getTree(); + } + + async loadCif(url: string, assemblyId?: string) { + const state = this.stateTemplate.build(); + + state.to('url').update(StateTransforms.Data.Download, p => ({ ...p, url })); + state.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId })); + + await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree: state }); + + PluginCommands.Camera.Reset.dispatch(this.plugin, { }); + } + + setBackground(color: number) { + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { backgroundColor: Color(color) } }); + } + + toggleSpin() { + const trackball = this.plugin.canvas3d.props.trackball; + const spinning = trackball.spin; + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } }); + if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { }); + } +} + +(window as any).BasicMolStarWrapper = new BasicWrapper(); \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 47a173e88898d0e6a67a11e2455ae49d32eb841c..4cd79396698daaec5bfdaaac9730919bb0e19481 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -104,19 +104,19 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ to: SO.Molecule.Structure, params(a) { if (!a) { - return { id: PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. If none specified (undefined or empty string), the asymmetric unit is used.' }) }; + return { id: PD.makeOptional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. If none specified (undefined or empty string), the asymmetric unit is used.' })) }; } const model = a.data; const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]); if (!ids.length) ids.push(['deposited', 'Deposited']) - return { id: PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' }) }; + return { id: PD.makeOptional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) }; } })({ apply({ a, params }, plugin: PluginContext) { return Task.create('Build Assembly', async ctx => { const model = a.data; const id = params.id; - const asm = ModelSymmetry.findAssembly(model, id); + const asm = ModelSymmetry.findAssembly(model, id || ''); if (!!id && id !== 'deposited' && !asm) throw new Error(`Assembly '${id}' not found`); const base = Structure.ofModel(model); diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index 9d5a802b96009435dadc2175cb8a9236034c8b9a..2a29958116c3495a7dc844ea0dd2e10f4643761a 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -24,15 +24,15 @@ export class TrajectoryControls extends PluginComponent { <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { state: this.plugin.state.dataState, action: UpdateTrajectory.create({ action: 'advance', by: -1 }) - })}>◀</button> + })} title='Previou Model'>◀</button> <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { state: this.plugin.state.dataState, action: UpdateTrajectory.create({ action: 'reset' }) - })}>↻</button> + })} title='First Model'>↻</button> <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { state: this.plugin.state.dataState, action: UpdateTrajectory.create({ action: 'advance', by: +1 }) - })}>►</button><br /> + })} title='Next Model'>►</button><br /> </div> } } diff --git a/webpack.config.js b/webpack.config.js index 50f0a8527b5b35631aa9128e46cf3514aaa9615f..b82967adcfb5d06a6480d21ea1fa65173529f11e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -87,6 +87,7 @@ function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name module.exports = [ createApp('viewer'), + createApp('basic-wrapper'), createNodeApp('state-docs'), createApp('model-server-query'),