From 4a0deb74166050d77b5dc27c2b8d34eabd198241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 6 Sep 2023 17:09:45 +0200 Subject: [PATCH] Add factory token --- packages/application/src/plugins/rise.ts | 7 ++- packages/lab/package.json | 1 + packages/lab/src/index.ts | 51 ++++++++--------- packages/lab/src/preview.ts | 72 +++++++++++++++++++++++- packages/lab/src/tokens.ts | 20 ++++++- yarn.lock | 1 + 6 files changed, 121 insertions(+), 31 deletions(-) diff --git a/packages/application/src/plugins/rise.ts b/packages/application/src/plugins/rise.ts index 3ddf105..d284bdc 100644 --- a/packages/application/src/plugins/rise.ts +++ b/packages/application/src/plugins/rise.ts @@ -89,7 +89,12 @@ export const plugin: JupyterFrontEndPlugin = { app.restored ]).then(async ([settings]) => { const notebookPath = PageConfig.getOption('notebookPath'); - const notebookPanel = documentManager.open(notebookPath) as NotebookPanel; + const notebookPanel = (documentManager.open(notebookPath, 'Notebook') ?? + // If the file cannot be opened with the Notebook factory, try jupytext + documentManager.open( + notebookPath, + 'Jupytext Notebook' + )) as NotebookPanel; // With the new windowing, some cells are not visible and we need // to deactivate the windowing and wait for each cell to be ready. notebookPanel.content.notebookConfig = { diff --git a/packages/lab/package.json b/packages/lab/package.json index 767b802..67fb841 100644 --- a/packages/lab/package.json +++ b/packages/lab/package.json @@ -55,6 +55,7 @@ "@lumino/coreutils": "^2.0.0", "@lumino/disposable": "^2.0.0", "@lumino/messaging": "^2.0.0", + "@lumino/signaling": "^2.0.0", "@lumino/widgets": "^2.0.1" }, "devDependencies": { diff --git a/packages/lab/src/index.ts b/packages/lab/src/index.ts index 9ce1a01..74c9765 100644 --- a/packages/lab/src/index.ts +++ b/packages/lab/src/index.ts @@ -11,8 +11,6 @@ import { WidgetTracker } from '@jupyterlab/apputils'; -import { PageConfig, URLExt } from '@jupyterlab/coreutils'; - import { DocumentRegistry } from '@jupyterlab/docregistry'; import { @@ -32,10 +30,11 @@ import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { fullScreenIcon, RISEIcon } from './icons'; -import { RisePreview, RisePreviewFactory } from './preview'; -import { IRisePreviewTracker } from './tokens'; +import { RisePreview } from './preview'; + +import { IRisePreviewFactory, IRisePreviewTracker } from './tokens'; -export { IRisePreviewTracker } from './tokens'; +export { IRisePreviewFactory, IRisePreviewTracker } from './tokens'; /** * Command IDs namespace for JupyterLab RISE extension @@ -56,13 +55,22 @@ namespace CommandIDs { export const riseSetSlideType = 'RISE:set-slide-type'; } +const factory: JupyterFrontEndPlugin = { + id: 'jupyterlab-rise:factory', + provides: IRisePreviewFactory, + activate: (app: JupyterFrontEnd): IRisePreviewFactory => { + const { commands, docRegistry } = app; + return new RisePreview.FactoryToken({ commands, docRegistry }); + } +}; + /** * Open the notebook with RISE. */ const plugin: JupyterFrontEndPlugin = { id: 'jupyterlab-rise:plugin', autoStart: true, - requires: [ITranslator], + requires: [IRisePreviewFactory, ITranslator], optional: [ INotebookTracker, ICommandPalette, @@ -72,6 +80,7 @@ const plugin: JupyterFrontEndPlugin = { provides: IRisePreviewTracker, activate: ( app: JupyterFrontEnd, + factory: IRisePreviewFactory, translator: ITranslator, notebookTracker: INotebookTracker | null, palette: ICommandPalette | null, @@ -89,7 +98,7 @@ const plugin: JupyterFrontEndPlugin = { return tracker; } - const { commands, docRegistry, shell } = app; + const { commands, shell } = app; const trans = translator.load('rise'); let settings: ISettingRegistry.ISettings | null = null; @@ -99,27 +108,19 @@ const plugin: JupyterFrontEndPlugin = { }); } - const factory = new RisePreviewFactory(getRiseUrl, commands, { - name: 'rise', - fileTypes: ['notebook'], - modelName: 'notebook' - }); - if (restorer) { restorer.restore(tracker, { // Need to modify to handle auto full screen command: 'docmanager:open', args: panel => ({ path: panel.context.path, - factory: factory.name + factory: RisePreview.FACTORY_NAME }), name: panel => panel.context.path, when: app.serviceManager.ready }); } - docRegistry.addWidgetFactory(factory); - function getCurrent(args: ReadonlyPartialJSONObject): NotebookPanel | null { const widget = notebookTracker?.currentWidget ?? null; const activate = args['activate'] !== false; @@ -138,15 +139,6 @@ const plugin: JupyterFrontEndPlugin = { ); } - function getRiseUrl(path: string, activeCellIndex?: number): string { - const baseUrl = PageConfig.getBaseUrl(); - let url = `${baseUrl}rise/${path}`; - if (typeof activeCellIndex === 'number') { - url += URLExt.objectToQueryString({ activeCellIndex }); - } - return url; - } - factory.widgetCreated.connect((sender, widget) => { // Notify the widget tracker if restore data needs to update. widget.context.pathChanged.connect(() => { @@ -172,7 +164,10 @@ const plugin: JupyterFrontEndPlugin = { } await current.context.save(); window.open( - getRiseUrl(current.context.path, current.content.activeCellIndex) + RisePreview.getRiseUrl( + current.context.path, + current.content.activeCellIndex + ) ); }, isEnabled @@ -194,7 +189,7 @@ const plugin: JupyterFrontEndPlugin = { 'docmanager:open', { path: context.path, - factory: 'rise', + factory: RisePreview.FACTORY_NAME, options: { mode: 'split-right' } @@ -374,4 +369,4 @@ const plugin: JupyterFrontEndPlugin = { } }; -export default plugin; +export default [factory, plugin]; diff --git a/packages/lab/src/preview.ts b/packages/lab/src/preview.ts index cc4cdfd..f7f8d73 100644 --- a/packages/lab/src/preview.ts +++ b/packages/lab/src/preview.ts @@ -1,5 +1,7 @@ import { IFrame, ToolbarButton, Toolbar } from '@jupyterlab/apputils'; +import { PageConfig, URLExt } from '@jupyterlab/coreutils'; + import { ABCWidgetFactory, DocumentRegistry, @@ -16,13 +18,16 @@ import { CommandRegistry } from '@lumino/commands'; import { PromiseDelegate } from '@lumino/coreutils'; +import { DisposableSet, IDisposable } from '@lumino/disposable'; + import { Message } from '@lumino/messaging'; -import { Signal } from '@lumino/signaling'; +import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from '@lumino/widgets'; import { fullScreenIcon, RISEIcon } from './icons'; +import { IRisePreviewFactory } from './tokens'; /** * A DocumentWidget that shows a Rise preview in an IFrame. @@ -212,6 +217,8 @@ export class RisePreview extends DocumentWidget { * A namespace for RisePreview statics. */ export namespace RisePreview { + export const FACTORY_NAME = 'rise'; + /** * Instantiation options for `RisePreview`. */ @@ -231,6 +238,69 @@ export namespace RisePreview { */ renderOnSave?: boolean; } + + export function getRiseUrl(path: string, activeCellIndex?: number): string { + const baseUrl = PageConfig.getBaseUrl(); + let url = `${baseUrl}rise/${path}`; + if (typeof activeCellIndex === 'number') { + url += URLExt.objectToQueryString({ activeCellIndex }); + } + return url; + } + + export class FactoryToken implements IRisePreviewFactory { + constructor(options: { + commands: CommandRegistry; + docRegistry: DocumentRegistry; + fileTypes?: string[]; + }) { + const { commands, docRegistry, fileTypes } = options; + this._commands = commands; + this._docRegistry = docRegistry; + this._fileTypes = fileTypes ?? ['notebook']; + + this._updateFactory(); + } + + addFileType(ft: string): void { + if (!this._fileTypes.includes(ft)) { + this._fileTypes.push(ft); + this._updateFactory(); + } + } + + get widgetCreated(): ISignal { + return this._widgetCreated; + } + + private _updateFactory(): void { + if (this._disposeFactory) { + this._disposeFactory.dispose(); + this._disposeFactory = null; + } + + const factory = new RisePreviewFactory(getRiseUrl, this._commands, { + name: FACTORY_NAME, + fileTypes: this._fileTypes, + modelName: 'notebook' + }); + + factory.widgetCreated.connect((_, args) => { + this._widgetCreated.emit(args); + }, this); + + this._disposeFactory = DisposableSet.from([ + this._docRegistry.addWidgetFactory(factory), + factory + ]); + } + + private _commands: CommandRegistry; + private _disposeFactory: IDisposable | null = null; + private _docRegistry: DocumentRegistry; + private _fileTypes: string[]; + private _widgetCreated = new Signal(this); + } } export class RisePreviewFactory extends ABCWidgetFactory< diff --git a/packages/lab/src/tokens.ts b/packages/lab/src/tokens.ts index 392c951..033e7d4 100644 --- a/packages/lab/src/tokens.ts +++ b/packages/lab/src/tokens.ts @@ -1,5 +1,6 @@ import { IWidgetTracker } from '@jupyterlab/apputils'; import { Token } from '@lumino/coreutils'; +import { ISignal } from '@lumino/signaling'; import type { RisePreview } from './preview'; /** @@ -12,5 +13,22 @@ export interface IRisePreviewTracker extends IWidgetTracker {} * The Rise Preview tracker token. */ export const IRisePreviewTracker = new Token( - 'jupyterlab-rise:IRisePreviewTracker' + 'jupyterlab-rise:IRisePreviewTracker', + 'Adds a tracker for RISE slides preview widgets.' +); + +/** + * + */ +export interface IRisePreviewFactory { + readonly widgetCreated: ISignal; + addFileType(ft: string): void; +} + +/** + * + */ +export const IRisePreviewFactory = new Token( + 'jupyterlab-rise:IRisePreviewFactory', + 'Customize the RISE slides preview factory.' ); diff --git a/yarn.lock b/yarn.lock index f187b71..614e0d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9555,6 +9555,7 @@ __metadata: "@lumino/coreutils": ^2.0.0 "@lumino/disposable": ^2.0.0 "@lumino/messaging": ^2.0.0 + "@lumino/signaling": ^2.0.0 "@lumino/widgets": ^2.0.1 rimraf: ~5.0.0 typescript: ~5.0.4