From 95a63e05eeb862b9f45631b41811d7065b543a05 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Thu, 4 Feb 2021 17:33:20 +0100 Subject: [PATCH] feat: when a widget fails, show a fallback error widget --- packages/voila/src/errorwidget.ts | 30 ++++++++++++++++++++++++++++++ packages/voila/src/manager.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 packages/voila/src/errorwidget.ts diff --git a/packages/voila/src/errorwidget.ts b/packages/voila/src/errorwidget.ts new file mode 100644 index 000000000..b220bff4c --- /dev/null +++ b/packages/voila/src/errorwidget.ts @@ -0,0 +1,30 @@ +import * as widgets from "@jupyter-widgets/base"; + +// Create a Widget Model that captures an error object +export function createErrorWidget(error : string) : any { + class ErrorWidget extends widgets.DOMWidgetModel { + constructor(attributes?: any, options?: any) { + attributes = { + ...attributes, + _view_name: 'ErrorWidgetView', + _view_module: 'voila-errorwidget', + _model_module_version: '1.0.0', + _view_module_version: '1.0.0', + failed_module: attributes._view_module, + failed_model_name: attributes._model_name, + error: error + }; + super(attributes, options); + } + } + return ErrorWidget as any; +} + +export class ErrorWidgetView extends widgets.DOMWidgetView { + render() { + const module = this.model.get('failed_module'); + const name = this.model.get('failed_model_name'); + const error = String(this.model.get('error').stack); + this.el.innerHTML = `Failed to load widget '${name}' from module '${module}', error:
${error}
`; + } +} diff --git a/packages/voila/src/manager.ts b/packages/voila/src/manager.ts index 72898fc63..19e9c413f 100644 --- a/packages/voila/src/manager.ts +++ b/packages/voila/src/manager.ts @@ -31,10 +31,11 @@ import * as PhosphorCommands from '@phosphor/commands'; import * as PhosphorDomutils from '@phosphor/domutils'; import { MessageLoop } from '@phosphor/messaging'; +import { Widget } from '@phosphor/widgets'; import { requireLoader } from './loader'; import { batchRateMap } from './utils'; -import { Widget } from '@phosphor/widgets'; +import * as errorwidget from "./errorwidget"; if (typeof window !== 'undefined' && typeof window.define !== 'undefined') { window.define('@jupyter-widgets/base', base); @@ -53,6 +54,8 @@ if (typeof window !== 'undefined' && typeof window.define !== 'undefined') { window.define('@phosphor/algorithm', PhosphorAlgorithm); window.define('@phosphor/commands', PhosphorCommands); window.define('@phosphor/domutils', PhosphorDomutils); + + window.define("voila-errorwidget", errorwidget); } const WIDGET_MIMETYPE = 'application/vnd.jupyter.widget-view+json'; @@ -129,11 +132,24 @@ export class WidgetManager extends JupyterLabManager { className: string, moduleName: string, moduleVersion: string + ): Promise { + try { + return await this._loadClass(className, moduleName, moduleVersion) + } catch(error) { + return errorwidget.createErrorWidget(error); + }; + } + + async _loadClass( + className: string, + moduleName: string, + moduleVersion: string ): Promise { if ( moduleName === '@jupyter-widgets/base' || moduleName === '@jupyter-widgets/controls' || - moduleName === '@jupyter-widgets/output' + moduleName === '@jupyter-widgets/output' || + moduleName === 'voila-errorwidget' ) { return super.loadClass(className, moduleName, moduleVersion); } else { @@ -142,7 +158,7 @@ export class WidgetManager extends JupyterLabManager { if (module[className]) { return module[className]; } else { - return Promise.reject( + throw Error( 'Class ' + className + ' not found in module ' + @@ -174,7 +190,12 @@ export class WidgetManager extends JupyterLabManager { name: '@jupyter-widgets/output', version: output.OUTPUT_WIDGET_VERSION, exports: output as any - }); + }), + this.register({ + name: 'voila-errorwidget', + version: '1.0.0', + exports: errorwidget as any + });; } async _build_models(): Promise<{ [key: string]: base.WidgetModel }> {