From 81706ce05f7ac4eff73cd15da71b2bbfe3133abc 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 | 27 ++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 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..cf0996ea3 --- /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..3251817dc 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 ' + @@ -175,6 +191,11 @@ export class WidgetManager extends JupyterLabManager { 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 }> {