Bladeren bron

mol-plugin: fix plugin crash when WebGL is not available

David Sehnal 5 jaren geleden
bovenliggende
commit
e8663b5bfc

+ 3 - 1
src/apps/basic-wrapper/index.ts

@@ -108,11 +108,13 @@ class BasicWrapper {
     }
 
     setBackground(color: number) {
-        const renderer = this.plugin.canvas3d.props.renderer;
+        const renderer = this.plugin.canvas3d!.props.renderer;
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer,  backgroundColor: Color(color) } } });
     }
 
     toggleSpin() {
+        if (!this.plugin.canvas3d) return;
+
         const trackball = this.plugin.canvas3d.props.trackball;
         const spinning = trackball.spin;
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });

+ 3 - 3
src/apps/demos/lighting/index.ts

@@ -97,15 +97,15 @@ class LightingDemo {
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
             ...props,
             multiSample: {
-                ...this.plugin.canvas3d.props.multiSample,
+                ...this.plugin.canvas3d!.props.multiSample,
                 ...props.multiSample
             },
             renderer: {
-                ...this.plugin.canvas3d.props.renderer,
+                ...this.plugin.canvas3d!.props.renderer,
                 ...props.renderer
             },
             postprocessing: {
-                ...this.plugin.canvas3d.props.postprocessing,
+                ...this.plugin.canvas3d!.props.postprocessing,
                 ...props.postprocessing
             },
         }});

+ 3 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -230,11 +230,13 @@ class MolStarProteopediaWrapper {
     }
 
     setBackground(color: number) {
+        if (!this.plugin.canvas3d) return;
         const renderer = this.plugin.canvas3d.props.renderer;
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer,  backgroundColor: Color(color) } } });
     }
 
     toggleSpin() {
+        if (!this.plugin.canvas3d) return;
         const trackball = this.plugin.canvas3d.props.trackball;
         const spinning = trackball.spin;
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
@@ -383,7 +385,7 @@ class MolStarProteopediaWrapper {
             // const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
             // Vec3.normalize(position, position);
             // Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
-            const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
+            const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
             PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
         }
     }

+ 1 - 1
src/mol-plugin/behavior/dynamic/labels.ts

@@ -193,7 +193,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({
             }
             if (updated) {
                 Object.assign(this.params, p)
-                this.ctx.canvas3d.add(this.repr)
+                this.ctx.canvas3d?.add(this.repr)
             }
             return updated;
         }

+ 1 - 1
src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts

@@ -248,7 +248,7 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
 
         const provider = BuiltInVolumeRepresentations.isosurface;
         const props = params.type.params || {}
-        const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
+        const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
         repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: channel.data }, params))
         await repr.createOrUpdate(props, channel.data).runInContext(ctx);
         return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });

+ 3 - 3
src/mol-plugin/behavior/static/camera.ts

@@ -16,13 +16,13 @@ export function registerDefault(ctx: PluginContext) {
 
 export function Reset(ctx: PluginContext) {
     PluginCommands.Camera.Reset.subscribe(ctx, () => {
-        ctx.canvas3d.resetCamera();
+        ctx.canvas3d?.resetCamera();
     })
 }
 
 export function SetSnapshot(ctx: PluginContext) {
     PluginCommands.Camera.SetSnapshot.subscribe(ctx, ({ snapshot, durationMs }) => {
-        ctx.canvas3d.camera.transition.apply(snapshot, durationMs);
+        ctx.canvas3d?.camera.transition.apply(snapshot, durationMs);
     })
 }
 
@@ -36,7 +36,7 @@ export function Snapshots(ctx: PluginContext) {
     });
 
     PluginCommands.Camera.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
-        const entry = CameraSnapshotManager.Entry(ctx.canvas3d.camera.getSnapshot(), name, description);
+        const entry = CameraSnapshotManager.Entry(ctx.canvas3d!.camera.getSnapshot(), name, description);
         ctx.state.cameraSnapshots.add(entry);
     });
 

+ 1 - 1
src/mol-plugin/behavior/static/misc.ts

