diff --git a/04-materials/02-anatomy-of-extensions.md b/04-materials/02-anatomy-of-extensions.md index 99f65c76..ade2c1d5 100644 --- a/04-materials/02-anatomy-of-extensions.md +++ b/04-materials/02-anatomy-of-extensions.md @@ -1272,30 +1272,26 @@ First, we need to define a tracker object: ```{code} typescript :linenos: -:emphasize-lines: 1-5 +:emphasize-lines: 1-4 :filename: src/index.ts // Track widget state - const tracker_namespace = 'jupytercon2025-extension-workshop'; const tracker = new WidgetTracker({ - namespace: tracker_namespace + namespace: 'jupytercon2025-extension-workshop' }); //Register a new command: const command_id = 'image-caption:open'; app.commands.addCommand(command_id, { - execute: () => { ``` Then, add our widget to the tracker: ```{code} typescript :linenos: -:emphasize-lines: 6-8 +:emphasize-lines: 4-6 :filename: src/index.ts - app.commands.addCommand(command_id, { - execute: () => { // When the command is executed, create a new instance of our widget const widget = new ImageCaptionMainAreaWidget(); @@ -1305,17 +1301,18 @@ Then, add our widget to the tracker: // Then add it to the main area: app.shell.add(widget, 'main'); - }, - icon: imageIcon, - label: 'View a random image & caption' - }); ``` -And finally, restore any previous state when our plugin is activated: +Now we can restore previously-open widgets when our plugin is activated. +This requires a unique identifier for our widget, `id`, which is currently undefined +(we'll get it in the next step). +The restorer will execute the command to restore the widget, and pass in the original +widget's `id` to the command as an optional argument. + ```{code} typescript :linenos: -:emphasize-lines: 4-10 +:emphasize-lines: 4-11 :filename: src/index.ts palette.addItem({ command: command_id, category: 'Tutorial' }); @@ -1325,11 +1322,49 @@ And finally, restore any previous state when our plugin is activated: if (restorer) { restorer.restore(tracker, { command: command_id, - name: () => tracker_namespace + args: widget => ({ id: widget.id }), + name: widget => widget.id }); } ``` +Now we need to update our command to accept the new `id` argument. +It will only receive this when restoring, so if it's populated we can use it to create a +new widget with an identical `id`. +Remember, our widgets don't have an `id` at all at this stage, so we also need to handle +the case where `id` isn't populated and generate a randomized one. + +```{code} typescript +:linenos: +:emphasize-lines: 2,6-12 +:filename: src/index.ts + + app.commands.addCommand(command_id, { + execute: (args?: { id?: string }) => { + // When the command is executed, create a new instance of our widget + const widget = new ImageCaptionMainAreaWidget(); + + // Use provided ID or generate a new one + // During restoration, the args will contain the saved widget ID + if (args && args.id) { + widget.id = args.id; + } else { + widget.id = `image-caption-${crypto.randomUUID()}`; + } + + if (!tracker.has(widget)) { + tracker.add(widget); + } + + // Then add it to the main area: + app.shell.add(widget, 'main'); + return widget; + }, + icon: imageIcon, + label: 'View a random image & caption' + }); +``` + ### ๐Ÿงช Test @@ -1345,6 +1380,8 @@ You may see a different image; this is because we're loading a new image every t widget is initialized. ::: +Don't forget to test with multiple widgets open! + ### ๐Ÿง  What do we know now? @@ -1355,6 +1392,14 @@ We know that we can use the `WidgetTracker` to remember the state of our widget `ILayoutRestorer` plugin to restore that state. +#### Think about... + +When we restore our widget, the exact same image isn't displayed. + +What if we want to restore the widget with the exact same image and caption it displayed +before we refreshed the page? + + ## ๐ŸŽ‰ Great job! The extension we built together today is just a small example of what can be done with