jupyter-specta 0.1.8 → 0.2.0
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/README.md +32 -6
- package/lib/layout/layout_registry.d.ts +2 -1
- package/lib/layout/layout_registry.js +5 -2
- package/lib/layout/slides.d.ts +14 -0
- package/lib/layout/slides.js +123 -0
- package/lib/specta_model.d.ts +0 -1
- package/lib/specta_model.js +2 -2
- package/lib/specta_widget.js +9 -8
- package/lib/specta_widget_factory.js +8 -5
- package/lib/token.d.ts +5 -0
- package/lib/tool.d.ts +5 -0
- package/lib/tool.js +47 -1
- package/lib/topbar/settingDialog.d.ts +2 -1
- package/lib/topbar/settingDialog.js +9 -3
- package/lib/topbar/widget.js +6 -4
- package/package.json +11 -5
- package/schema/app-meta.json +31 -1
- package/style/base.css +22 -1
- package/style/index.css +3 -0
- package/style/index.js +4 -1
- package/style/slides.css +30 -0
package/README.md
CHANGED
|
@@ -14,8 +14,9 @@ Specta is a custom JupyterLite app for rendering notebooks and Jupyter‑support
|
|
|
14
14
|
|
|
15
15
|
Render notebooks in:
|
|
16
16
|
|
|
17
|
-
- **Dashboard mode** – structured panels for interactive widgets and outputs
|
|
18
|
-
- **Article mode** – a minimal, blog-like reading experience
|
|
17
|
+
- **Dashboard mode** – structured panels for interactive widgets and outputs.
|
|
18
|
+
- **Article mode** – a minimal, blog-like reading experience.
|
|
19
|
+
- **Slides mode** – a fullscreen presentation mode.
|
|
19
20
|
|
|
20
21
|
### Clean Viewer for all Jupyter-supported file types
|
|
21
22
|
|
|
@@ -25,6 +26,12 @@ View any Jupyter-supported file using Specta's clean viewer with all Jupyter UI
|
|
|
25
26
|
|
|
26
27
|
A `specta` preview can be launched directly from JupyterLab, letting users verify how their documents will look when published.
|
|
27
28
|
|
|
29
|
+
## Try it online!
|
|
30
|
+
|
|
31
|
+
You can try it online by clicking on this badge:
|
|
32
|
+
|
|
33
|
+
[](https://trungleduc.github.io/specta/specta/)
|
|
34
|
+
|
|
28
35
|
## Installation and Usage
|
|
29
36
|
|
|
30
37
|
You can install `specta` using `pip` or `conda`
|
|
@@ -47,6 +54,14 @@ Then serve the contents of the output directory (by default `./_output`) using a
|
|
|
47
54
|
|
|
48
55
|
## Specta Configuration
|
|
49
56
|
|
|
57
|
+
### Available layouts
|
|
58
|
+
|
|
59
|
+
Specta comes with three built-in layouts:
|
|
60
|
+
|
|
61
|
+
- `default`: The default layout, which renders the notebook as a dashboard.
|
|
62
|
+
- `article`: A minimal, blog-like reading experience.
|
|
63
|
+
- `slides`: A fullscreen presentation mode using [reveal.js](https://revealjs.com/).
|
|
64
|
+
|
|
50
65
|
### Top-level configuration
|
|
51
66
|
|
|
52
67
|
Specta can be configured using the typicall JupyterLite configuration file: `jupyter-lite.json`. You can add a `spectaConfig` key to the `jupyter-config-data` section of this file to customize the Specta app.
|
|
@@ -56,6 +71,7 @@ The following options are available:
|
|
|
56
71
|
- `defaultLayout`: The default layout when opening a file.
|
|
57
72
|
- `hideTopbar`: Boolean flag to show or hide the top bar.
|
|
58
73
|
- `topBar`: Configuration for the top bar.
|
|
74
|
+
- `slidesTheme`: The theme for the slides layout. The list of available themes can be found [here](https://revealjs.com/themes/).
|
|
59
75
|
|
|
60
76
|
```json
|
|
61
77
|
"topBar": {
|
|
@@ -78,11 +94,21 @@ The following options are available:
|
|
|
78
94
|
"title": "My blog",
|
|
79
95
|
"themeToggle": false
|
|
80
96
|
}
|
|
97
|
+
},
|
|
98
|
+
"slides.ipynb": {
|
|
99
|
+
"hideTopbar": true,
|
|
100
|
+
"slidesTheme": "sky"
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
```
|
|
84
104
|
|
|
85
|
-
### Notebook
|
|
105
|
+
### Notebook metadata configuration
|
|
106
|
+
|
|
107
|
+
In addition to the global configuration, you can also configure the layout and top bar for each notebook by using the notebook metadata. You can use the `Specta App Config` of the `Property Inspector` panel of JupyterLab to edit the notebook metadata.
|
|
108
|
+
|
|
109
|
+

|
|
110
|
+
|
|
111
|
+
### Notebook cell configuration
|
|
86
112
|
|
|
87
113
|
By default, when you open a notebook in Specta, all code cells are hidden, and placeholder skeletons are shown instead at the position of the cell. You can configure the visibility of each cell by using the Specta cell metadata toolbar.
|
|
88
114
|
|
|
@@ -93,8 +119,8 @@ By opening the `Property Inspector` panel of JupyterLab and selecting the `Spect
|
|
|
93
119
|
- `Show cell source`: use this toggle to show or hide the cell source code. Default to `false`
|
|
94
120
|
- `Show output placeholder`: use this toggle to show or hide the output skeleton. Default to `true`
|
|
95
121
|
|
|
96
|
-
|
|
122
|
+
### Slides layout configuration
|
|
97
123
|
|
|
98
|
-
|
|
124
|
+
For the slides layout, you can set the cells as a sub-slide for [vertical slide](https://revealjs.com/vertical-slides/) or [a fragment](https://revealjs.com/fragments/) using the Slide Type field in the `Common Tools` section of the `Property Inspector` panel.
|
|
99
125
|
|
|
100
|
-
|
|
126
|
+

|
|
@@ -10,10 +10,11 @@ export declare class SpectaLayoutRegistry implements ISpectaLayoutRegistry {
|
|
|
10
10
|
get selectedLayoutChanged(): ISignal<SpectaLayoutRegistry, {
|
|
11
11
|
name: string;
|
|
12
12
|
layout: ISpectaLayout;
|
|
13
|
+
oldLayout?: ISpectaLayout;
|
|
13
14
|
}>;
|
|
14
15
|
get(name: string): ISpectaLayout | undefined;
|
|
15
16
|
getDefaultLayout(): ISpectaLayout;
|
|
16
|
-
setSelectedLayout(name: string): void
|
|
17
|
+
setSelectedLayout(name: string): Promise<void>;
|
|
17
18
|
register(name: string, layout: ISpectaLayout): void;
|
|
18
19
|
allLayouts(): string[];
|
|
19
20
|
private _selectedLayout;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Signal } from '@lumino/signaling';
|
|
2
2
|
import { DefaultLayout } from './default';
|
|
3
3
|
import { ArticleLayout } from './article';
|
|
4
|
+
import { SlidesLayout } from './slides';
|
|
4
5
|
export class SpectaLayoutRegistry {
|
|
5
6
|
constructor() {
|
|
6
7
|
this._registry = new Map();
|
|
@@ -10,6 +11,7 @@ export class SpectaLayoutRegistry {
|
|
|
10
11
|
this._registry = new Map();
|
|
11
12
|
this._registry.set('default', defaultLayout);
|
|
12
13
|
this._registry.set('article', new ArticleLayout());
|
|
14
|
+
this._registry.set('slides', new SlidesLayout());
|
|
13
15
|
this._selectedLayout = {
|
|
14
16
|
name: 'default',
|
|
15
17
|
layout: defaultLayout
|
|
@@ -30,12 +32,13 @@ export class SpectaLayoutRegistry {
|
|
|
30
32
|
getDefaultLayout() {
|
|
31
33
|
return this._registry.get('default');
|
|
32
34
|
}
|
|
33
|
-
setSelectedLayout(name) {
|
|
35
|
+
async setSelectedLayout(name) {
|
|
34
36
|
if (!this._registry.has(name)) {
|
|
35
37
|
throw new Error(`Layout with name ${name} does not exist`);
|
|
36
38
|
}
|
|
39
|
+
const oldLayout = this._selectedLayout.layout;
|
|
37
40
|
this._selectedLayout = { name, layout: this._registry.get(name) };
|
|
38
|
-
this._selectedLayoutChanged.emit(this._selectedLayout);
|
|
41
|
+
this._selectedLayoutChanged.emit(Object.assign(Object.assign({}, this._selectedLayout), { oldLayout }));
|
|
39
42
|
}
|
|
40
43
|
register(name, layout) {
|
|
41
44
|
if (this._registry.has(name)) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Panel } from '@lumino/widgets';
|
|
2
|
+
import { SpectaCellOutput } from '../specta_cell_output';
|
|
3
|
+
import * as nbformat from '@jupyterlab/nbformat';
|
|
4
|
+
import { ISpectaAppConfig, ISpectaLayout } from '../token';
|
|
5
|
+
export declare class SlidesLayout implements ISpectaLayout {
|
|
6
|
+
render(options: {
|
|
7
|
+
host: Panel;
|
|
8
|
+
items: SpectaCellOutput[];
|
|
9
|
+
notebook: nbformat.INotebookContent;
|
|
10
|
+
readyCallback: () => Promise<void>;
|
|
11
|
+
spectaConfig: ISpectaAppConfig;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
private _deckMap;
|
|
14
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Panel, Widget } from '@lumino/widgets';
|
|
2
|
+
import Reveal from 'reveal.js';
|
|
3
|
+
import { emitResizeEvent, setRevealTheme } from '../tool';
|
|
4
|
+
class HostPanel extends Panel {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.addClass('specta-slides-host-widget');
|
|
8
|
+
this.addClass('reveal');
|
|
9
|
+
this._outputs = new Panel();
|
|
10
|
+
this._outputs.addClass('slides');
|
|
11
|
+
this.addWidget(this._outputs);
|
|
12
|
+
}
|
|
13
|
+
addOutput(slideEl) {
|
|
14
|
+
const { type, widget } = slideEl;
|
|
15
|
+
const sectionWidget = new Widget({
|
|
16
|
+
node: document.createElement('section')
|
|
17
|
+
});
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'slide': {
|
|
20
|
+
const outputWidget = widget[0];
|
|
21
|
+
sectionWidget.node.appendChild(outputWidget.node);
|
|
22
|
+
outputWidget.parent = sectionWidget;
|
|
23
|
+
sectionWidget.processMessage = msg => {
|
|
24
|
+
outputWidget.processMessage(msg);
|
|
25
|
+
};
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'subslide': {
|
|
29
|
+
for (const outputWidget of widget) {
|
|
30
|
+
const subSection = document.createElement('section');
|
|
31
|
+
subSection.appendChild(outputWidget.node);
|
|
32
|
+
outputWidget.parent = sectionWidget;
|
|
33
|
+
sectionWidget.node.appendChild(subSection);
|
|
34
|
+
}
|
|
35
|
+
sectionWidget.processMessage = msg => {
|
|
36
|
+
widget.forEach(w => w.processMessage(msg));
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case 'fragment': {
|
|
41
|
+
for (const [idx, outputWidget] of widget.entries()) {
|
|
42
|
+
if (idx !== 0) {
|
|
43
|
+
outputWidget.addClass('fragment');
|
|
44
|
+
outputWidget.addClass('fade-in');
|
|
45
|
+
}
|
|
46
|
+
sectionWidget.node.appendChild(outputWidget.node);
|
|
47
|
+
outputWidget.parent = sectionWidget;
|
|
48
|
+
}
|
|
49
|
+
sectionWidget.processMessage = msg => {
|
|
50
|
+
widget.forEach(w => w.processMessage(msg));
|
|
51
|
+
};
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
this._outputs.addWidget(sectionWidget);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class SlidesLayout {
|
|
61
|
+
constructor() {
|
|
62
|
+
this._deckMap = new WeakMap();
|
|
63
|
+
}
|
|
64
|
+
async render(options) {
|
|
65
|
+
var _a, _b, _c;
|
|
66
|
+
const theme = options.spectaConfig.slidesTheme;
|
|
67
|
+
if (theme) {
|
|
68
|
+
setRevealTheme(theme);
|
|
69
|
+
}
|
|
70
|
+
const { host, items, readyCallback } = options;
|
|
71
|
+
const hostPanel = new HostPanel();
|
|
72
|
+
const elementList = [];
|
|
73
|
+
for (const el of items) {
|
|
74
|
+
const info = el.info;
|
|
75
|
+
const cellMeta = ((_b = (_a = info.cellModel) === null || _a === void 0 ? void 0 : _a.metadata) !== null && _b !== void 0 ? _b : {});
|
|
76
|
+
const slideType = (_c = cellMeta === null || cellMeta === void 0 ? void 0 : cellMeta.slideshow) === null || _c === void 0 ? void 0 : _c.slide_type;
|
|
77
|
+
if (!info.hidden) {
|
|
78
|
+
const lastEl = elementList[elementList.length - 1];
|
|
79
|
+
switch (slideType) {
|
|
80
|
+
case 'subslide': {
|
|
81
|
+
if ((lastEl === null || lastEl === void 0 ? void 0 : lastEl.type) === 'subslide') {
|
|
82
|
+
lastEl.widget.push(el);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
elementList.push({ type: 'subslide', widget: [el] });
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'fragment': {
|
|
90
|
+
if ((lastEl === null || lastEl === void 0 ? void 0 : lastEl.type) === 'fragment') {
|
|
91
|
+
lastEl.widget.push(el);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
elementList.push({ type: 'fragment', widget: [el] });
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'slide': {
|
|
99
|
+
elementList.push({ type: 'slide', widget: [el] });
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
default: {
|
|
103
|
+
elementList.push({ type: 'slide', widget: [el] });
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const element of elementList) {
|
|
110
|
+
hostPanel.addOutput(element);
|
|
111
|
+
}
|
|
112
|
+
host.addWidget(hostPanel);
|
|
113
|
+
await readyCallback();
|
|
114
|
+
const deck = new Reveal(hostPanel.node, {
|
|
115
|
+
embedded: true
|
|
116
|
+
});
|
|
117
|
+
deck.initialize();
|
|
118
|
+
deck.on('slidetransitionend', event => {
|
|
119
|
+
emitResizeEvent();
|
|
120
|
+
});
|
|
121
|
+
this._deckMap.set(host.node, deck);
|
|
122
|
+
}
|
|
123
|
+
}
|
package/lib/specta_model.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
|
6
6
|
import { ServiceManager } from '@jupyterlab/services';
|
|
7
7
|
import { IExecuteReplyMsg } from '@jupyterlab/services/lib/kernel/messages';
|
|
8
8
|
import { SpectaCellOutput } from './specta_cell_output';
|
|
9
|
-
export declare const VIEW = "grid_default";
|
|
10
9
|
export declare class AppModel {
|
|
11
10
|
private options;
|
|
12
11
|
constructor(options: AppModel.IOptions);
|
package/lib/specta_model.js
CHANGED
|
@@ -2,8 +2,7 @@ import { CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells';
|
|
|
2
2
|
import { OutputAreaModel, SimplifiedOutputArea } from '@jupyterlab/outputarea';
|
|
3
3
|
import { createNotebookContext, createNotebookPanel } from './create_notebook_panel';
|
|
4
4
|
import { SpectaCellOutput } from './specta_cell_output';
|
|
5
|
-
import { readCellConfig } from './tool';
|
|
6
|
-
export const VIEW = 'grid_default';
|
|
5
|
+
import { emitResizeEvent, readCellConfig } from './tool';
|
|
7
6
|
export class AppModel {
|
|
8
7
|
constructor(options) {
|
|
9
8
|
this.options = options;
|
|
@@ -148,6 +147,7 @@ export class AppModel {
|
|
|
148
147
|
const source = cell.sharedModel.source;
|
|
149
148
|
const rep = await SimplifiedOutputArea.execute(source, output, this._context.sessionContext);
|
|
150
149
|
output.future.done.then(() => {
|
|
150
|
+
emitResizeEvent();
|
|
151
151
|
outputWrapper.removePlaceholder();
|
|
152
152
|
});
|
|
153
153
|
return rep;
|
package/lib/specta_widget.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
2
2
|
import { Panel, Widget } from '@lumino/widgets';
|
|
3
|
-
import { hideAppLoadingIndicator, isSpectaApp } from './tool';
|
|
3
|
+
import { emitResizeEvent, hideAppLoadingIndicator, isSpectaApp } from './tool';
|
|
4
4
|
export class AppWidget extends Panel {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
super();
|
|
@@ -14,16 +14,14 @@ export class AppWidget extends Panel {
|
|
|
14
14
|
this._layoutRegistry = options.layoutRegistry;
|
|
15
15
|
this._host = new Panel();
|
|
16
16
|
this._host.addClass('specta-output-host');
|
|
17
|
+
this.addClass('specta-app-widget');
|
|
17
18
|
this.addWidget(this._host);
|
|
18
|
-
this.node.style.overflow = 'auto';
|
|
19
19
|
if (!isSpectaApp()) {
|
|
20
20
|
// Not a specta app, add spinner
|
|
21
21
|
this.addSpinner();
|
|
22
22
|
}
|
|
23
23
|
this._model.initialize().then(() => {
|
|
24
|
-
this.render()
|
|
25
|
-
.catch(console.error)
|
|
26
|
-
.then(() => window.dispatchEvent(new Event('resize')));
|
|
24
|
+
this.render().catch(console.error).then(emitResizeEvent);
|
|
27
25
|
});
|
|
28
26
|
this._layoutRegistry.selectedLayoutChanged.connect(this._onSelectedLayoutChanged, this);
|
|
29
27
|
}
|
|
@@ -85,7 +83,8 @@ export class AppWidget extends Panel {
|
|
|
85
83
|
host: this._host,
|
|
86
84
|
items: this._outputs,
|
|
87
85
|
notebook: (_e = this._model.context) === null || _e === void 0 ? void 0 : _e.model.toJSON(),
|
|
88
|
-
readyCallback
|
|
86
|
+
readyCallback,
|
|
87
|
+
spectaConfig: this._spectaAppConfig
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
90
|
onCloseRequest(msg) {
|
|
@@ -94,16 +93,18 @@ export class AppWidget extends Panel {
|
|
|
94
93
|
}
|
|
95
94
|
_onSelectedLayoutChanged(sender, args) {
|
|
96
95
|
var _a;
|
|
96
|
+
const { layout } = args;
|
|
97
97
|
const currentEls = [...this._host.widgets];
|
|
98
98
|
currentEls.forEach(el => {
|
|
99
99
|
var _a;
|
|
100
100
|
(_a = this._host.layout) === null || _a === void 0 ? void 0 : _a.removeWidget(el);
|
|
101
101
|
});
|
|
102
|
-
|
|
102
|
+
layout.render({
|
|
103
103
|
host: this._host,
|
|
104
104
|
items: this._outputs,
|
|
105
105
|
notebook: (_a = this._model.context) === null || _a === void 0 ? void 0 : _a.model.toJSON(),
|
|
106
|
-
readyCallback: async () => { }
|
|
106
|
+
readyCallback: async () => { },
|
|
107
|
+
spectaConfig: this._spectaAppConfig
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -8,11 +8,17 @@ export class SpectaWidgetFactory {
|
|
|
8
8
|
this._options = options;
|
|
9
9
|
}
|
|
10
10
|
async createNew(options) {
|
|
11
|
-
var _a;
|
|
11
|
+
var _a, _b;
|
|
12
12
|
const { context } = options;
|
|
13
13
|
const rendermime = this._options.rendermime.clone({
|
|
14
14
|
resolver: context.urlResolver
|
|
15
15
|
});
|
|
16
|
+
const spectaConfig = readSpectaConfig({
|
|
17
|
+
nbMetadata: context.model.metadata,
|
|
18
|
+
nbPath: (_a = context.contentsModel) === null || _a === void 0 ? void 0 : _a.path
|
|
19
|
+
});
|
|
20
|
+
const defaultLayout = (_b = spectaConfig.defaultLayout) !== null && _b !== void 0 ? _b : 'default';
|
|
21
|
+
this._options.spectaLayoutRegistry.setSelectedLayout(defaultLayout);
|
|
16
22
|
const model = new AppModel({
|
|
17
23
|
context,
|
|
18
24
|
manager: this._options.manager,
|
|
@@ -24,10 +30,7 @@ export class SpectaWidgetFactory {
|
|
|
24
30
|
notebookConfig: StaticNotebook.defaultNotebookConfig,
|
|
25
31
|
editorServices: this._options.editorServices
|
|
26
32
|
});
|
|
27
|
-
|
|
28
|
-
nbMetadata: context.model.metadata,
|
|
29
|
-
nbPath: (_a = context.contentsModel) === null || _a === void 0 ? void 0 : _a.path
|
|
30
|
-
});
|
|
33
|
+
// Create the specta pane
|
|
31
34
|
const panel = new AppWidget({
|
|
32
35
|
id: UUID.uuid4(),
|
|
33
36
|
label: '',
|
package/lib/token.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface ISpectaLayout {
|
|
|
14
14
|
items: SpectaCellOutput[];
|
|
15
15
|
notebook: nbformat.INotebookContent;
|
|
16
16
|
readyCallback: () => Promise<void>;
|
|
17
|
+
spectaConfig: ISpectaAppConfig;
|
|
17
18
|
}): Promise<void>;
|
|
18
19
|
}
|
|
19
20
|
export interface ISpectaLayoutRegistry {
|
|
@@ -30,6 +31,7 @@ export interface ISpectaLayoutRegistry {
|
|
|
30
31
|
selectedLayoutChanged: ISignal<ISpectaLayoutRegistry, {
|
|
31
32
|
name: string;
|
|
32
33
|
layout: ISpectaLayout;
|
|
34
|
+
oldLayout?: ISpectaLayout;
|
|
33
35
|
}>;
|
|
34
36
|
}
|
|
35
37
|
export interface ITopbarConfig {
|
|
@@ -38,12 +40,15 @@ export interface ITopbarConfig {
|
|
|
38
40
|
title?: string;
|
|
39
41
|
icon?: string;
|
|
40
42
|
kernelActivity?: boolean;
|
|
43
|
+
settingsButton?: boolean;
|
|
41
44
|
themeToggle?: boolean;
|
|
45
|
+
layoutToggle?: boolean;
|
|
42
46
|
}
|
|
43
47
|
export interface ISpectaAppConfig {
|
|
44
48
|
topBar?: ITopbarConfig;
|
|
45
49
|
defaultLayout?: string;
|
|
46
50
|
hideTopbar?: boolean;
|
|
51
|
+
slidesTheme?: string;
|
|
47
52
|
}
|
|
48
53
|
export interface ISpectaCellConfig {
|
|
49
54
|
showSource?: boolean;
|
package/lib/tool.d.ts
CHANGED
|
@@ -21,9 +21,14 @@ export declare function createFileBrowser(options: {
|
|
|
21
21
|
defaultBrowser: IDefaultFileBrowser;
|
|
22
22
|
}): any;
|
|
23
23
|
export declare function hideAppLoadingIndicator(): void;
|
|
24
|
+
export declare function mergeObjects(...objects: Record<string, any>[]): Record<string, any>;
|
|
25
|
+
export declare function getSpectaAssetUrl(path: string): string;
|
|
24
26
|
export declare function isSpectaApp(): boolean;
|
|
25
27
|
export declare function readSpectaConfig({ nbMetadata, nbPath }: {
|
|
26
28
|
nbMetadata?: INotebookMetadata;
|
|
27
29
|
nbPath?: string | null;
|
|
28
30
|
}): ISpectaAppConfig;
|
|
29
31
|
export declare function readCellConfig(cell?: ICell): Required<ISpectaCellConfig>;
|
|
32
|
+
export declare function debounce<T extends (...args: any[]) => void>(fn: T, delay?: number): (...args: Parameters<T>) => void;
|
|
33
|
+
export declare const emitResizeEvent: () => void;
|
|
34
|
+
export declare function setRevealTheme(themeName: string): void;
|
package/lib/tool.js
CHANGED
|
@@ -87,6 +87,22 @@ export function hideAppLoadingIndicator() {
|
|
|
87
87
|
}, 1000);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
export function mergeObjects(...objects) {
|
|
91
|
+
const result = {};
|
|
92
|
+
for (const obj of objects) {
|
|
93
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
94
|
+
if (value !== null && value !== undefined) {
|
|
95
|
+
result[key] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
export function getSpectaAssetUrl(path) {
|
|
102
|
+
const labExtension = PageConfig.getOption('fullLabextensionsUrl');
|
|
103
|
+
const url = URLExt.join(labExtension, 'jupyter-specta', 'static', path);
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
90
106
|
export function isSpectaApp() {
|
|
91
107
|
return !!document.querySelector('meta[name="specta-config"]');
|
|
92
108
|
}
|
|
@@ -107,7 +123,13 @@ export function readSpectaConfig({ nbMetadata, nbPath }) {
|
|
|
107
123
|
spectaConfig = Object.assign(Object.assign({}, spectaConfig), perFileConfig[pathWithoutDrive]);
|
|
108
124
|
}
|
|
109
125
|
const spectaMetadata = ((_a = nbMetadata === null || nbMetadata === void 0 ? void 0 : nbMetadata.specta) !== null && _a !== void 0 ? _a : {});
|
|
110
|
-
|
|
126
|
+
if (spectaMetadata.hideTopbar === 'Yes') {
|
|
127
|
+
spectaMetadata.hideTopbar = true;
|
|
128
|
+
}
|
|
129
|
+
else if (spectaMetadata.hideTopbar === 'No') {
|
|
130
|
+
spectaMetadata.hideTopbar = false;
|
|
131
|
+
}
|
|
132
|
+
return mergeObjects(spectaConfig, spectaMetadata);
|
|
111
133
|
}
|
|
112
134
|
export function readCellConfig(cell) {
|
|
113
135
|
var _a, _b;
|
|
@@ -124,3 +146,27 @@ export function readCellConfig(cell) {
|
|
|
124
146
|
}
|
|
125
147
|
return spectaCellConfig;
|
|
126
148
|
}
|
|
149
|
+
export function debounce(fn, delay = 100) {
|
|
150
|
+
let timeoutId;
|
|
151
|
+
return (...args) => {
|
|
152
|
+
clearTimeout(timeoutId);
|
|
153
|
+
timeoutId = setTimeout(() => {
|
|
154
|
+
fn(...args);
|
|
155
|
+
}, delay);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export const emitResizeEvent = debounce(() => {
|
|
159
|
+
window.dispatchEvent(new Event('resize'));
|
|
160
|
+
});
|
|
161
|
+
export function setRevealTheme(themeName) {
|
|
162
|
+
let themeLink = document.getElementById('reveal-theme');
|
|
163
|
+
if (!themeLink) {
|
|
164
|
+
// Create <link> tag if it doesn't exist
|
|
165
|
+
themeLink = document.createElement('link');
|
|
166
|
+
themeLink.rel = 'stylesheet';
|
|
167
|
+
themeLink.id = 'reveal-theme';
|
|
168
|
+
document.head.appendChild(themeLink);
|
|
169
|
+
}
|
|
170
|
+
// Set or update href to new theme
|
|
171
|
+
themeLink.href = getSpectaAssetUrl(`reveal.js/${themeName}.css`);
|
|
172
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { ISpectaLayoutRegistry } from '../token';
|
|
3
|
+
import { ISpectaLayoutRegistry, ITopbarConfig } from '../token';
|
|
4
4
|
export declare const SettingContent: (props: {
|
|
5
|
+
config?: ITopbarConfig;
|
|
5
6
|
themeManager?: IThemeManager;
|
|
6
7
|
layoutRegistry?: ISpectaLayoutRegistry;
|
|
7
8
|
}) => React.JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { Divider } from '../components/divider';
|
|
3
3
|
export const SettingContent = (props) => {
|
|
4
|
-
var _a, _b, _c, _d, _e;
|
|
4
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
5
5
|
const { themeManager, layoutRegistry } = props;
|
|
6
6
|
const [themeOptions, setThemeOptions] = useState([
|
|
7
7
|
...((_a = themeManager === null || themeManager === void 0 ? void 0 : themeManager.themes) !== null && _a !== void 0 ? _a : [])
|
|
@@ -54,7 +54,10 @@ export const SettingContent = (props) => {
|
|
|
54
54
|
return (React.createElement("div", { style: { padding: '0 10px' } },
|
|
55
55
|
React.createElement("p", { style: { marginTop: 0, marginBottom: '5px', fontSize: '1rem' } }, "SETTINGS"),
|
|
56
56
|
React.createElement(Divider, null),
|
|
57
|
-
|
|
57
|
+
(((_f = props.config) === null || _f === void 0 ? void 0 : _f.layoutToggle) !== undefined
|
|
58
|
+
? props.config.layoutToggle
|
|
59
|
+
: true) &&
|
|
60
|
+
layoutRegistry && (React.createElement("div", null,
|
|
58
61
|
React.createElement("label", { htmlFor: "" }, "Select layout"),
|
|
59
62
|
React.createElement("div", { className: "jp-select-wrapper" },
|
|
60
63
|
React.createElement("select", { className: " jp-mod-styled specta-topbar-theme", onChange: onLayoutChange, value: selectedLayout }, layoutOptions.map(el => {
|
|
@@ -62,7 +65,10 @@ export const SettingContent = (props) => {
|
|
|
62
65
|
background: 'var(--jp-layout-color2)'
|
|
63
66
|
} }, el.charAt(0).toUpperCase() + el.slice(1)));
|
|
64
67
|
}))))),
|
|
65
|
-
|
|
68
|
+
(((_g = props.config) === null || _g === void 0 ? void 0 : _g.themeToggle) !== undefined
|
|
69
|
+
? props.config.themeToggle
|
|
70
|
+
: true) &&
|
|
71
|
+
themeManager && (React.createElement("div", null,
|
|
66
72
|
React.createElement("label", { htmlFor: "" }, "Select theme"),
|
|
67
73
|
React.createElement("div", { className: "jp-select-wrapper" },
|
|
68
74
|
React.createElement("select", { className: " jp-mod-styled specta-topbar-theme", onChange: onThemeChange, value: selectedTheme }, themeOptions.map(el => {
|
package/lib/topbar/widget.js
CHANGED
|
@@ -3,7 +3,7 @@ import { GearIcon } from '../components/icon/gear';
|
|
|
3
3
|
import { IconButton } from '../components/iconButton';
|
|
4
4
|
import { SettingContent } from './settingDialog';
|
|
5
5
|
export function TopbarElement(props) {
|
|
6
|
-
var _a, _b;
|
|
6
|
+
var _a, _b, _c;
|
|
7
7
|
const config = React.useMemo(() => {
|
|
8
8
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
9
9
|
return {
|
|
@@ -32,11 +32,13 @@ export function TopbarElement(props) {
|
|
|
32
32
|
}, []);
|
|
33
33
|
return (React.createElement("div", { className: "specta-topbar", style: { background: (_a = config.background) !== null && _a !== void 0 ? _a : 'var(--jp-layout-color2)' } },
|
|
34
34
|
React.createElement("div", { className: "specta-topbar-left" },
|
|
35
|
-
React.createElement("div",
|
|
35
|
+
React.createElement("div", { className: "specta-topbar-icon-container" }, config.icon && React.createElement("img", { style: { height: '100%' }, src: config.icon })),
|
|
36
36
|
React.createElement("div", { className: "specta-topbar-title", style: { color: (_b = config.textColor) !== null && _b !== void 0 ? _b : 'var(--jp-ui-font-color1)' } }, config.title)),
|
|
37
|
-
|
|
37
|
+
(((_c = props.config) === null || _c === void 0 ? void 0 : _c.settingsButton) !== undefined
|
|
38
|
+
? props.config.settingsButton
|
|
39
|
+
: true) && (React.createElement("div", { className: "specta-topbar-right" },
|
|
38
40
|
React.createElement(IconButton, { ref: buttonRef, onClick: () => setOpen(!open), icon: React.createElement(GearIcon, { fill: "var(--jp-ui-font-color2)", height: 23, width: 23 }) }),
|
|
39
41
|
open && (React.createElement("div", { ref: dialogRef, className: "jp-Dialog-content specta-config-dialog" },
|
|
40
42
|
React.createElement("div", { className: "specta-config-arrow" }),
|
|
41
|
-
React.createElement(SettingContent, {
|
|
43
|
+
React.createElement(SettingContent, { config: props.config, themeManager: props.themeManager })))))));
|
|
42
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyter-specta",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/trungleduc/specta",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"clean": "rimraf specta/labextension && rimraf app/specta/build && rimraf specta/app_static",
|
|
30
30
|
"build:lib": "tsc",
|
|
31
|
-
"build:labextension": "jupyter labextension build .",
|
|
32
|
-
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
31
|
+
"build:labextension": "jupyter labextension build . && npm run copy:reveal",
|
|
32
|
+
"build:labextension:dev": "jupyter labextension build --development True . && npm run copy:reveal",
|
|
33
33
|
"build:lab": "npm run clean && npm run build:lib && npm run build:labextension",
|
|
34
34
|
"build:app": "npm run clean && npm run build:lib && cd app && NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production webpack --mode production",
|
|
35
35
|
"build:app:dev": "npm run clean && npm run build:lib && cd app && webpack",
|
|
@@ -37,9 +37,13 @@
|
|
|
37
37
|
"build:all:dev": "npm run build:app:dev && npm run build:labextension:dev",
|
|
38
38
|
"build:demo": "cd demo && rm -rf .jupyterlite.doit.db _output && jupyter lite build .",
|
|
39
39
|
"update:demo": "node script/build-dev.mjs",
|
|
40
|
+
"copy:reveal": "node script/copy-reveal.mjs",
|
|
40
41
|
"lint": "npm run lint:prettier && npm run lint:eslint",
|
|
42
|
+
"lint:check": "npm run lint:prettier:check && npm run lint:eslint:check",
|
|
41
43
|
"lint:prettier": "prettier --no-error-on-unmatched-pattern --write \"**/*{.ts,.tsx,.jsx,.css,.json,.md,.yml}\"",
|
|
42
|
-
"lint:
|
|
44
|
+
"lint:prettier:check": "prettier --no-error-on-unmatched-pattern --check \"**/*{.ts,.tsx,.jsx,.css,.json,.md,.yml}\"",
|
|
45
|
+
"lint:eslint": "eslint --ext .ts,.tsx . --fix",
|
|
46
|
+
"lint:eslint:check": "eslint --ext .ts,.tsx .",
|
|
43
47
|
"install:extension": "npm run build:lib && npm run build:labextension:dev"
|
|
44
48
|
},
|
|
45
49
|
"overrides": {
|
|
@@ -106,9 +110,11 @@
|
|
|
106
110
|
"@lumino/signaling": "^2.0.0",
|
|
107
111
|
"@lumino/virtualdom": "^2.0.0",
|
|
108
112
|
"@lumino/widgets": "^2.0.0",
|
|
113
|
+
"@types/reveal.js": "^5.2.0",
|
|
109
114
|
"@voila-dashboards/voila": "^0.5.5",
|
|
110
115
|
"react": "^18.3.0",
|
|
111
|
-
"react-dom": "^18.3.0"
|
|
116
|
+
"react-dom": "^18.3.0",
|
|
117
|
+
"reveal.js": "^5.2.1"
|
|
112
118
|
},
|
|
113
119
|
"devDependencies": {
|
|
114
120
|
"@jupyterlab/builder": "~4.4.2",
|
package/schema/app-meta.json
CHANGED
|
@@ -12,8 +12,34 @@
|
|
|
12
12
|
"/specta/defaultLayout": {
|
|
13
13
|
"title": "Page layout",
|
|
14
14
|
"type": "string",
|
|
15
|
-
"enum": ["default", "article"],
|
|
15
|
+
"enum": ["default", "article", "slides"],
|
|
16
16
|
"default": "default"
|
|
17
|
+
},
|
|
18
|
+
"/specta/hideTopbar": {
|
|
19
|
+
"title": "Hide topbar",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": ["Yes", "No", null],
|
|
22
|
+
"default": null
|
|
23
|
+
},
|
|
24
|
+
"/specta/slidesTheme": {
|
|
25
|
+
"title": "Slides layout theme",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": [
|
|
28
|
+
null,
|
|
29
|
+
"beige",
|
|
30
|
+
"black",
|
|
31
|
+
"blood",
|
|
32
|
+
"dracula",
|
|
33
|
+
"league",
|
|
34
|
+
"moon",
|
|
35
|
+
"night",
|
|
36
|
+
"serif",
|
|
37
|
+
"simple",
|
|
38
|
+
"sky",
|
|
39
|
+
"solarized",
|
|
40
|
+
"white"
|
|
41
|
+
],
|
|
42
|
+
"default": null
|
|
17
43
|
}
|
|
18
44
|
}
|
|
19
45
|
},
|
|
@@ -25,6 +51,10 @@
|
|
|
25
51
|
"/specta/defaultLayout": {
|
|
26
52
|
"metadataLevel": "notebook",
|
|
27
53
|
"writeDefault": false
|
|
54
|
+
},
|
|
55
|
+
"/specta/slidesTheme": {
|
|
56
|
+
"metadataLevel": "notebook",
|
|
57
|
+
"writeDefault": false
|
|
28
58
|
}
|
|
29
59
|
}
|
|
30
60
|
}
|
package/style/base.css
CHANGED
|
@@ -52,10 +52,20 @@
|
|
|
52
52
|
left: calc(50% - 52px);
|
|
53
53
|
color: var(--jp-ui-font-color1) !important;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
.specta-app-widget {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-grow: 1;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
}
|
|
56
60
|
.jp-specta-notebook-panel {
|
|
57
61
|
overflow: auto;
|
|
58
62
|
padding: 0 5px 5px 5px;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.specta-output-host {
|
|
68
|
+
flex-grow: 1;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
#specta-top-panel {
|
|
@@ -88,15 +98,26 @@
|
|
|
88
98
|
display: flex;
|
|
89
99
|
align-items: center;
|
|
90
100
|
gap: 0.5rem;
|
|
101
|
+
height: 80%;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.specta-topbar-icon-container {
|
|
105
|
+
height: 100%;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
.specta-topbar-right {
|
|
94
109
|
position: relative;
|
|
95
110
|
}
|
|
96
111
|
|
|
112
|
+
.specta-topbar-right .jp-Dialog-content {
|
|
113
|
+
min-height: unset !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
.specta-topbar-title {
|
|
98
117
|
line-height: 36px;
|
|
99
118
|
font-size: 1.25rem;
|
|
119
|
+
font-family: 'Quicksand', sans-serif;
|
|
120
|
+
font-weight: 500;
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
.specta-topbar-theme {
|
package/style/index.css
CHANGED
package/style/index.js
CHANGED
package/style/slides.css
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.specta-slides-host-widget {
|
|
2
|
+
--jp-content-font-family: 'Source Serif 4', Georgia, serif;
|
|
3
|
+
--jp-code-font-family:
|
|
4
|
+
'Source Code Pro', Menlo, Monaco, 'Courier New', Courier, monospace;
|
|
5
|
+
--jp-code-font-size: 14px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.specta-slides-host-widget.reveal {
|
|
9
|
+
.jp-Collapser-child,
|
|
10
|
+
.jp-collapseHeadingButton,
|
|
11
|
+
.jp-InputPrompt,
|
|
12
|
+
.jp-InputCollapser,
|
|
13
|
+
.jp-InternalAnchorLink {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.jp-CodeMirrorEditor {
|
|
18
|
+
text-align: start !important;
|
|
19
|
+
}
|
|
20
|
+
h1:last-child,
|
|
21
|
+
h2:last-child,
|
|
22
|
+
h3:last-child,
|
|
23
|
+
h4:last-child,
|
|
24
|
+
h5:last-child,
|
|
25
|
+
h6:last-child {
|
|
26
|
+
margin-bottom: calc(
|
|
27
|
+
0.5 * var(--jp-content-heading-margin-bottom)
|
|
28
|
+
) !important;
|
|
29
|
+
}
|
|
30
|
+
}
|