@@ -15,7 +15,7 @@ export function registerDefault(ctx: PluginContext) {
 
 export function Canvas3DSetSettings(ctx: PluginContext) {
     PluginCommands.Canvas3D.SetSettings.subscribe(ctx, e => {
-        ctx.canvas3d.setProps(e.settings);
+        ctx.canvas3d?.setProps(e.settings);
         ctx.events.canvas3d.settingsUpdated.next();
     })
 }

+ 15 - 15
src/mol-plugin/behavior/static/representation.ts

@@ -19,8 +19,8 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
     let reprCount = 0;
 
     ctx.events.canvas3d.initialized.subscribe(() => {
-        ctx.canvas3d.reprCount.subscribe(v => {
-            if (reprCount === 0) ctx.canvas3d.resetCamera();
+        ctx.canvas3d?.reprCount.subscribe(v => {
+            if (reprCount === 0) ctx.canvas3d?.resetCamera();
             reprCount = v;
         });
     })
@@ -30,12 +30,12 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
         if (!SO.isRepresentation3D(e.obj)) return;
         updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr);
         e.obj.data.repr.setState({ syncManually: true });
-        ctx.canvas3d.add(e.obj.data.repr);
+        ctx.canvas3d?.add(e.obj.data.repr);
     });
     events.object.updated.subscribe(e => {
         if (e.oldObj && SO.isRepresentation3D(e.oldObj)) {
-            ctx.canvas3d.remove(e.oldObj.data.repr);
-            ctx.canvas3d.requestDraw(true);
+            ctx.canvas3d?.remove(e.oldObj.data.repr);
+            ctx.canvas3d?.requestDraw(true);
             e.oldObj.data.repr.destroy();
         }
 
@@ -47,12 +47,12 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
         if (e.action === 'recreate') {
             e.obj.data.repr.setState({ syncManually: true });
         }
-        ctx.canvas3d.add(e.obj.data.repr);
+        ctx.canvas3d?.add(e.obj.data.repr);
     });
     events.object.removed.subscribe(e => {
         if (!SO.isRepresentation3D(e.obj)) return;
-        ctx.canvas3d.remove(e.obj.data.repr);
-        ctx.canvas3d.requestDraw(true);
+        ctx.canvas3d?.remove(e.obj.data.repr);
+        ctx.canvas3d?.requestDraw(true);
         e.obj.data.repr.destroy();
     });
 }
@@ -65,22 +65,22 @@ export function SyncStructureRepresentation3DState(ctx: PluginContext) {
         if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
         const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
         data.source.data.repr.setState(data.state);
-        ctx.canvas3d.update(data.source.data.repr);
-        ctx.canvas3d.requestDraw(true);
+        ctx.canvas3d?.update(data.source.data.repr);
+        ctx.canvas3d?.requestDraw(true);
     });
     events.object.updated.subscribe(e => {
         if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
         const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
         data.source.data.repr.setState(data.state);
-        ctx.canvas3d.update(data.source.data.repr);
-        ctx.canvas3d.requestDraw(true);
+        ctx.canvas3d?.update(data.source.data.repr);
+        ctx.canvas3d?.requestDraw(true);
     });
     events.object.removed.subscribe(e => {
         if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
         const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
         data.source.data.repr.setState(data.initialState);
-        ctx.canvas3d.update(data.source.data.repr);
-        ctx.canvas3d.requestDraw(true);
+        ctx.canvas3d?.update(data.source.data.repr);
+        ctx.canvas3d?.requestDraw(true);
     });
 }
 
@@ -90,7 +90,7 @@ export function UpdateRepresentationVisibility(ctx: PluginContext) {
         const cell = e.state.cells.get(e.ref)!;
         if (!SO.isRepresentation3D(cell.obj)) return;
         updateVisibility(cell, cell.obj.data.repr);
-        ctx.canvas3d.requestDraw(true);
+        ctx.canvas3d?.requestDraw(true);
     })
 }
 

+ 5 - 4
src/mol-plugin/context.ts

@@ -102,7 +102,7 @@ export class PluginContext {
         }
     } as const
 
-    readonly canvas3d: Canvas3D;
+    readonly canvas3d: Canvas3D | undefined;
     readonly layout = new PluginLayout(this);
     readonly toasts = new PluginToastManager(this);
     readonly interactivity: Interactivity;
