From a7b69effc7e78024b30b66a858096ae2f135d11e Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 9 Feb 2021 15:42:07 +0100 Subject: [PATCH 1/7] feat: save additional mime bundles in notebook --- packages/base/src/widget.ts | 11 +++++++++++ widgetsnbextension/src/manager.js | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index 981264fc31..788ad54640 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -88,6 +88,17 @@ export class WidgetModel extends Backbone.Model { }; } + generateMimeBundle() { + return Promise.resolve({ + 'application/vnd.jupyter.widget-view+json': { + model_id: this.model_id, + version_major: 2, + version_minor: 0 + }, + 'text/html': '

Hi!

' + }); + } + /** * Test to see if the model has been synced with the server. * diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index b49537c366..6ffa7e33c8 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -219,6 +219,24 @@ export class WidgetManager extends ManagerBase { */ _init_actions() { var notifier = Jupyter.notification_area.widget('widgets'); + this.notebook.events.on('before_save.Notebook', async () => { + var cells = Jupyter.notebook.get_cells(); + // notebook.js save_notebook doesn't want for this promise, we are simply lucky when this + // finishes before saving. + await Promise.all( + cells.map(async cell => { + var widget_output = cell.output_area.outputs.find(output => { + return output.data && output.data[MIME_TYPE]; + }); + if (widget_output) { + var model_id = widget_output.data[MIME_TYPE].model_id; + var model = await this.get_model(model_id); + var bundle = await model.generateMimeBundle(); + _.extend(widget_output.data, bundle); + } + }) + ); + }); this.saveWidgetsAction = { handler: function () { this.get_state({ From 883bbd3bdb2e415878292b2b44f19e1fc936a46f Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 9 Feb 2021 18:51:37 +0100 Subject: [PATCH 2/7] use async, and do not return widget mime type --- packages/base/src/widget.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index 788ad54640..8037c6485c 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -88,15 +88,10 @@ export class WidgetModel extends Backbone.Model { }; } - generateMimeBundle() { - return Promise.resolve({ - 'application/vnd.jupyter.widget-view+json': { - model_id: this.model_id, - version_major: 2, - version_minor: 0 - }, + async generateMimeBundle() { + return { 'text/html': '

Hi!

' - }); + }; } /** From 4f3f57b3568973b0b8857f54c73a4177f4577b5d Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 9 Feb 2021 18:51:53 +0100 Subject: [PATCH 3/7] example for HTML and Image --- packages/controls/src/widget_image.ts | 9 +++++++++ packages/controls/src/widget_string.ts | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/packages/controls/src/widget_image.ts b/packages/controls/src/widget_image.ts index a1169840ab..9c008edcbc 100644 --- a/packages/controls/src/widget_image.ts +++ b/packages/controls/src/widget_image.ts @@ -26,6 +26,15 @@ export class ImageModel extends CoreDOMWidgetModel { }, }, }; + + async generateMimeBundle() { + const view: ImageView = await this.widget_manager.create_view( + this + ); + return Promise.resolve({ + 'text/html': view.el.outerHTML + }); + } } export class ImageView extends DOMWidgetView { diff --git a/packages/controls/src/widget_string.ts b/packages/controls/src/widget_string.ts index 43e7e50e3d..8ca422bcd6 100644 --- a/packages/controls/src/widget_string.ts +++ b/packages/controls/src/widget_string.ts @@ -184,6 +184,11 @@ export class HTMLModel extends StringModel { _model_name: 'HTMLModel', }; } + generateMimeBundle() { + return Promise.resolve({ + 'text/html': this.get('value') + }); + } } export class HTMLView extends StringView { From e1c5ab1a5455f06764352c02f407f9d145f36e7f Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 9 Feb 2021 18:52:15 +0100 Subject: [PATCH 4/7] fix: skip non-existing models --- widgetsnbextension/src/manager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index 6ffa7e33c8..21942eeaa4 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -231,8 +231,10 @@ export class WidgetManager extends ManagerBase { if (widget_output) { var model_id = widget_output.data[MIME_TYPE].model_id; var model = await this.get_model(model_id); - var bundle = await model.generateMimeBundle(); - _.extend(widget_output.data, bundle); + if (model) { + var bundle = await model.generateMimeBundle(); + _.extend(widget_output.data, bundle); + } } }) ); From ffd91d24585e34982549a9a07c72775d779fb1df Mon Sep 17 00:00:00 2001 From: martinRenou Date: Mon, 13 Sep 2021 17:32:19 +0200 Subject: [PATCH 5/7] Do not fail if there is no output_area --- widgetsnbextension/src/manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index 21942eeaa4..a2519d28c3 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -225,6 +225,10 @@ export class WidgetManager extends ManagerBase { // finishes before saving. await Promise.all( cells.map(async cell => { + if (!cell.output_area) { + return; + } + var widget_output = cell.output_area.outputs.find(output => { return output.data && output.data[MIME_TYPE]; }); From 84a15745929e87143aabc61713c7749f04edaf79 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Tue, 14 Sep 2021 10:22:06 +0200 Subject: [PATCH 6/7] Allow for overwriting the mimebundle This will be useful for widgets like ipympl or ipycanvas, where the model does not contain enough information to recover the widget (the kernel only contains the information about the state of the plot) so we need to store a static version of the plot (static image) for convenience, and we need to dump the widget model. --- packages/base/src/widget.ts | 15 ++++++++++++--- widgetsnbextension/src/manager.js | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index 8037c6485c..d7e0dd11da 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -88,10 +88,19 @@ export class WidgetModel extends Backbone.Model { }; } + /** + * Generate extra mime bundle for this widget + */ async generateMimeBundle() { - return { - 'text/html': '

Hi!

' - }; + return {}; + } + + /** + * Whether the `generateMimeBundle` output should + * overwrite the mimeBundle or extend it + */ + shouldOverwriteMimeBundle() : boolean { + return false; } /** diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index a2519d28c3..ca8795de1f 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -230,13 +230,23 @@ export class WidgetManager extends ManagerBase { } var widget_output = cell.output_area.outputs.find(output => { - return output.data && output.data[MIME_TYPE]; + return (output.data && output.data[MIME_TYPE]) || (output.metadata && output.metadata[MIME_TYPE]); }); if (widget_output) { - var model_id = widget_output.data[MIME_TYPE].model_id; + var model_id = widget_output.data[MIME_TYPE] ? + widget_output.data[MIME_TYPE].model_id : widget_output.metadata[MIME_TYPE].model_id; var model = await this.get_model(model_id); if (model) { var bundle = await model.generateMimeBundle(); + + if (widget_output.data[MIME_TYPE] && model.shouldOverwriteMimeBundle()) { + widget_output.metadata[MIME_TYPE] = widget_output.data[MIME_TYPE]; + widget_output.metadata['text/plain'] = widget_output.data['text/plain']; + + delete widget_output.data[MIME_TYPE]; + delete widget_output.data['text/plain']; + } + _.extend(widget_output.data, bundle); } } From f4c89fa5f3c55449c74c2fa49ee70ce9055859e4 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Tue, 14 Sep 2021 10:26:50 +0200 Subject: [PATCH 7/7] Lint --- packages/base/src/widget.ts | 2 +- packages/controls/src/widget_image.ts | 2 +- packages/controls/src/widget_string.ts | 2 +- widgetsnbextension/src/manager.js | 25 +++++++++++++++++-------- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index d7e0dd11da..a1f847f3b2 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -99,7 +99,7 @@ export class WidgetModel extends Backbone.Model { * Whether the `generateMimeBundle` output should * overwrite the mimeBundle or extend it */ - shouldOverwriteMimeBundle() : boolean { + shouldOverwriteMimeBundle(): boolean { return false; } diff --git a/packages/controls/src/widget_image.ts b/packages/controls/src/widget_image.ts index 9c008edcbc..a06b377422 100644 --- a/packages/controls/src/widget_image.ts +++ b/packages/controls/src/widget_image.ts @@ -32,7 +32,7 @@ export class ImageModel extends CoreDOMWidgetModel { this ); return Promise.resolve({ - 'text/html': view.el.outerHTML + 'text/html': view.el.outerHTML, }); } } diff --git a/packages/controls/src/widget_string.ts b/packages/controls/src/widget_string.ts index 8ca422bcd6..d593b81830 100644 --- a/packages/controls/src/widget_string.ts +++ b/packages/controls/src/widget_string.ts @@ -186,7 +186,7 @@ export class HTMLModel extends StringModel { } generateMimeBundle() { return Promise.resolve({ - 'text/html': this.get('value') + 'text/html': this.get('value'), }); } } diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index ca8795de1f..6d641b61c3 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -224,24 +224,33 @@ export class WidgetManager extends ManagerBase { // notebook.js save_notebook doesn't want for this promise, we are simply lucky when this // finishes before saving. await Promise.all( - cells.map(async cell => { + cells.map(async (cell) => { if (!cell.output_area) { return; } - var widget_output = cell.output_area.outputs.find(output => { - return (output.data && output.data[MIME_TYPE]) || (output.metadata && output.metadata[MIME_TYPE]); + var widget_output = cell.output_area.outputs.find((output) => { + return ( + (output.data && output.data[MIME_TYPE]) || + (output.metadata && output.metadata[MIME_TYPE]) + ); }); if (widget_output) { - var model_id = widget_output.data[MIME_TYPE] ? - widget_output.data[MIME_TYPE].model_id : widget_output.metadata[MIME_TYPE].model_id; + var model_id = widget_output.data[MIME_TYPE] + ? widget_output.data[MIME_TYPE].model_id + : widget_output.metadata[MIME_TYPE].model_id; var model = await this.get_model(model_id); if (model) { var bundle = await model.generateMimeBundle(); - if (widget_output.data[MIME_TYPE] && model.shouldOverwriteMimeBundle()) { - widget_output.metadata[MIME_TYPE] = widget_output.data[MIME_TYPE]; - widget_output.metadata['text/plain'] = widget_output.data['text/plain']; + if ( + widget_output.data[MIME_TYPE] && + model.shouldOverwriteMimeBundle() + ) { + widget_output.metadata[MIME_TYPE] = + widget_output.data[MIME_TYPE]; + widget_output.metadata['text/plain'] = + widget_output.data['text/plain']; delete widget_output.data[MIME_TYPE]; delete widget_output.data['text/plain'];