Browse Source

Merge pull request #321 from molstar/update-create-plugin

mol-plugin-ui/createPluginUI
Alexander Rose 3 years ago
parent
commit
1f262ee422

+ 4 - 0
CHANGELOG.md

@@ -24,6 +24,10 @@ Note that since we don't clearly distinguish between a public and private interf
     - pLDDT & qmean score: coloring, repr presets, molql symbol, loci labels (including avg for mutli-residue selections)
     - pLDDT: selection query
 - Warn about erroneous symmetry operator matrix (instead of throwing an error)
+- Added ``createPluginUI`` to ``mol-plugin-ui``
+    - Support ``onBeforeUIRender`` to make sure initial UI works with custom presets and similar features.
+- [Breaking] Removed ``createPlugin`` and ``createPluginAsync`` from ``mol-plugin-ui``
+    - Please use ``createPluginUI`` instead
 
 ## [v3.0.0-dev.5] - 2021-12-16
 

+ 6 - 6
src/apps/docking-viewer/index.html

@@ -19,8 +19,6 @@
         <div id="app"></div>
         <script type="text/javascript" src="./molstar.js"></script>
         <script type="text/javascript">
-            var viewer = new DockingViewer('app', [0x33DD22, 0x1133EE], true);
-
             function getParam(name, regex) {
                 var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
                 return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
@@ -28,10 +26,12 @@
             var pdbqt = getParam('pdbqt', '[^&]+').trim();
             var mol2 = getParam('mol2', '[^&]+').trim();
 
-            viewer.loadStructuresFromUrlsAndMerge([
-                { url: pdbqt, format: 'pdbqt' },
-                { url: mol2, format: 'mol2' }
-            ]);
+            DockingViewer.create('app', [0x33DD22, 0x1133EE], true).then(viewer => {
+                viewer.loadStructuresFromUrlsAndMerge([
+                    { url: pdbqt, format: 'pdbqt' },
+                    { url: mol2, format: 'mol2' }
+                ]);
+            });
         </script>
     </body>
 </html>

+ 12 - 9
src/apps/docking-viewer/index.ts

@@ -8,7 +8,7 @@
 import { Structure } from '../../mol-model/structure';
 import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
 import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
-import { createPlugin } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
 import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
@@ -54,9 +54,10 @@ const DefaultViewerOptions = {
 };
 
 class Viewer {
-    plugin: PluginUIContext;
+    constructor(public plugin: PluginUIContext) {
+    }
 
-    constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
+    static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
         const o = { ...DefaultViewerOptions, ...{
             layoutIsExpanded: false,
             layoutShowControls: false,
@@ -125,29 +126,31 @@ class Viewer {
             ? document.getElementById(elementOrId)
             : elementOrId;
         if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
-        this.plugin = createPlugin(element, spec);
+        const plugin = await createPluginUI(element, spec);
 
-        (this.plugin.customState as any) = {
+        (plugin.customState as any) = {
             colorPalette: {
                 name: 'colors',
                 params: { list: { colors } }
             }
         };
 
-        this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
+        plugin.behaviors.canvas3d.initialized.subscribe(v => {
             if (v) {
-                PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
+                PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
                     renderer: {
-                        ...this.plugin.canvas3d!.props.renderer,
+                        ...plugin.canvas3d!.props.renderer,
                         backgroundColor: ColorNames.white,
                     },
                     camera: {
-                        ...this.plugin.canvas3d!.props.camera,
+                        ...plugin.canvas3d!.props.camera,
                         helper: { axes: { name: 'off', params: {} } }
                     }
                 } });
             }
         });
+
+        return new Viewer(plugin);
     }
 
     async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {

+ 5 - 5
src/apps/viewer/embedded.html

@@ -20,7 +20,7 @@
         <div id="app"></div>
         <script type="text/javascript" src="./molstar.js"></script>
         <script type="text/javascript">
-            var viewer = new molstar.Viewer('app', {
+            molstar.Viewer.create('app', {
                 layoutIsExpanded: true,
                 layoutShowControls: false,
                 layoutShowRemoteState: false,
@@ -34,11 +34,11 @@
 
                 pdbProvider: 'rcsb',
                 emdbProvider: 'rcsb',
+            }).then(viewer => {
+                viewer.loadPdb('7bv2');
+                viewer.loadEmdb('EMD-30210', { detail: 6 });
+                // viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
             });
-            viewer.loadPdb('7bv2');
-            viewer.loadEmdb('EMD-30210', { detail: 6 });            
-
-            // viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
         </script>
     </body>
 </html>

+ 23 - 22
src/apps/viewer/index.html

@@ -56,7 +56,8 @@
             var pixelScale = getParam('pixel-scale', '[^&]+').trim();
             var pickScale = getParam('pick-scale', '[^&]+').trim();
             var pickPadding = getParam('pick-padding', '[^&]+').trim();
-            var viewer = new molstar.Viewer('app', {
+
+            molstar.Viewer.create('app', {
                 layoutShowControls: !hideControls,
                 viewportShowExpand: false,
                 collapseLeftPanel: collapseLeftPanel,
@@ -68,34 +69,34 @@
                 pixelScale: parseFloat(pixelScale) || 1,
                 pickScale: parseFloat(pickScale) || 0.25,
                 pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
-            });
+            }).then(viewer => {
+                var snapshotId = getParam('snapshot-id', '[^&]+').trim();
+                if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
 
-            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() || 'molj';
+                if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
 
-            var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
-            var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
-            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 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 pdb = getParam('pdb', '[^&]+').trim();
-            if (pdb) viewer.loadPdb(pdb);
+                var pdbDev = getParam('pdb-dev', '[^&]+').trim();
+                if (pdbDev) viewer.loadPdbDev(pdbDev);
 
-            var pdbDev = getParam('pdb-dev', '[^&]+').trim();
-            if (pdbDev) viewer.loadPdbDev(pdbDev);
+                var emdb = getParam('emdb', '[^&]+').trim();
+                if (emdb) viewer.loadEmdb(emdb);
 
-            var emdb = getParam('emdb', '[^&]+').trim();
-            if (emdb) viewer.loadEmdb(emdb);
+                var afdb = getParam('afdb', '[^&]+').trim();
+                if (afdb) viewer.loadAlphaFoldDb(afdb);
 
-            var afdb = getParam('afdb', '[^&]+').trim();
-            if (afdb) viewer.loadAlphaFoldDb(afdb);
-
-            var modelArchive = getParam('model-archive', '[^&]+').trim();
-            if (modelArchive) viewer.loadModelArchive(modelArchive);
+                var modelArchive = getParam('model-archive', '[^&]+').trim();
+                if (modelArchive) viewer.loadModelArchive(modelArchive);
+            });
         </script>
         <!-- __MOLSTAR_ANALYTICS__ -->
     </body>

+ 12 - 6
src/apps/viewer/index.ts

@@ -28,7 +28,7 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
 import { PluginStateObject } from '../../mol-plugin-state/objects';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
 import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
-import { createPlugin } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
 import { PluginCommands } from '../../mol-plugin/commands';
@@ -99,9 +99,10 @@ const DefaultViewerOptions = {
 type ViewerOptions = typeof DefaultViewerOptions;
 
 export class Viewer {
-    plugin: PluginUIContext;
+    constructor(public plugin: PluginUIContext) {
+    }
 
-    constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
+    static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
         const o = { ...DefaultViewerOptions, ...options };
         const defaultSpec = DefaultPluginUISpec();
 
@@ -162,9 +163,14 @@ export class Viewer {
             ? document.getElementById(elementOrId)
             : elementOrId;
         if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
-        this.plugin = createPlugin(element, spec);
-
-        this.plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
+        const plugin = await createPluginUI(element, spec, {
+            onBeforeUIRender: plugin => {
+                // the preset needs to be added before the UI renders otherwise
+                // "Download Structure" wont be able to pick it up
+                plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
+            }
+        });
+        return new Viewer(plugin);
     }
 
     setRemoteSnapshot(id: string) {

+ 3 - 3
src/examples/alpha-orbitals/index.ts

@@ -11,7 +11,7 @@ import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-f
 import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
 import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
 import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { createPluginAsync } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
 import { PluginCommands } from '../../mol-plugin/commands';
@@ -54,7 +54,7 @@ export class AlphaOrbitalsExample {
 
     async init(target: string | HTMLElement) {
         const defaultSpec = DefaultPluginUISpec();
-        this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
+        this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
             ...defaultSpec,
             layout: {
                 initial: {
@@ -67,7 +67,7 @@ export class AlphaOrbitalsExample {
             },
             canvas3d: {
                 camera: {
-                    helper: { axes: { name: 'off', params: { } } }
+                    helper: { axes: { name: 'off', params: {} } }
                 }
             },
             config: [

+ 3 - 2
src/examples/basic-wrapper/index.html

@@ -69,8 +69,9 @@
             $('format').value = format;
             $('format').onchange = function (e) { format = e.target.value; }
 
-            BasicMolStarWrapper.init('app' /** or document.getElementById('app') */);
-            BasicMolStarWrapper.setBackground(0xffffff);
+            BasicMolStarWrapper.init('app' /** or document.getElementById('app') */).then(() => {
+                BasicMolStarWrapper.setBackground(0xffffff);
+            });
 
             addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
             addControl('Load Assembly', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));

+ 4 - 4
src/examples/basic-wrapper/index.ts

@@ -9,7 +9,7 @@ import { EmptyLoci } from '../../mol-model/loci';
 import { StructureSelection } from '../../mol-model/structure';
 import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
 import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
-import { createPlugin } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
 import { PluginCommands } from '../../mol-plugin/commands';
@@ -28,8 +28,8 @@ type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: bo
 class BasicWrapper {
     plugin: PluginUIContext;
 
-    init(target: string | HTMLElement) {
-        this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
+    async init(target: string | HTMLElement) {
+        this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
             ...DefaultPluginUISpec(),
             layout: {
                 initial: {
@@ -60,7 +60,7 @@ class BasicWrapper {
                 params: { id: assemblyId }
             } : {
                 name: 'model',
-                params: { }
+                params: {}
             },
             showUnitcell: false,
             representationPreset: 'auto'

+ 3 - 2
src/examples/lighting/index.html

@@ -45,8 +45,9 @@
         <div id='controls'></div>
         <div id="app"></div>
         <script>
-            LightingDemo.init('app')
-            LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3)
+            LightingDemo.init('app').then(() => {
+                LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3);
+            });
 
             addHeader('Example PDB IDs');
             addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));

+ 27 - 25
src/examples/lighting/index.ts

@@ -6,7 +6,7 @@
 
 import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
 import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
-import { createPlugin } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
 import { PluginCommands } from '../../mol-plugin/commands';
@@ -21,7 +21,7 @@ type _Preset = Pick<Canvas3DProps, 'multiSample' | 'postprocessing' | 'renderer'
 type Preset = { [K in keyof _Preset]: Partial<_Preset[K]> }
 
 const Canvas3DPresets = {
-    illustrative: <Preset> {
+    illustrative: <Preset>{
         multiSample: {
             mode: 'temporal' as Canvas3DProps['multiSample']['mode']
         },
@@ -33,25 +33,25 @@ const Canvas3DPresets = {
             style: { name: 'flat', params: {} }
         }
     },
-    occlusion: <Preset> {
+    occlusion: <Preset>{
         multiSample: {
             mode: 'temporal' as Canvas3DProps['multiSample']['mode']
         },
         postprocessing: {
             occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
-            outline: { name: 'off', params: { } }
+            outline: { name: 'off', params: {} }
         },
         renderer: {
             style: { name: 'matte', params: {} }
         }
     },
-    standard: <Preset> {
+    standard: <Preset>{
         multiSample: {
             mode: 'off' as Canvas3DProps['multiSample']['mode']
         },
         postprocessing: {
-            occlusion: { name: 'off', params: { } },
-            outline: { name: 'off', params: { } }
+            occlusion: { name: 'off', params: {} },
+            outline: { name: 'off', params: {} }
         },
         renderer: {
             style: { name: 'matte', params: {} }
@@ -69,8 +69,8 @@ class LightingDemo {
     private bias = 1.1;
     private preset: Canvas3DPreset = 'illustrative';
 
-    init(target: string | HTMLElement) {
-        this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
+    async init(target: string | HTMLElement) {
+        this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
             ...DefaultPluginUISpec(),
             layout: {
                 initial: {
@@ -92,21 +92,23 @@ class LightingDemo {
             props.postprocessing.occlusion.params.radius = this.radius;
             props.postprocessing.occlusion.params.bias = this.bias;
         }
-        PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
-            ...props,
-            multiSample: {
-                ...this.plugin.canvas3d!.props.multiSample,
-                ...props.multiSample
-            },
-            renderer: {
-                ...this.plugin.canvas3d!.props.renderer,
-                ...props.renderer
-            },
-            postprocessing: {
-                ...this.plugin.canvas3d!.props.postprocessing,
-                ...props.postprocessing
-            },
-        } });
+        PluginCommands.Canvas3D.SetSettings(this.plugin, {
+            settings: {
+                ...props,
+                multiSample: {
+                    ...this.plugin.canvas3d!.props.multiSample,
+                    ...props.multiSample
+                },
+                renderer: {
+                    ...this.plugin.canvas3d!.props.renderer,
+                    ...props.renderer
+                },
+                postprocessing: {
+                    ...this.plugin.canvas3d!.props.postprocessing,
+                    ...props.postprocessing
+                },
+            }
+        });
     }
 
     async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
@@ -115,7 +117,7 @@ class LightingDemo {
         const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
         const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
         const model = await this.plugin.builders.structure.createModel(trajectory);
-        const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'model', params: { } });
+        const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'model', params: {} });
 
         const polymer = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'polymer');
         if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });

+ 4 - 3
src/examples/proteopedia-wrapper/index.html

@@ -103,10 +103,11 @@
 
             PluginWrapper.init('app' /** or document.getElementById('app') */, {
                 customColorList: CustomColors
+            }).then(() => {
+                PluginWrapper.setBackground(0xffffff);
+                loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
+                PluginWrapper.toggleSpin();
             });
-            PluginWrapper.setBackground(0xffffff);
-            loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
-            PluginWrapper.toggleSpin();
 
             PluginWrapper.events.modelInfo.subscribe(function (info) {
                 console.log('Model Info', info);

+ 9 - 9
src/examples/proteopedia-wrapper/index.ts

@@ -10,7 +10,7 @@ import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/mod
 import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
 import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
-import { createPlugin } from '../../mol-plugin-ui';
+import { createPluginUI } from '../../mol-plugin-ui';
 import { PluginUIContext } from '../../mol-plugin-ui/context';
 import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
 import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
@@ -43,10 +43,10 @@ class MolStarProteopediaWrapper {
 
     plugin: PluginUIContext;
 
-    init(target: string | HTMLElement, options?: {
+    async init(target: string | HTMLElement, options?: {
         customColorList?: number[]
     }) {
-        this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
+        this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
             ...DefaultPluginUISpec(),
             animations: [
                 AnimateModelIndex
@@ -95,7 +95,7 @@ class MolStarProteopediaWrapper {
                 params: { id: assemblyId }
             } : {
                 name: 'model' as const,
-                params: { }
+                params: {}
             }
         };
 
@@ -113,7 +113,7 @@ class MolStarProteopediaWrapper {
         const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly);
         if (!structure) return;
 
-        const style = _style || { };
+        const style = _style || {};
 
         const update = this.state.build();
 
@@ -229,7 +229,7 @@ class MolStarProteopediaWrapper {
                     params: { id: asmId }
                 } : {
                     name: 'model' as const,
-                    params: { }
+                    params: {}
                 }
             };
             tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
@@ -269,7 +269,7 @@ class MolStarProteopediaWrapper {
 
     camera = {
         toggleSpin: () => this.toggleSpin(),
-        resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
+        resetPosition: () => PluginCommands.Camera.Reset(this.plugin, {})
     };
 
     private animateModelIndexTargetFps() {
@@ -336,11 +336,11 @@ class MolStarProteopediaWrapper {
         reset: () => {
             const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
             PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
-            PluginCommands.Camera.Reset(this.plugin, { });
+            PluginCommands.Camera.Reset(this.plugin, {});
         },
         focusFirst: async (compId: string, options?: { hideLabels: boolean, doNotLabelWaters: boolean }) => {
             if (!this.state.transforms.has(StateElements.Assembly)) return;
-            await PluginCommands.Camera.Reset(this.plugin, { });
+            await PluginCommands.Camera.Reset(this.plugin, {});
 
             const update = this.state.build();
 

+ 4 - 10
src/mol-plugin-ui/index.ts

@@ -11,18 +11,12 @@ import { Plugin } from './plugin';
 import { PluginUIContext } from './context';
 import { DefaultPluginUISpec, PluginUISpec } from './spec';
 
-export function createPlugin(target: HTMLElement, spec?: PluginUISpec): PluginUIContext {
-    const ctx = new PluginUIContext(spec || DefaultPluginUISpec());
-    ctx.init().then(() => {
-        ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
-    });
-    return ctx;
-}
-
-/** Returns the instance of the plugin after all behaviors have been initialized */
-export async function createPluginAsync(target: HTMLElement, spec?: PluginUISpec) {
+export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, options?: { onBeforeUIRender?: (ctx: PluginUIContext) => (Promise<void> | void) }) {
     const ctx = new PluginUIContext(spec || DefaultPluginUISpec());
     await ctx.init();
+    if (options?.onBeforeUIRender) {
+        await options.onBeforeUIRender(ctx);
+    }
     ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
     return ctx;
 }