@@ -147,12 +147,13 @@ export class PluginContext {
         try {
             this.layout.setRoot(container);
             if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
+
             (this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, t => this.runTask(t));
             this.events.canvas3d.initialized.next()
             this.events.canvas3d.initialized.isStopped = true // TODO is this a good way?
-            const renderer = this.canvas3d.props.renderer;
+            const renderer = this.canvas3d!.props.renderer;
             PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
-            this.canvas3d.animate();
+            this.canvas3d!.animate();
             (this.helpers.viewportScreenshot as ViewportScreenshotWrapper) = new ViewportScreenshotWrapper(this);
             return true;
         } catch (e) {
@@ -188,7 +189,7 @@ export class PluginContext {
     dispose() {
         if (this.disposed) return;
         this.commands.dispose();
-        this.canvas3d.dispose();
+        this.canvas3d?.dispose();
         this.ev.dispose();
         this.state.dispose();
         this.tasks.dispose();

+ 1 - 0
src/mol-plugin/skin/base/components/misc.scss

@@ -34,6 +34,7 @@
     top: 0;
     display: table;
     text-align: center;
+    background: $default-background;
 
     > div {
         b {

+ 2 - 2
src/mol-plugin/state.ts

@@ -52,12 +52,12 @@ class PluginState {
             animation: p.animation ? this.animation.getSnapshot() : void 0,
             startAnimation: p.startAnimation ? !!p.startAnimation : void 0,
             camera: p.camera ? {
-                current: this.plugin.canvas3d.camera.getSnapshot(),
+                current: this.plugin.canvas3d!.camera.getSnapshot(),
                 transitionStyle: p.cameraTranstion.name,
                 transitionDurationInMs: (params && params.cameraTranstion && params.cameraTranstion.name === 'animate') ? params.cameraTranstion.params.durationInMs : undefined
             } : void 0,
             cameraSnapshots: p.cameraSnapshots ? this.cameraSnapshots.getStateSnapshot() : void 0,
-            canvas3d: p.canvas3d ? { props: this.plugin.canvas3d.props } : void 0,
+            canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
             interactivity: p.interactivity ? { props: this.plugin.interactivity.props } : void 0,
             durationInMs: params && params.durationInMs
         };

+ 2 - 2
src/mol-plugin/state/transforms/representation.ts

@@ -204,7 +204,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
         return Task.create('Structure Representation', async ctx => {
             const provider = plugin.structureRepresentation.registry.get(params.type.name)
             const props = params.type.params || {}
-            const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.structureRepresentation.themeCtx }, provider.getParams)
+            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, provider.getParams)
             repr.setTheme(createTheme(plugin.structureRepresentation.themeCtx, { structure: a.data }, params))
             // TODO set initial state, repr.setState({})
             await repr.createOrUpdate(props, a.data).runInContext(ctx);
@@ -620,7 +620,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
         return Task.create('Volume Representation', async ctx => {
             const provider = plugin.volumeRepresentation.registry.get(params.type.name)
             const props = params.type.params || {}
-            const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
+            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
             repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: a.data }, params))
             // TODO set initial state, repr.setState({})
             await repr.createOrUpdate(props, a.data).runInContext(ctx);

+ 1 - 1
src/mol-plugin/ui/controls/parameters.tsx

@@ -130,7 +130,7 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
                 <span title={desc}>
                     {label}
                     {hasHelp &&
-                        <button className='msp-help msp-btn-link msp-btn-icon msp-control-group-expander' onClick={this.toggleExpanded} 
+                        <button className='msp-help msp-btn-link msp-btn-icon msp-control-group-expander' onClick={this.toggleExpanded}
                             title={desc || `${this.state.isExpanded ? 'Hide' : 'Show'} help`}
                             style={{ background: 'transparent', textAlign: 'left', padding: '0' }}>
                             <span className={`msp-icon msp-icon-help-circle-${this.state.isExpanded ? 'collapse' : 'expand'}`} />

+ 5 - 3
src/mol-plugin/ui/image.tsx

@@ -42,7 +42,7 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
     }
 
     private getSize() {
-        return this.state.size === 'canvas' ? {
+        return this.state.size === 'canvas' && this.plugin.canvas3d ? {
             width: this.plugin.canvas3d.webgl.gl.drawingBufferWidth,
             height: this.plugin.canvas3d.webgl.gl.drawingBufferHeight
         } : {
@@ -66,7 +66,7 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
             h = Math.round(height * (w / width))
         }
         setCanvasSize(this.canvas, w, h)
-        const { pixelRatio } = this.plugin.canvas3d.webgl
+        const pixelRatio = this.plugin.canvas3d?.webgl.pixelRatio || 1
         const pw = Math.round(w * pixelRatio)
         const ph = Math.round(h * pixelRatio)
         const imageData = this.imagePass.getImageData(pw, ph)
@@ -100,12 +100,14 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
     }
 
     componentDidMount() {
+        if (!this.plugin.canvas3d) return;
+
         this.handlePreview()
 
         this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => {
             this.imagePass.setProps({
                 multiSample: { mode: 'on', sampleLevel: 2 },
-                postprocessing: this.plugin.canvas3d.props.postprocessing
+                postprocessing: this.plugin.canvas3d?.props.postprocessing
             })
             this.handlePreview()
         })

+ 2 - 2
src/mol-plugin/ui/structure/selection.tsx

@@ -63,7 +63,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
         const principalAxes = this.plugin.helpers.structureSelectionManager.getPrincipalAxes();
         const { origin, dirA, dirC } = principalAxes.boxAxes
         const radius = Math.max(Vec3.magnitude(dirA) + extraRadius, minRadius);
-        this.plugin.canvas3d.camera.focus(origin, radius, durationMs, dirA, dirC);
+        this.plugin.canvas3d?.camera.focus(origin, radius, durationMs, dirA, dirC);
     }
 
     focusLoci(loci: StructureElement.Loci) {
@@ -72,7 +72,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
             if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
             const sphere = Sphere3D.fromAxes3D(Sphere3D(), StructureElement.Loci.getPrincipalAxes(loci).boxAxes)
             const radius = Math.max(sphere.radius + extraRadius, minRadius);
-            this.plugin.canvas3d.camera.focus(sphere.center, radius, durationMs);
+            this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
         }
     }
 

+ 7 - 6
src/mol-plugin/ui/viewport.tsx

@@ -94,9 +94,9 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
                 <ControlGroup header='Interactivity' initialExpanded={true}>
                     <ParameterControls params={Interactivity.Params} values={this.plugin.interactivity.props} onChange={this.setInteractivityProps} />
                 </ControlGroup>
-                <ControlGroup header='Viewport' initialExpanded={true}>
+                {this.plugin.canvas3d && <ControlGroup header='Viewport' initialExpanded={true}>
                     <ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
-                </ControlGroup>
+                </ControlGroup>}
             </div>}
         </div>
     }
@@ -127,7 +127,7 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
     };
 
     private handleLogo = () => {
-        this.setState({ showLogo: this.plugin.canvas3d.reprCount.value === 0 })
+        this.setState({ showLogo: !this.plugin.canvas3d?.reprCount.value })
     }
 
     private handleResize = () => {
@@ -135,18 +135,19 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
         const canvas = this.canvas.current;
         if (container && canvas) {
             resizeCanvas(canvas, container);
-            this.plugin.canvas3d.handleResize();
+            this.plugin.canvas3d!.handleResize();
         }
     }
 
     componentDidMount() {
         if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
             this.setState({ noWebGl: true });
+            return;
         }
         this.handleLogo();
         this.handleResize();
 
