Skip to content

Commit 0f0b7a1

Browse files
authored
Backport PR #50 on branch 3.x (Add factory token for jupytext customization) (#54)
* Backport PR #50: Add factory token for jupytext customization * Fix tokens API
1 parent 1046c85 commit 0f0b7a1

File tree

6 files changed

+190
-58
lines changed

6 files changed

+190
-58
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.bundle.*
22
lib/
33
node_modules/
4+
.yarn/
45
.eslintcache
56
.stylelintcache
67
*.egg-info/

packages/application/src/plugins/rise.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ export const plugin: JupyterFrontEndPlugin<void> = {
8888
app.restored
8989
]).then(async ([settings]) => {
9090
const notebookPath = PageConfig.getOption('notebookPath');
91-
const notebookPanel = documentManager.open(notebookPath) as NotebookPanel;
91+
const notebookPanel = (documentManager.open(notebookPath, 'Notebook') ??
92+
// If the file cannot be opened with the Notebook factory, try jupytext
93+
documentManager.open(
94+
notebookPath,
95+
'Jupytext Notebook'
96+
)) as NotebookPanel;
9297

9398
Rise.registerCommands(app.commands, notebookPanel, trans);
9499
if (palette) {

packages/lab/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@lumino/coreutils": "^1.11.0",
5656
"@lumino/disposable": "^1.7.0",
5757
"@lumino/messaging": "^1.10.1",
58+
"@lumino/signaling": "^1.11.1",
5859
"@lumino/widgets": "^1.19.0"
5960
},
6061
"devDependencies": {

packages/lab/src/index.ts

+37-36
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import {
1111
WidgetTracker
1212
} from '@jupyterlab/apputils';
1313

14-
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
15-
1614
import { DocumentRegistry } from '@jupyterlab/docregistry';
1715

1816
import {
@@ -24,21 +22,19 @@ import {
2422

2523
import { ISettingRegistry } from '@jupyterlab/settingregistry';
2624

27-
import { ITranslator } from '@jupyterlab/translation';
25+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
2826

2927
import { toArray } from '@lumino/algorithm';
3028

3129
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
3230

3331
import { fullScreenIcon, RISEIcon } from './icons';
3432

35-
import {
36-
RisePreview,
37-
IRisePreviewTracker,
38-
RisePreviewFactory
39-
} from './preview';
33+
import { RisePreview } from './preview';
4034

41-
export { IRisePreviewTracker } from './preview';
35+
import { IRisePreviewFactory, IRisePreviewTracker } from './tokens';
36+
37+
export { IRisePreviewFactory, IRisePreviewTracker } from './tokens';
4238

4339
/**
4440
* Command IDs namespace for JupyterLab RISE extension
@@ -59,27 +55,46 @@ namespace CommandIDs {
5955
export const riseSetSlideType = 'RISE:set-slide-type';
6056
}
6157

58+
const factory: JupyterFrontEndPlugin<IRisePreviewFactory> = {
59+
id: 'jupyterlab-rise:factory',
60+
provides: IRisePreviewFactory,
61+
optional: [ITranslator],
62+
activate: (
63+
app: JupyterFrontEnd,
64+
translator: ITranslator | null
65+
): IRisePreviewFactory => {
66+
const { commands, docRegistry } = app;
67+
return new RisePreview.FactoryToken({
68+
commands,
69+
docRegistry,
70+
translator: translator ?? undefined
71+
});
72+
}
73+
};
74+
6275
/**
6376
* Open the notebook with RISE.
6477
*/
6578
const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
6679
id: 'jupyterlab-rise:plugin',
6780
autoStart: true,
68-
requires: [ITranslator],
81+
requires: [IRisePreviewFactory],
6982
optional: [
7083
INotebookTracker,
7184
ICommandPalette,
7285
ILayoutRestorer,
73-
ISettingRegistry
86+
ISettingRegistry,
87+
ITranslator
7488
],
7589
provides: IRisePreviewTracker,
7690
activate: (
7791
app: JupyterFrontEnd,
78-
translator: ITranslator,
92+
factory: IRisePreviewFactory,
7993
notebookTracker: INotebookTracker | null,
8094
palette: ICommandPalette | null,
8195
restorer: ILayoutRestorer | null,
82-
settingRegistry: ISettingRegistry | null
96+
settingRegistry: ISettingRegistry | null,
97+
translator: ITranslator | null
8398
): IRisePreviewTracker => {
8499
console.log('JupyterLab extension jupyterlab-rise is activated!');
85100

@@ -92,8 +107,8 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
92107
return tracker;
93108
}
94109

95-
const { commands, docRegistry, shell } = app;
96-
const trans = translator.load('rise');
110+
const { commands, shell } = app;
111+
const trans = (translator ?? nullTranslator).load('rise');
97112

98113
let settings: ISettingRegistry.ISettings | null = null;
99114
if (settingRegistry) {
@@ -102,27 +117,19 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
102117
});
103118
}
104119

105-
const factory = new RisePreviewFactory(getRiseUrl, commands, {
106-
name: 'rise',
107-
fileTypes: ['notebook'],
108-
modelName: 'notebook'
109-
});
110-
111120
if (restorer) {
112121
restorer.restore(tracker, {
113122
// Need to modify to handle auto full screen
114123
command: 'docmanager:open',
115124
args: panel => ({
116125
path: panel.context.path,
117-
factory: factory.name
126+
factory: RisePreview.FACTORY_NAME
118127
}),
119128
name: panel => panel.context.path,
120129
when: app.serviceManager.ready
121130
});
122131
}
123132

124-
docRegistry.addWidgetFactory(factory);
125-
126133
function getCurrent(args: ReadonlyPartialJSONObject): NotebookPanel | null {
127134
const widget = notebookTracker?.currentWidget ?? null;
128135
const activate = args['activate'] !== false;
@@ -141,15 +148,6 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
141148
);
142149
}
143150

144-
function getRiseUrl(path: string, activeCellIndex?: number): string {
145-
const baseUrl = PageConfig.getBaseUrl();
146-
let url = `${baseUrl}rise/${path}`;
147-
if (typeof activeCellIndex === 'number') {
148-
url += URLExt.objectToQueryString({ activeCellIndex });
149-
}
150-
return url;
151-
}
152-
153151
factory.widgetCreated.connect((sender, widget) => {
154152
// Notify the widget tracker if restore data needs to update.
155153
widget.context.pathChanged.connect(() => {
@@ -175,7 +173,10 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
175173
}
176174
await current.context.save();
177175
window.open(
178-
getRiseUrl(current.context.path, current.content.activeCellIndex)
176+
RisePreview.getRiseUrl(
177+
current.context.path,
178+
current.content.activeCellIndex
179+
)
179180
);
180181
},
181182
isEnabled
@@ -197,7 +198,7 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
197198
'docmanager:open',
198199
{
199200
path: context.path,
200-
factory: 'rise',
201+
factory: RisePreview.FACTORY_NAME,
201202
options: {
202203
mode: 'split-right'
203204
}
@@ -378,4 +379,4 @@ const plugin: JupyterFrontEndPlugin<IRisePreviewTracker> = {
378379
}
379380
};
380381

381-
export default plugin;
382+
export default [factory, plugin];

packages/lab/src/preview.ts

+102-21
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import {
2-
IFrame,
3-
ToolbarButton,
4-
IWidgetTracker,
5-
Toolbar
6-
} from '@jupyterlab/apputils';
1+
import { IFrame, ToolbarButton, Toolbar } from '@jupyterlab/apputils';
2+
3+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
74

85
import {
96
ABCWidgetFactory,
@@ -19,28 +16,18 @@ import { refreshIcon } from '@jupyterlab/ui-components';
1916

2017
import { CommandRegistry } from '@lumino/commands';
2118

22-
import { PromiseDelegate, Token } from '@lumino/coreutils';
19+
import { PromiseDelegate } from '@lumino/coreutils';
20+
21+
import { DisposableSet, IDisposable } from '@lumino/disposable';
2322

2423
import { Message } from '@lumino/messaging';
2524

26-
import { Signal } from '@lumino/signaling';
25+
import { ISignal, Signal } from '@lumino/signaling';
2726

2827
import { Widget } from '@lumino/widgets';
2928

3029
import { fullScreenIcon, RISEIcon } from './icons';
31-
32-
/**
33-
* A class that tracks Rise Preview widgets.
34-
*/
35-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
36-
export interface IRisePreviewTracker extends IWidgetTracker<RisePreview> {}
37-
38-
/**
39-
* The Rise Preview tracker token.
40-
*/
41-
export const IRisePreviewTracker = new Token<IRisePreviewTracker>(
42-
'jupyterlab-rise:IRisePreviewTracker'
43-
);
30+
import { IRisePreviewFactory } from './tokens';
4431

4532
/**
4633
* A DocumentWidget that shows a Rise preview in an IFrame.
@@ -230,6 +217,8 @@ export class RisePreview extends DocumentWidget<IFrame, INotebookModel> {
230217
* A namespace for RisePreview statics.
231218
*/
232219
export namespace RisePreview {
220+
export const FACTORY_NAME = 'rise';
221+
233222
/**
234223
* Instantiation options for `RisePreview`.
235224
*/
@@ -249,8 +238,100 @@ export namespace RisePreview {
249238
*/
250239
renderOnSave?: boolean;
251240
}
241+
242+
/**
243+
* Generate the URL required to open a file as RISE slideshow.
244+
*
245+
* @param path File path
246+
* @param activeCellIndex Active cell index
247+
* @returns URL to open
248+
*/
249+
export function getRiseUrl(path: string, activeCellIndex?: number): string {
250+
const baseUrl = PageConfig.getBaseUrl();
251+
let url = `${baseUrl}rise/${path}`;
252+
if (typeof activeCellIndex === 'number') {
253+
url += URLExt.objectToQueryString({ activeCellIndex });
254+
}
255+
return url;
256+
}
257+
258+
/**
259+
* RISE Preview document factory token implementation.
260+
*/
261+
export class FactoryToken implements IRisePreviewFactory {
262+
constructor({
263+
commands,
264+
docRegistry,
265+
fileTypes,
266+
translator
267+
}: {
268+
commands: CommandRegistry;
269+
docRegistry: DocumentRegistry;
270+
fileTypes?: string[];
271+
translator?: ITranslator;
272+
}) {
273+
this._commands = commands;
274+
this._docRegistry = docRegistry;
275+
this._fileTypes = fileTypes ?? ['notebook'];
276+
277+
this._updateFactory();
278+
}
279+
280+
/**
281+
* Add a new file type to the RISE preview factory.
282+
*
283+
* #### Notes
284+
* Useful to add file types for jupytext.
285+
*
286+
* @param ft File type
287+
*/
288+
addFileType(ft: string): void {
289+
if (!this._fileTypes.includes(ft)) {
290+
this._fileTypes.push(ft);
291+
this._updateFactory();
292+
}
293+
}
294+
295+
/**
296+
* Signal emitted when a RISE preview is created.
297+
*/
298+
get widgetCreated(): ISignal<IRisePreviewFactory, RisePreview> {
299+
return this._widgetCreated;
300+
}
301+
302+
private _updateFactory(): void {
303+
if (this._disposeFactory) {
304+
this._disposeFactory.dispose();
305+
this._disposeFactory = null;
306+
}
307+
308+
const factory = new RisePreviewFactory(getRiseUrl, this._commands, {
309+
name: FACTORY_NAME,
310+
fileTypes: this._fileTypes,
311+
modelName: 'notebook'
312+
});
313+
314+
factory.widgetCreated.connect((_, args) => {
315+
this._widgetCreated.emit(args);
316+
}, this);
317+
318+
this._disposeFactory = DisposableSet.from([
319+
this._docRegistry.addWidgetFactory(factory),
320+
factory
321+
]);
322+
}
323+
324+
private _commands: CommandRegistry;
325+
private _disposeFactory: IDisposable | null = null;
326+
private _docRegistry: DocumentRegistry;
327+
private _fileTypes: string[];
328+
private _widgetCreated = new Signal<FactoryToken, RisePreview>(this);
329+
}
252330
}
253331

332+
/**
333+
* RISE Preview widget factory
334+
*/
254335
export class RisePreviewFactory extends ABCWidgetFactory<
255336
RisePreview,
256337
INotebookModel

0 commit comments

Comments
 (0)