Selaa lähdekoodia

add PluginContext.initialized promise (#935)

David Sehnal 1 vuosi sitten
vanhempi
commit
e548a3ed85
4 muutettua tiedostoa jossa 81 lisäystä ja 17 poistoa
  1. 2 0
      CHANGELOG.md
  2. 38 4
      src/mol-plugin-ui/plugin.tsx
  3. 5 0
      src/mol-plugin-ui/skin/base/base.scss
  4. 36 13
      src/mol-plugin/context.ts

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
+
 ## [v3.40.1] - 2023-09-30
 
 - Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.

+ 38 - 4
src/mol-plugin-ui/plugin.tsx

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -23,12 +23,46 @@ import { Asset } from '../mol-util/assets';
 import { BehaviorSubject } from 'rxjs';
 import { useBehavior } from './hooks/use-behavior';
 
-export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
-    render() {
-        return <PluginReactContext.Provider value={this.props.plugin}>
+export function Plugin({ plugin }: { plugin: PluginUIContext }) {
+    if (plugin.isInitialized) {
+        return <PluginReactContext.Provider value={plugin}>
             <Layout />
         </PluginReactContext.Provider>;
     }
+
+    return <PluginInitWrapper plugin={plugin} />;
+}
+
+type LoadState =
+    | { kind: 'initialized' }
+    | { kind: 'pending' }
+    | { kind: 'error', message: string }
+
+function PluginInitWrapper({ plugin }: { plugin: PluginUIContext }) {
+    const [state, setState] = React.useState<LoadState>({ kind: 'pending' });
+    React.useEffect(() => {
+        setState({ kind: 'pending' });
+        let mounted = true;
+
+        plugin.initialized.then(() => {
+            if (mounted) setState({ kind: 'initialized' });
+        }).catch(err => {
+            if (mounted) setState({ kind: 'error', message: `${err}` });
+        });
+
+        return () => { mounted = false; };
+    }, [plugin]);
+
+    if (state.kind === 'pending') return null;
+    if (state.kind === 'error') {
+        return <div className='msp-plugin'>
+            <div className='msp-plugin-init-error'>Initialization error: {state.message}</div>
+        </div>;
+    }
+
+    return <PluginReactContext.Provider value={plugin}>
+        <Layout />
+    </PluginReactContext.Provider>;
 }
 
 export class PluginContextContainer extends React.Component<{ plugin: PluginUIContext, children?: any }> {

+ 5 - 0
src/mol-plugin-ui/skin/base/base.scss

@@ -29,5 +29,10 @@
         color: $font-color;
     }
 
+    .msp-plugin-init-error {
+        white-space: pre;
+        margin: $control-spacing;
+    }
+
     background: $default-background;
 }

+ 36 - 13
src/mol-plugin/context.ts

@@ -62,6 +62,11 @@ import { ViewportScreenshotHelper } from './util/viewport-screenshot';
 import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
 import { setSaccharideCompIdMapType } from '../mol-model/structure/structure/carbohydrates/constants';
 
+export type PluginInitializedState =
+    | { kind: 'no' }
+    | { kind: 'yes' }
+    | { kind: 'error', error: any }
+
 export class PluginContext {
     runTask = <T>(task: Task<T>, params?: { useOverlay?: boolean }) => this.managers.task.run(task, params);
     resolveTask = <T>(object: Task<T> | T | undefined) => {
@@ -72,6 +77,8 @@ export class PluginContext {
 
     protected subs: Subscription[] = [];
     private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
+    private _isInitialized = false;
+    private initializedPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
 
     private disposed = false;
     private canvasContainer: HTMLDivElement | undefined = void 0;
@@ -115,6 +122,14 @@ export class PluginContext {
         this.initCanvas3dPromiseCallbacks = [res, rej];
     });
 
+    readonly initialized = new Promise<void>((res, rej) => {
+        this.initializedPromiseCallbacks = [res, rej];
+    });
+
+    get isInitialized() {
+        return this._isInitialized;
+    }
+
     readonly canvas3dContext: Canvas3DContext | undefined;
     readonly canvas3d: Canvas3D | undefined;
     readonly layout = new PluginLayout(this);
@@ -481,24 +496,32 @@ export class PluginContext {
     }
 
     async init() {
-        this.subs.push(this.events.log.subscribe(e => this.log.entries = this.log.entries.push(e)));
+        try {
+            this.subs.push(this.events.log.subscribe(e => this.log.entries = this.log.entries.push(e)));
 
-        this.initCustomFormats();
-        this.initBehaviorEvents();
-        this.initBuiltInBehavior();
+            this.initCustomFormats();
+            this.initBehaviorEvents();
+            this.initBuiltInBehavior();
 
-        (this.managers.interactivity as InteractivityManager) = new InteractivityManager(this);
-        (this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
-        (this.builders.structure as StructureBuilder) = new StructureBuilder(this);
+            (this.managers.interactivity as InteractivityManager) = new InteractivityManager(this);
+            (this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
+            (this.builders.structure as StructureBuilder) = new StructureBuilder(this);
 
-        this.initAnimations();
-        this.initDataActions();
+            this.initAnimations();
+            this.initDataActions();
 
-        await this.initBehaviors();
+            await this.initBehaviors();
 
-        this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
-        if (!isProductionMode) this.log.message(`Development mode enabled`);
-        if (isDebugMode) this.log.message(`Debug mode enabled`);
+            this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
+            if (!isProductionMode) this.log.message(`Development mode enabled`);
+            if (isDebugMode) this.log.message(`Debug mode enabled`);
+
+            this._isInitialized = true;
+            this.initializedPromiseCallbacks[0]();
+        } catch (err) {
+            this.initializedPromiseCallbacks[1](err);
+            throw err;
+        }
     }
 
     constructor(public spec: PluginSpec) {