-        const canvas3d = this.plugin.canvas3d;
+        const canvas3d = this.plugin.canvas3d!;
         this.subscribe(canvas3d.reprCount, this.handleLogo);
         this.subscribe(canvas3d.input.resize, this.handleResize);
 
@@ -163,7 +164,7 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
     }
 
     renderMissing() {
-        return <div>
+        return <div className='msp-no-webgl'>
             <div>
                 <p><b>WebGL does not seem to be available.</b></p>
                 <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>

+ 4 - 4
src/mol-plugin/util/viewport-screenshot.ts

@@ -16,8 +16,8 @@ import { download } from '../../mol-util/download';
 export class ViewportScreenshotWrapper {
     private getCanvasSize() {
         return {
-            width: this.plugin.canvas3d.webgl.gl.drawingBufferWidth,
-            height: this.plugin.canvas3d.webgl.gl.drawingBufferHeight
+            width: this.plugin.canvas3d?.webgl.gl.drawingBufferWidth || 0,
+            height: this.plugin.canvas3d?.webgl.gl.drawingBufferHeight || 0
         };
     }
 
@@ -28,10 +28,10 @@ export class ViewportScreenshotWrapper {
     get imagePass() {
         if (this._imagePass) return this._imagePass;
 
-        this._imagePass = this.plugin.canvas3d.getImagePass()
+        this._imagePass = this.plugin.canvas3d!.getImagePass()
         this._imagePass.setProps({
             multiSample: { mode: 'on', sampleLevel: 2 },
-            postprocessing: this.plugin.canvas3d.props.postprocessing
+            postprocessing: this.plugin.canvas3d!.props.postprocessing
         });
         return this._imagePass;
     }