@fails-components/jupyter-applet-view 0.0.1-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +101 -0
- package/lib/appletview.d.ts +7 -0
- package/lib/appletview.js +333 -0
- package/lib/avoutputarea.d.ts +149 -0
- package/lib/avoutputarea.js +751 -0
- package/lib/avtoolbarextension.d.ts +36 -0
- package/lib/avtoolbarextension.js +213 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +35 -0
- package/lib/splitviewnotebookpanel.d.ts +31 -0
- package/lib/splitviewnotebookpanel.js +127 -0
- package/package.json +226 -0
- package/schema/plugin.json +31 -0
- package/schema/toolbar.json +8 -0
- package/src/appletview.ts +395 -0
- package/src/avoutputarea.ts +966 -0
- package/src/avtoolbarextension.ts +280 -0
- package/src/index.ts +47 -0
- package/src/splitviewnotebookpanel.ts +186 -0
- package/style/base.css +5 -0
- package/style/index.css +163 -0
- package/style/index.js +2 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolbarRegistry,
|
|
3
|
+
createDefaultFactory,
|
|
4
|
+
setToolbar
|
|
5
|
+
} from '@jupyterlab/apputils';
|
|
6
|
+
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
7
|
+
import { IObservableList, ObservableList } from '@jupyterlab/observables';
|
|
8
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
9
|
+
import { IDisposable } from '@lumino/disposable';
|
|
10
|
+
import { PanelLayout, Widget } from '@lumino/widgets';
|
|
11
|
+
import { SplitViewNotebookPanel } from './splitviewnotebookpanel';
|
|
12
|
+
import { AppletViewOutputArea, IViewPart } from './avoutputarea';
|
|
13
|
+
import { Toolbar } from '@jupyterlab/ui-components';
|
|
14
|
+
import { Signal } from '@lumino/signaling';
|
|
15
|
+
import { IFailsLauncherInfo } from '@fails-components/jupyter-launcher';
|
|
16
|
+
|
|
17
|
+
// portions used from Jupyterlab:
|
|
18
|
+
/* -----------------------------------------------------------------------------
|
|
19
|
+
| Copyright (c) Jupyter Development Team.
|
|
20
|
+
| Distributed under the terms of the Modified BSD License.
|
|
21
|
+
|----------------------------------------------------------------------------*/
|
|
22
|
+
// This code contains portions from or is inspired by Jupyter lab's notebook extension, especially the createOutputView part
|
|
23
|
+
// Also a lot is taken from the cell toolbar related parts.
|
|
24
|
+
|
|
25
|
+
export const defaultToolbarItems: ToolbarRegistry.IWidget[] = [
|
|
26
|
+
{
|
|
27
|
+
command: 'fails-components-jupyter-applet-view:move_view_up',
|
|
28
|
+
name: 'move-view-up'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: 'fails-components-jupyter-applet-view:move_view_down',
|
|
32
|
+
name: 'move-view-down'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
command: 'fails-components-jupyter-applet-view:move_view_app_up',
|
|
36
|
+
name: 'move-view-app-up'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
command: 'fails-components-jupyter-applet-view:move_view_app_down',
|
|
40
|
+
name: 'move-view-app-down'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
command: 'fails-components-jupyter-applet-view:delete_view',
|
|
44
|
+
name: 'delete-view'
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// a lot of code taken from Jupyter labs CellBarExtension
|
|
49
|
+
|
|
50
|
+
export class AppletViewToolbarExtension
|
|
51
|
+
implements DocumentRegistry.WidgetExtension
|
|
52
|
+
{
|
|
53
|
+
static readonly FACTORY_NAME = 'AppletView';
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
commands: CommandRegistry,
|
|
57
|
+
launcherInfo: IFailsLauncherInfo | null,
|
|
58
|
+
toolbarFactory?: (
|
|
59
|
+
widget: Widget
|
|
60
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>
|
|
61
|
+
) {
|
|
62
|
+
this._commands = commands;
|
|
63
|
+
this._launcherInfo = launcherInfo;
|
|
64
|
+
// # TODO we have to make sure, we get the default, how can we do this?
|
|
65
|
+
this._toolbarFactory = toolbarFactory ?? this.defaultToolbarFactory;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected get defaultToolbarFactory(): (
|
|
69
|
+
widget: Widget
|
|
70
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem> {
|
|
71
|
+
const itemFactory = createDefaultFactory(this._commands);
|
|
72
|
+
return (widget: Widget) =>
|
|
73
|
+
new ObservableList({
|
|
74
|
+
values: defaultToolbarItems.map(item => {
|
|
75
|
+
// console.log('widget? factory', widget);
|
|
76
|
+
const applet = widget.parent as Widget;
|
|
77
|
+
const parent = applet.parent as AppletViewOutputArea;
|
|
78
|
+
const path = parent.path;
|
|
79
|
+
return {
|
|
80
|
+
name: item.name,
|
|
81
|
+
widget: itemFactory(
|
|
82
|
+
AppletViewToolbarExtension.FACTORY_NAME,
|
|
83
|
+
widget,
|
|
84
|
+
{
|
|
85
|
+
...item,
|
|
86
|
+
args: {
|
|
87
|
+
// @ts-expect-error cellid is not part of Widget
|
|
88
|
+
cellid: widget.cellid,
|
|
89
|
+
notepadpath: path,
|
|
90
|
+
// @ts-expect-error appid is not part of Widget
|
|
91
|
+
widgetid: widget.widgetid
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
};
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
createNew(panel: SplitViewNotebookPanel): IDisposable {
|
|
101
|
+
return new AppletViewToolbarTracker(
|
|
102
|
+
panel,
|
|
103
|
+
this._toolbarFactory,
|
|
104
|
+
this._launcherInfo
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _commands: CommandRegistry;
|
|
109
|
+
private _toolbarFactory: (
|
|
110
|
+
widget: Widget
|
|
111
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>;
|
|
112
|
+
private _launcherInfo: IFailsLauncherInfo | null;
|
|
113
|
+
}
|
|
114
|
+
export class AppletViewToolbarTracker implements IDisposable {
|
|
115
|
+
/**
|
|
116
|
+
* AppletViewToolbarTracker constructor
|
|
117
|
+
*
|
|
118
|
+
* @param view The Applet View area
|
|
119
|
+
* @param toolbarFactory The toolbar factory
|
|
120
|
+
*/
|
|
121
|
+
constructor(
|
|
122
|
+
notebookpanel: SplitViewNotebookPanel,
|
|
123
|
+
toolbarFactory: (
|
|
124
|
+
widget: Widget
|
|
125
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>,
|
|
126
|
+
launcherInfo: IFailsLauncherInfo | null
|
|
127
|
+
) {
|
|
128
|
+
this._notebookpanel = notebookpanel;
|
|
129
|
+
this._toolbarFactory = toolbarFactory ?? null;
|
|
130
|
+
|
|
131
|
+
// Only add the toolbar to the notebook's active cell (if any) once it has fully rendered and been revealed.
|
|
132
|
+
void notebookpanel.revealed.then(() => {
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
this._notebookpanel?.appletViewWidget.viewChanged.connect(
|
|
135
|
+
this._addToolbar,
|
|
136
|
+
this
|
|
137
|
+
);
|
|
138
|
+
this._addToolbar();
|
|
139
|
+
if (launcherInfo?.inLecture) {
|
|
140
|
+
this._setHiddenToolbars(launcherInfo?.inLecture);
|
|
141
|
+
}
|
|
142
|
+
if (launcherInfo) {
|
|
143
|
+
let hasToolbar = !launcherInfo?.inLecture;
|
|
144
|
+
launcherInfo.inLectureChanged.connect(
|
|
145
|
+
(sender: IFailsLauncherInfo, newInLecture: boolean) => {
|
|
146
|
+
if (hasToolbar !== !newInLecture) {
|
|
147
|
+
this._setHiddenToolbars(newInLecture);
|
|
148
|
+
hasToolbar = !newInLecture;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
dispose(): void {
|
|
158
|
+
if (this.isDisposed) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this._isDisposed = true;
|
|
162
|
+
|
|
163
|
+
this._toolbarStore.forEach(tb => tb.dispose());
|
|
164
|
+
this._toolbarStore = [];
|
|
165
|
+
this._toolbars = new WeakMap<IViewPart, Toolbar>();
|
|
166
|
+
|
|
167
|
+
this._notebookpanel = null;
|
|
168
|
+
|
|
169
|
+
Signal.clearData(this);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get isDisposed(): boolean {
|
|
173
|
+
return this._isDisposed;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private _addToolbar(): void {
|
|
177
|
+
const notebookpanel = this._notebookpanel;
|
|
178
|
+
|
|
179
|
+
if (notebookpanel && !notebookpanel.isDisposed) {
|
|
180
|
+
const promises: Promise<void>[] = [
|
|
181
|
+
/*notebookpanel.ready*/
|
|
182
|
+
]; // remove area ready
|
|
183
|
+
const applets = notebookpanel.appletViewWidget?.applets;
|
|
184
|
+
|
|
185
|
+
const doAddToolbar = (part: IViewPart) => {
|
|
186
|
+
const clone = part.clone;
|
|
187
|
+
if (clone) {
|
|
188
|
+
// eslint-disable-next-line no-constant-condition
|
|
189
|
+
const toolbarWidget = new Toolbar();
|
|
190
|
+
this._toolbars.set(part, toolbarWidget);
|
|
191
|
+
this._toolbarStore.push(toolbarWidget);
|
|
192
|
+
// Note: CELL_MENU_CLASS is deprecated.
|
|
193
|
+
toolbarWidget.addClass('fl-jp-AppletViewToolbar'); // implement MR
|
|
194
|
+
if (this._toolbarFactory) {
|
|
195
|
+
// ts-expect-error Widget has no toolbar
|
|
196
|
+
// clone.toolbar = toolbarWidget;
|
|
197
|
+
setToolbar(clone, this._toolbarFactory, toolbarWidget);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const applet of applets) {
|
|
203
|
+
for (const part of applet.parts) {
|
|
204
|
+
const clone = part.clone;
|
|
205
|
+
if (!this._toolbars.has(part)) {
|
|
206
|
+
if (clone) {
|
|
207
|
+
// eslint-disable-next-line no-constant-condition
|
|
208
|
+
doAddToolbar(part);
|
|
209
|
+
} else {
|
|
210
|
+
// we have to defer it
|
|
211
|
+
const slot = () => {
|
|
212
|
+
doAddToolbar(part);
|
|
213
|
+
part.cloned.disconnect(slot);
|
|
214
|
+
};
|
|
215
|
+
part.cloned.connect(slot);
|
|
216
|
+
this._toolbars.set(part, null);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// FIXME toolbarWidget.update() - strangely this does not work
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// promises.push(area.ready); // remove?
|
|
224
|
+
// Wait for all the buttons to be rendered before attaching the toolbar.
|
|
225
|
+
Promise.all(promises)
|
|
226
|
+
.then(() => {
|
|
227
|
+
for (const applet of applets) {
|
|
228
|
+
for (const part of applet.parts) {
|
|
229
|
+
const toolbarWidget = this._toolbars.get(part);
|
|
230
|
+
if (!toolbarWidget) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!part.clone || part.clone.isDisposed) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const clone = part.clone;
|
|
237
|
+
if (clone) {
|
|
238
|
+
// (clone!.layout as PanelLayout).insertWidget(0, toolbarWidget);
|
|
239
|
+
(clone!.layout as PanelLayout).addWidget(toolbarWidget);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// For rendered markdown, watch for resize events.
|
|
245
|
+
// area.displayChanged.connect(this._resizeEventCallback, this); // remove?
|
|
246
|
+
// Watch for changes in the cell's contents.
|
|
247
|
+
// area.model.contentChanged.connect(this._changedEventCallback, this); ?
|
|
248
|
+
// Hide the cell toolbar if it overlaps with cell contents
|
|
249
|
+
// this._updateCellForToolbarOverlap(area); // remove?
|
|
250
|
+
})
|
|
251
|
+
.catch(e => {
|
|
252
|
+
console.error('Error rendering buttons of the cell toolbar: ', e);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_setHiddenToolbars(hidden: boolean): void {
|
|
258
|
+
const notebookpanel = this._notebookpanel;
|
|
259
|
+
if (notebookpanel && !notebookpanel.isDisposed) {
|
|
260
|
+
const applets = notebookpanel.appletViewWidget?.applets;
|
|
261
|
+
for (const applet of applets) {
|
|
262
|
+
for (const part of applet.parts) {
|
|
263
|
+
const toolbarWidget = this._toolbars.get(part);
|
|
264
|
+
if (!toolbarWidget) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
toolbarWidget.setHidden(hidden);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private _isDisposed = false;
|
|
274
|
+
private _notebookpanel: SplitViewNotebookPanel | null;
|
|
275
|
+
private _toolbars = new WeakMap<IViewPart, Toolbar | null>();
|
|
276
|
+
private _toolbarStore: Toolbar[] = [];
|
|
277
|
+
private _toolbarFactory: (
|
|
278
|
+
widget: Widget
|
|
279
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>;
|
|
280
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ILayoutRestorer,
|
|
3
|
+
JupyterFrontEnd,
|
|
4
|
+
JupyterFrontEndPlugin
|
|
5
|
+
} from '@jupyterlab/application';
|
|
6
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
7
|
+
import { ITranslator } from '@jupyterlab/translation';
|
|
8
|
+
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
9
|
+
import { AppletViewToolbarExtension } from './avtoolbarextension';
|
|
10
|
+
import { activateAppletView } from './appletview';
|
|
11
|
+
import { IFailsInterceptor } from '@fails-components/jupyter-interceptor';
|
|
12
|
+
import { IFailsLauncherInfo } from '@fails-components/jupyter-launcher';
|
|
13
|
+
|
|
14
|
+
const appletView: JupyterFrontEndPlugin<void> = {
|
|
15
|
+
id: '@fails-components/jupyter-applet-view:plugin',
|
|
16
|
+
description:
|
|
17
|
+
"An extension, that let's you select cell and switch to an applet mode, where only the selected cells are visible. This is used for fails-components to have jupyter applets in interactive teaching. ",
|
|
18
|
+
requires: [IDocumentManager, INotebookTracker, ITranslator],
|
|
19
|
+
optional: [ILayoutRestorer, IFailsLauncherInfo, IFailsInterceptor],
|
|
20
|
+
autoStart: true,
|
|
21
|
+
activate: activateAppletView
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const appletViewToolbar: JupyterFrontEndPlugin<void> = {
|
|
25
|
+
id: '@fails-components/jupyter-applet-view:toolbar',
|
|
26
|
+
description: 'Add the applet view toolbar during editing.',
|
|
27
|
+
autoStart: true,
|
|
28
|
+
activate: async (app: JupyterFrontEnd, launcherInfo: IFailsLauncherInfo) => {
|
|
29
|
+
const toolbarItems = undefined;
|
|
30
|
+
app.docRegistry.addWidgetExtension(
|
|
31
|
+
'Notebook',
|
|
32
|
+
new AppletViewToolbarExtension(app.commands, launcherInfo, toolbarItems)
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
optional: [IFailsLauncherInfo]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialization data for the @fails-components/jupyter-applet-view extension.
|
|
40
|
+
*/
|
|
41
|
+
const plugins: JupyterFrontEndPlugin<any>[] = [
|
|
42
|
+
// all JupyterFrontEndPlugins
|
|
43
|
+
appletView,
|
|
44
|
+
appletViewToolbar
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export default plugins;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry';
|
|
2
|
+
import {
|
|
3
|
+
NotebookPanel,
|
|
4
|
+
Notebook,
|
|
5
|
+
INotebookModel,
|
|
6
|
+
NotebookHistory,
|
|
7
|
+
NotebookWidgetFactory,
|
|
8
|
+
StaticNotebook
|
|
9
|
+
} from '@jupyterlab/notebook';
|
|
10
|
+
import { BoxLayout, SplitPanel } from '@lumino/widgets';
|
|
11
|
+
import { AppletViewOutputArea } from './avoutputarea';
|
|
12
|
+
import {
|
|
13
|
+
IFailsLauncherInfo,
|
|
14
|
+
IAppletScreenshottaker,
|
|
15
|
+
IScreenShotOpts
|
|
16
|
+
} from '@fails-components/jupyter-launcher';
|
|
17
|
+
import { IFailsInterceptor } from '@fails-components/jupyter-interceptor';
|
|
18
|
+
|
|
19
|
+
interface IAppletResizeEvent {
|
|
20
|
+
appid: string;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class SplitViewNotebookPanel
|
|
26
|
+
extends NotebookPanel
|
|
27
|
+
implements IAppletScreenshottaker
|
|
28
|
+
{
|
|
29
|
+
constructor(
|
|
30
|
+
options: DocumentWidget.IOptions<Notebook, INotebookModel>,
|
|
31
|
+
failsLauncherInfo: IFailsLauncherInfo | undefined,
|
|
32
|
+
failsInterceptor: IFailsInterceptor | undefined
|
|
33
|
+
) {
|
|
34
|
+
super(options);
|
|
35
|
+
this._failsLauncherInfo = failsLauncherInfo;
|
|
36
|
+
// now we have to do the following
|
|
37
|
+
// 1. remove this._content from the layout
|
|
38
|
+
const content = this['_content'];
|
|
39
|
+
const layout = this.layout as BoxLayout;
|
|
40
|
+
layout.removeWidget(content);
|
|
41
|
+
// 2. add a BoxLayout instead
|
|
42
|
+
const splitPanel = new SplitPanel({
|
|
43
|
+
spacing: 1,
|
|
44
|
+
orientation: 'horizontal'
|
|
45
|
+
});
|
|
46
|
+
BoxLayout.setStretch(splitPanel, 1);
|
|
47
|
+
|
|
48
|
+
// 3. add content to the BoxLayout, as well as a applet view area
|
|
49
|
+
splitPanel.addWidget(content);
|
|
50
|
+
const widget = (this._appletviewWidget = new AppletViewOutputArea({
|
|
51
|
+
notebook: this,
|
|
52
|
+
applets: undefined,
|
|
53
|
+
translator: options.translator,
|
|
54
|
+
interceptor: failsInterceptor
|
|
55
|
+
}));
|
|
56
|
+
splitPanel.addWidget(widget);
|
|
57
|
+
layout.addWidget(splitPanel);
|
|
58
|
+
// move to separate handler
|
|
59
|
+
if (failsLauncherInfo?.inLecture) {
|
|
60
|
+
this.toolbar.hide();
|
|
61
|
+
this.addClass('fl-jl-notebook-inlecture');
|
|
62
|
+
this._appletviewWidget.inLecture = true;
|
|
63
|
+
content.hide();
|
|
64
|
+
splitPanel.setRelativeSizes([0, 1]); // change sizes
|
|
65
|
+
}
|
|
66
|
+
if (failsLauncherInfo) {
|
|
67
|
+
failsLauncherInfo.inLectureChanged.connect(
|
|
68
|
+
(sender: IFailsLauncherInfo, newInLecture: boolean) => {
|
|
69
|
+
if (newInLecture) {
|
|
70
|
+
this.toolbar.hide();
|
|
71
|
+
this.addClass('fl-jl-notebook-inlecture');
|
|
72
|
+
this._appletviewWidget.inLecture = true;
|
|
73
|
+
content.hide();
|
|
74
|
+
splitPanel.setRelativeSizes([0, 1]); // change sizes
|
|
75
|
+
} else {
|
|
76
|
+
this.toolbar.show();
|
|
77
|
+
this.removeClass('fl-jl-notebook-inlecture');
|
|
78
|
+
this._appletviewWidget.inLecture = false;
|
|
79
|
+
content.show();
|
|
80
|
+
splitPanel.setRelativeSizes([1, 1]); // change sizes
|
|
81
|
+
widget.unselectApplet();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
if (
|
|
86
|
+
failsLauncherInfo.inLecture &&
|
|
87
|
+
typeof failsLauncherInfo.selectedAppid !== 'undefined'
|
|
88
|
+
) {
|
|
89
|
+
widget.selectApplet(failsLauncherInfo.selectedAppid);
|
|
90
|
+
}
|
|
91
|
+
failsLauncherInfo.selectedAppidChanged.connect(
|
|
92
|
+
(sender: IFailsLauncherInfo, appid: string | undefined) => {
|
|
93
|
+
if (typeof appid !== 'undefined') {
|
|
94
|
+
widget.selectApplet(appid);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
widget.viewChanged.connect((sender: AppletViewOutputArea) => {
|
|
100
|
+
const failsData = sender.saveData();
|
|
101
|
+
if (failsData) {
|
|
102
|
+
const model = this.context.model;
|
|
103
|
+
model.setMetadata('failsApp', failsData);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const metadataUpdater = () => {
|
|
107
|
+
const { failsApp, kernelspec } = this.context.model.metadata;
|
|
108
|
+
if (failsLauncherInfo?.reportMetadata) {
|
|
109
|
+
failsLauncherInfo.reportMetadata({ failsApp, kernelspec });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
this.context.model.metadataChanged.connect(metadataUpdater);
|
|
113
|
+
metadataUpdater();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
appletResizeinfo({ appid, width, height }: IAppletResizeEvent) {
|
|
117
|
+
if (this._failsLauncherInfo) {
|
|
118
|
+
this._failsLauncherInfo.appletSizes[appid] = { appid, width, height };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get appletViewWidget() {
|
|
123
|
+
return this._appletviewWidget;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async takeAppScreenshot(opts: IScreenShotOpts): Promise<Blob | undefined> {
|
|
127
|
+
return await this._appletviewWidget.takeAppScreenshot(opts);
|
|
128
|
+
}
|
|
129
|
+
/*
|
|
130
|
+
private _splitPanel: SplitPanel; */
|
|
131
|
+
private _appletviewWidget: AppletViewOutputArea;
|
|
132
|
+
private _failsLauncherInfo: IFailsLauncherInfo | undefined;
|
|
133
|
+
}
|
|
134
|
+
namespace SplitViewNotebookWidgetFactory {
|
|
135
|
+
export interface IOptions
|
|
136
|
+
extends NotebookWidgetFactory.IOptions<NotebookPanel> {
|
|
137
|
+
failsLauncherInfo?: IFailsLauncherInfo;
|
|
138
|
+
failsInterceptor?: IFailsInterceptor;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class SplitViewNotebookWidgetFactory extends NotebookWidgetFactory {
|
|
143
|
+
constructor(options: SplitViewNotebookWidgetFactory.IOptions) {
|
|
144
|
+
super(options);
|
|
145
|
+
this._failsLauncherInfo = options.failsLauncherInfo;
|
|
146
|
+
this._failsInterceptor = options.failsInterceptor;
|
|
147
|
+
}
|
|
148
|
+
protected createNewWidget(
|
|
149
|
+
context: DocumentRegistry.IContext<INotebookModel>,
|
|
150
|
+
source?: NotebookPanel
|
|
151
|
+
): SplitViewNotebookPanel {
|
|
152
|
+
// copied from basis object
|
|
153
|
+
const translator = (context as any).translator;
|
|
154
|
+
const kernelHistory = new NotebookHistory({
|
|
155
|
+
sessionContext: context.sessionContext,
|
|
156
|
+
translator: translator
|
|
157
|
+
});
|
|
158
|
+
const nbOptions = {
|
|
159
|
+
rendermime: source
|
|
160
|
+
? source.content.rendermime
|
|
161
|
+
: this.rendermime.clone({ resolver: context.urlResolver }),
|
|
162
|
+
contentFactory: this.contentFactory,
|
|
163
|
+
mimeTypeService: this.mimeTypeService,
|
|
164
|
+
editorConfig: source
|
|
165
|
+
? source.content.editorConfig
|
|
166
|
+
: (this['_editorConfig'] as StaticNotebook.IEditorConfig),
|
|
167
|
+
notebookConfig: source
|
|
168
|
+
? source.content.notebookConfig
|
|
169
|
+
: (this['_notebookConfig'] as StaticNotebook.INotebookConfig),
|
|
170
|
+
translator,
|
|
171
|
+
kernelHistory
|
|
172
|
+
};
|
|
173
|
+
const content = this.contentFactory.createNotebook(nbOptions);
|
|
174
|
+
return new SplitViewNotebookPanel(
|
|
175
|
+
{
|
|
176
|
+
context,
|
|
177
|
+
content
|
|
178
|
+
},
|
|
179
|
+
this._failsLauncherInfo,
|
|
180
|
+
this._failsInterceptor
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private _failsLauncherInfo: IFailsLauncherInfo | undefined;
|
|
185
|
+
private _failsInterceptor: IFailsInterceptor | undefined;
|
|
186
|
+
}
|
package/style/base.css
ADDED
package/style/index.css
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
@import url('base.css');
|
|
2
|
+
|
|
3
|
+
.fl-jp-AppletView .fl-jp-Applet .jp-OutputArea {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.fl-jp-AppletView .fl-jp-Applet {
|
|
8
|
+
overflow-y: auto;
|
|
9
|
+
background: var(--jp-layout-color0);
|
|
10
|
+
box-shadow: var(--jp-elevation-z4);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fl-jp-AppletView {
|
|
14
|
+
overflow-y: auto;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.fl-jp-AppletView .jp-collapseHeadingButton {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.fl-jp-AppletView .jp-InternalAnchorLink {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-OutputArea-child,
|
|
26
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-Cell,
|
|
27
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-CellHeader,
|
|
28
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-CellFooter,
|
|
29
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-Cell-inputWrapper,
|
|
30
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-InputArea,
|
|
31
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet .jp-OutputArea {
|
|
32
|
+
width: fit-content;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h1,
|
|
36
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h2,
|
|
37
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h3,
|
|
38
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h4,
|
|
39
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h5,
|
|
40
|
+
.fl-jl-notebook-inlecture .fl-jp-Applet h6 {
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
|
|
43
|
+
/* overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis; */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.fl-jl-notebook-inlecture .jp-OutputArea-output {
|
|
48
|
+
width: max-content;
|
|
49
|
+
height: max-content;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes fade-out {
|
|
53
|
+
0% {
|
|
54
|
+
opacity: 1;
|
|
55
|
+
max-height: 50vh;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
90% {
|
|
59
|
+
opacity: 0.1;
|
|
60
|
+
max-height: 50vh;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
100% {
|
|
64
|
+
opacity: 0;
|
|
65
|
+
max-height: 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.fl-jl-notebook-inlecture [data-mime-type='application/vnd.jupyter.stderr'] {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
max-height: 50vh;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
animation: fade-out 2s ease forwards;
|
|
74
|
+
animation-delay: 2s;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fl-jp-AppletViewToolbar {
|
|
78
|
+
position: absolute;
|
|
79
|
+
right: 0;
|
|
80
|
+
top: 0;
|
|
81
|
+
box-shadow: none;
|
|
82
|
+
border-bottom: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.lm-AccordionPanel .lm-AccordionPanel-title {
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
padding: 0 10px;
|
|
88
|
+
background: #f5f5f5;
|
|
89
|
+
border: 2px solid #c0c0c0;
|
|
90
|
+
border-bottom: none;
|
|
91
|
+
font:
|
|
92
|
+
12px Helvetica,
|
|
93
|
+
Arial,
|
|
94
|
+
sans-serif;
|
|
95
|
+
min-height: 22px;
|
|
96
|
+
max-height: 22px;
|
|
97
|
+
min-width: 35px;
|
|
98
|
+
line-height: 20px;
|
|
99
|
+
margin: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.lm-AccordionPanel
|
|
103
|
+
.lm-AccordionPanel-title
|
|
104
|
+
.lm-AccordionPanel-titleCollapser::before {
|
|
105
|
+
content: '\25B2';
|
|
106
|
+
position: absolute;
|
|
107
|
+
right: 10px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.lm-AccordionPanel
|
|
111
|
+
.lm-AccordionPanel-title.lm-mod-expanded
|
|
112
|
+
.lm-AccordionPanel-titleCollapser::before {
|
|
113
|
+
content: '\25BC';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.fl-jl-notebook-inlecture .lm-AccordionPanel-title {
|
|
117
|
+
display: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.fl-jl-notebook-inlecture .lm-SplitPanel-handle {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.fl-jl-notebook-inlecture .fl-jp-AppletView {
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.fl-jl-notebook-inlecture .jp-NotebookPanel-notebook {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
body[data-notebook='notebooks'] .jp-WindowedPanel-inner {
|
|
133
|
+
margin-top: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.jp-OutputArea.fl-jl-cell-interceptor-unsupported:not(
|
|
137
|
+
.fl-jl-notebook-inlecture *
|
|
138
|
+
)::after {
|
|
139
|
+
content: 'Network transmission of this output is unsupported.';
|
|
140
|
+
position: absolute;
|
|
141
|
+
bottom: 0;
|
|
142
|
+
left: 50%;
|
|
143
|
+
transform: translateX(-50%);
|
|
144
|
+
color: red;
|
|
145
|
+
padding: 2px 4px;
|
|
146
|
+
font-size: 1em;
|
|
147
|
+
font-weight: bold;
|
|
148
|
+
text-align: start;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.jp-OutputArea .widget-button:not(.fl-jl-notebook-inlecture *)::after {
|
|
152
|
+
content: 'Unsupported!';
|
|
153
|
+
position: absolute;
|
|
154
|
+
bottom: 0;
|
|
155
|
+
left: 50%;
|
|
156
|
+
transform: translateX(-50%);
|
|
157
|
+
color: red;
|
|
158
|
+
background-color: #fffc;
|
|
159
|
+
padding: 2px 4px;
|
|
160
|
+
font-size: 1em;
|
|
161
|
+
font-weight: bold;
|
|
162
|
+
text-align: start;
|
|
163
|
+
}
|
package/style/index.js
ADDED