diff --git a/README.md b/README.md index dbff852..e1a079d 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,74 @@ For further information please consult the Jupyter documentation: - Creating an extension: https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html - Adding commands to the command registry: https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#commands +### Registering Callbacks + +If you would like to register a callback to be executed when a particular event occurs in the JupyterLab, you can make use of the `createProxy` function from the `jupyter-iframe-commands-host` package. This function allows you to create a proxy to be passed to the IFrame as a command `args`, so it can be invoked in a JupyterLab command. + +On the host, define the callback function: + +```js +import { createBridge, createProxy } from 'jupyter-iframe-commands-host'; + +const commandBridge = createBridge({ iframeId: 'jupyterlab' }); + +const kernelStatus = async ({ displayName, isBusy }) => { + console.log('Received kernel status update from the iframe'); + console.log('Display Name:', displayName); + console.log('Is Busy:', isBusy); +}; + +await commandBridge.execute('kernel-status', createProxy({ kernelStatus })); +``` + +In your custom JupyterLab extension, you can define a command: + +```ts +const demoPlugin: JupyterFrontEndPlugin = { + id: 'jupyter-iframe-commands:demo-callback', + autoStart: true, + description: 'Using host callbacks', + requires: [ILabStatus, INotebookTracker], + activate: async ( + app: JupyterFrontEnd, + labStatus: ILabStatus, + tracker: INotebookTracker + ) => { + const { commands } = app; + + commands.addCommand('kernel-status', { + label: 'Kernel Status', + execute: args => { + const kernelStatus = args['kernelStatus'] as unknown as ( + args?: any + ) => void; + + labStatus.busySignal.connect(async () => { + const kernelSpec = + await tracker.currentWidget?.sessionContext?.session?.kernel?.spec; + const displayName = kernelSpec + ? kernelSpec.display_name + : 'Loading...'; + kernelStatus({ displayName, isBusy: labStatus.isBusy }); + }); + } + }); + } +}; +``` + +> [!WARNING] +> The returned value of `createProxy` must be passed as the `args` of the command so the underlying proxy can properly be transferred by Comlink. `createProxy` can proxy an entire object, so you can pass multiple callbacks and other data as well at once. For example: +> +> ```js +> const proxy = createProxy({ +> kernelStatus, +> anotherCallback, +> someData: { foo: 'bar' } +> }); +> await commandBridge.execute('kernel-status', proxy); +> ``` + ## Demos ### Local Demo diff --git a/packages/host/src/index.ts b/packages/host/src/index.ts index 9265c29..008a1bd 100644 --- a/packages/host/src/index.ts +++ b/packages/host/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) TileDB, Inc. // Distributed under the terms of the Modified BSD License. -import { windowEndpoint, wrap } from 'comlink'; +import { windowEndpoint, wrap, proxy, ProxyOrClone } from 'comlink'; + import { ICommandBridgeRemote } from 'jupyter-iframe-commands'; /** * A bridge to expose actions on JupyterLab commands. @@ -22,3 +23,13 @@ export function createBridge({ iframeId }: { iframeId: string }) { return wrap(windowEndpoint(iframe.contentWindow)); } + +/** + * Creates a proxy for the given object. + * + * @param args - The object to create a proxy for. + * @returns A proxy for the given object. + */ +export function createProxy(args: any): ProxyOrClone { + return proxy(args); +} diff --git a/packages/host/tsconfig.json b/packages/host/tsconfig.json index 92d6e92..29b7565 100644 --- a/packages/host/tsconfig.json +++ b/packages/host/tsconfig.json @@ -4,5 +4,10 @@ "outDir": "lib", "rootDir": "src" }, - "include": ["src/**/*"] + "include": ["src/**/*"], + "references": [ + { + "path": "../extension" + } + ] }