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 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
+ [![Try on lite](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](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 specific configuration
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
+ ![Metadata](./docs/images/specta-meta.jpg)
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
- ## Try it online!
122
+ ### Slides layout configuration
97
123
 
98
- You can try it online by clicking on this badge:
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
- [![Try on lite](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://trungleduc.github.io/specta/specta/)
126
+ ![Slide tool](./docs/images/slide-tool.png)
@@ -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
+ }
@@ -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);
@@ -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;
@@ -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
- args.layout.render({
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
- const spectaConfig = readSpectaConfig({
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
- return Object.assign(Object.assign({}, spectaConfig), spectaMetadata);
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
- layoutRegistry && (React.createElement("div", null,
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
- themeManager && (React.createElement("div", null,
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 => {
@@ -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", null, config.icon && React.createElement("img", { style: { width: '50px' }, src: config.icon })),
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
- React.createElement("div", { className: "specta-topbar-right" },
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, { themeManager: props.themeManager, layoutRegistry: props.layoutRegistry }))))));
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.1.8",
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:eslint": "eslint --ext .ts,.tsx .",
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",
@@ -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
@@ -1,3 +1,6 @@
1
1
  @import url('base.css');
2
2
  @import url('article.css');
3
+ @import url('slides.css');
3
4
  @import url('skeleton.css');
5
+ @import url('reveal.js/dist/reveal.css');
6
+ @import url('reveal.js/dist/theme/white.css');
package/style/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  import './base.css';
2
2
  import './article.css';
3
- import './skeleton.css';
3
+ import './slides.css';
4
+ import './skeleton.css';
5
+ import 'reveal.js/dist/reveal.css';
6
+ import 'reveal.js/dist/theme/white.css';
@@ -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
+ }