@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.
@@ -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
@@ -0,0 +1,5 @@
1
+ /*
2
+ See the JupyterLab Developer Guide for useful CSS Patterns:
3
+
4
+ https://jupyterlab.readthedocs.io/en/stable/developer/css.html
5
+ */
@@ -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
@@ -0,0 +1,2 @@
1
+ import './base.css';
2
+ import './index.css';