jupyterlab-tour 4.0.0 → 4.1.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 +5 -3
- package/lib/__tests__/plugin.spec.js +37 -5
- package/lib/__tests__/tour.spec.d.ts +1 -0
- package/lib/__tests__/tour.spec.js +86 -0
- package/lib/components.d.ts +3 -3
- package/lib/components.js +14 -2
- package/lib/constants.d.ts +1 -1
- package/lib/defaults.d.ts +3 -3
- package/lib/notebookButton.d.ts +3 -3
- package/lib/notebookTourManager.d.ts +18 -6
- package/lib/notebookTourManager.js +61 -35
- package/lib/package.json +49 -25
- package/lib/plugin.d.ts +2 -2
- package/lib/plugin.js +8 -4
- package/lib/schema/user-tours.json +1 -1
- package/lib/tokens.d.ts +26 -11
- package/lib/tour.d.ts +17 -6
- package/lib/tour.js +28 -7
- package/lib/tourManager.d.ts +3 -3
- package/lib/tourManager.js +10 -3
- package/lib/userTourManager.d.ts +1 -1
- package/package.json +49 -25
- package/schema/user-tours.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
[](https://pypi.org/project/jupyterlab-tour)
|
|
8
8
|
[](https://anaconda.org/conda-forge/jupyterlab-tour)
|
|
9
9
|
|
|
10
|
-
A JupyterLab UI Tour based on [react-joyride](https://
|
|
10
|
+
A JupyterLab UI Tour based on [react-joyride](https://react-joyride.com/) v2. The site
|
|
11
|
+
currently only has documentation for v3. Docs for the v2 release used here are on the
|
|
12
|
+
[v2.9.3 tag](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md).
|
|
11
13
|
|
|
12
14
|

|
|
13
15
|
|
|
@@ -69,7 +71,7 @@ own _Tours_ as data.
|
|
|
69
71
|
- Open the JupyterLab _Advanced Settings_ panel <kbd>Ctrl+,</kbd>
|
|
70
72
|
- Select _Tours_ from list of settings groups
|
|
71
73
|
- In the editor, create JSON(5) compatible with the
|
|
72
|
-
[react-joyride data model](https://
|
|
74
|
+
[react-joyride data model](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/props.md)
|
|
73
75
|
- The _Tour_ will be available from the _Help Menu_, as well as the _Command Palette_
|
|
74
76
|
|
|
75
77
|
### A simple Tour
|
|
@@ -206,7 +208,7 @@ tour.stepChanged.connect((_, data) => {
|
|
|
206
208
|
```
|
|
207
209
|
|
|
208
210
|
> `data` is an object of type
|
|
209
|
-
> [`CallbackProps`](https://
|
|
211
|
+
> [`CallbackProps`](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/callback.md).
|
|
210
212
|
|
|
211
213
|
## Disabling the User, Notebook, and Default Tours
|
|
212
214
|
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { Notebook, NotebookModel, NotebookPanel } from '@jupyterlab/notebook';
|
|
2
2
|
import { CommandRegistry } from '@lumino/commands';
|
|
3
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
3
4
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
5
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
4
6
|
import { CodeMirrorEditorFactory, CodeMirrorMimeTypeService, EditorLanguageRegistry } from '@jupyterlab/codemirror';
|
|
5
7
|
import { Signal } from '@lumino/signaling';
|
|
6
8
|
import 'jest';
|
|
7
9
|
import { CommandIDs } from '../constants';
|
|
8
10
|
import plugin from '../index';
|
|
9
11
|
import { NS } from '../tokens';
|
|
12
|
+
import * as nbformat from '@jupyterlab/nbformat';
|
|
13
|
+
import { YNotebook } from '@jupyter/ydoc';
|
|
10
14
|
const DEFAULT_TOURS_SIZE = 2;
|
|
11
15
|
const [corePlugin, userPlugin, notebookPlugin, defaultsPlugin] = plugin;
|
|
16
|
+
async function mockFetch(input, init) {
|
|
17
|
+
if (!(typeof input == 'object' &&
|
|
18
|
+
input.url.match(/api\/config\/jupyterlabtourplugin/) != null)) {
|
|
19
|
+
throw new Error(`cannot handle ${input}`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
status: 200,
|
|
23
|
+
json: async () => {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
12
28
|
function mockApp() {
|
|
13
29
|
return {
|
|
14
30
|
commands: new CommandRegistry(),
|
|
15
31
|
restored: Promise.resolve(),
|
|
16
|
-
docRegistry: new DocumentRegistry()
|
|
32
|
+
docRegistry: new DocumentRegistry(),
|
|
33
|
+
serviceManager: {
|
|
34
|
+
serverSettings: ServerConnection.makeSettings({ fetch: mockFetch })
|
|
35
|
+
}
|
|
17
36
|
};
|
|
18
37
|
}
|
|
19
38
|
function aTour() {
|
|
@@ -126,15 +145,28 @@ describe(notebookPlugin.id, () => {
|
|
|
126
145
|
mimeTypeService,
|
|
127
146
|
contentFactory: new NotebookPanel.ContentFactory({ editorFactory })
|
|
128
147
|
});
|
|
129
|
-
const
|
|
148
|
+
const sharedModel = YNotebook.create({
|
|
149
|
+
data: {
|
|
150
|
+
nbformat: nbformat.MAJOR_VERSION,
|
|
151
|
+
nbformat_minor: nbformat.MINOR_VERSION,
|
|
152
|
+
metadata: {
|
|
153
|
+
kernelspec: { name: '', display_name: '' },
|
|
154
|
+
language_info: { name: '' }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const model = new NotebookModel({ sharedModel });
|
|
130
159
|
notebook.model = model;
|
|
131
160
|
notebookTourManager.addNotebook(notebook);
|
|
132
161
|
expect(notebookTourManager.tourManager.tours.size).toBe(0);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
162
|
+
let changed = new PromiseDelegate();
|
|
163
|
+
notebookTourManager.notebookToursChanged.connect(() => changed.resolve());
|
|
164
|
+
model.setMetadata(NS, { tours: [aTour()] });
|
|
165
|
+
await changed.promise;
|
|
166
|
+
changed = new PromiseDelegate();
|
|
136
167
|
expect(notebookTourManager.tourManager.tours.size).toBe(1);
|
|
137
168
|
model.deleteMetadata(NS);
|
|
169
|
+
await changed.promise;
|
|
138
170
|
expect(notebookTourManager.tourManager.tours.size).toBe(0);
|
|
139
171
|
});
|
|
140
172
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'jest';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import 'jest';
|
|
2
|
+
import { STATUS } from 'react-joyride';
|
|
3
|
+
import { TourManager } from '../tourManager';
|
|
4
|
+
import { TourHandler } from '../tour';
|
|
5
|
+
function event(status, index) {
|
|
6
|
+
return { status, index, step: { target: 'body', content: '' } };
|
|
7
|
+
}
|
|
8
|
+
function runningTour(controlled = false) {
|
|
9
|
+
const tour = new TourHandler('tour', 'Tour', undefined, null, 0, controlled);
|
|
10
|
+
tour.addStep({ target: 'body', content: 'one' });
|
|
11
|
+
tour.addStep({ target: 'body', content: 'two' });
|
|
12
|
+
tour.handleTourEvent(event(STATUS.RUNNING, 0));
|
|
13
|
+
return tour;
|
|
14
|
+
}
|
|
15
|
+
describe('TourHandler', () => {
|
|
16
|
+
it('leaves the tour uncontrolled by default', () => {
|
|
17
|
+
const tour = new TourHandler('tour', 'Tour');
|
|
18
|
+
expect(tour.controlled).toBe(false);
|
|
19
|
+
expect(tour.currentStepIndex).toBe(-1);
|
|
20
|
+
expect(tour.isRunning()).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('updates the shared index when a controlled tour sets currentStepIndex', () => {
|
|
23
|
+
const tour = new TourHandler('tour', 'Tour', undefined, null, 0, true);
|
|
24
|
+
tour.currentStepIndex = 2;
|
|
25
|
+
expect(tour.currentStepIndex).toBe(2);
|
|
26
|
+
expect(tour.isRunning()).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('follows Joyride callbacks on an uncontrolled tour', () => {
|
|
29
|
+
const tour = runningTour(false);
|
|
30
|
+
expect(tour.currentStepIndex).toBe(0);
|
|
31
|
+
tour.handleTourEvent(event(STATUS.RUNNING, 1));
|
|
32
|
+
expect(tour.currentStepIndex).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
it('stores a currentStepIndex write on an uncontrolled tour', () => {
|
|
35
|
+
const tour = runningTour(false);
|
|
36
|
+
tour.currentStepIndex = 1;
|
|
37
|
+
expect(tour.currentStepIndex).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
it('keeps a controlled currentStepIndex when Joyride reports another index', () => {
|
|
40
|
+
const tour = runningTour(true);
|
|
41
|
+
tour.currentStepIndex = 1;
|
|
42
|
+
tour.handleTourEvent(event(STATUS.RUNNING, 0));
|
|
43
|
+
expect(tour.currentStepIndex).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
it('returns to idle when the tour finishes', () => {
|
|
46
|
+
const tour = runningTour(false);
|
|
47
|
+
tour.handleTourEvent(event(STATUS.FINISHED, 1));
|
|
48
|
+
expect(tour.currentStepIndex).toBe(-1);
|
|
49
|
+
expect(tour.isRunning()).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('TourManager', () => {
|
|
53
|
+
it('resets currentStepIndex when a controlled tour is launched', async () => {
|
|
54
|
+
const manager = new TourManager();
|
|
55
|
+
const handler = manager.addTour({
|
|
56
|
+
id: 'sample',
|
|
57
|
+
label: 'Sample',
|
|
58
|
+
hasHelpEntry: false,
|
|
59
|
+
controlled: true,
|
|
60
|
+
steps: [{ target: 'body', content: 'one', placement: 'center' }]
|
|
61
|
+
});
|
|
62
|
+
if (!handler) {
|
|
63
|
+
throw new Error('tour was not created');
|
|
64
|
+
}
|
|
65
|
+
handler.currentStepIndex = 1;
|
|
66
|
+
await manager.launch([handler], true);
|
|
67
|
+
expect(handler.currentStepIndex).toBe(0);
|
|
68
|
+
manager.dispose();
|
|
69
|
+
});
|
|
70
|
+
it('does not reset currentStepIndex when an uncontrolled tour is launched', async () => {
|
|
71
|
+
const manager = new TourManager();
|
|
72
|
+
const handler = manager.addTour({
|
|
73
|
+
id: 'sample',
|
|
74
|
+
label: 'Sample',
|
|
75
|
+
hasHelpEntry: false,
|
|
76
|
+
steps: [{ target: 'body', content: 'one', placement: 'center' }]
|
|
77
|
+
});
|
|
78
|
+
if (!handler) {
|
|
79
|
+
throw new Error('tour was not created');
|
|
80
|
+
}
|
|
81
|
+
handler.handleTourEvent(event(STATUS.RUNNING, 1));
|
|
82
|
+
await manager.launch([handler], true);
|
|
83
|
+
expect(handler.currentStepIndex).toBe(1);
|
|
84
|
+
manager.dispose();
|
|
85
|
+
});
|
|
86
|
+
});
|
package/lib/components.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { ISignal } from '@lumino/signaling';
|
|
3
|
-
import { ITourManager } from './tokens';
|
|
4
|
-
import { TourHandler } from './tour';
|
|
2
|
+
import type { ISignal } from '@lumino/signaling';
|
|
3
|
+
import type { ITourManager } from './tokens';
|
|
4
|
+
import type { TourHandler } from './tour';
|
|
5
5
|
/**
|
|
6
6
|
* Tour container
|
|
7
7
|
*/
|
package/lib/components.js
CHANGED
|
@@ -18,8 +18,9 @@ class Tour extends React.Component {
|
|
|
18
18
|
};
|
|
19
19
|
this._handleJoyrideCallback = (data) => {
|
|
20
20
|
const { status } = data;
|
|
21
|
+
const handler = this.props.tours[this.state.index];
|
|
21
22
|
const finishedStatuses = [STATUS.FINISHED, STATUS.SKIPPED];
|
|
22
|
-
|
|
23
|
+
handler.handleTourEvent(data);
|
|
23
24
|
if (finishedStatuses.includes(status)) {
|
|
24
25
|
this.setState({ run: false });
|
|
25
26
|
const newIndex = this.state.index + 1;
|
|
@@ -37,7 +38,18 @@ class Tour extends React.Component {
|
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
render() {
|
|
40
|
-
|
|
41
|
+
var _a;
|
|
42
|
+
const handler = (_a = this.props.tours) === null || _a === void 0 ? void 0 : _a[this.state.index];
|
|
43
|
+
if (!handler) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const options = { ...handler.options };
|
|
47
|
+
delete options.stepIndex;
|
|
48
|
+
const joyride = (stepIndex) => (React.createElement(ReactJoyride, { key: handler.id, ...options, callback: this._handleJoyrideCallback, run: this.state.run, steps: handler.steps, ...(stepIndex === undefined ? {} : { stepIndex }) }));
|
|
49
|
+
if (!handler.controlled) {
|
|
50
|
+
return joyride();
|
|
51
|
+
}
|
|
52
|
+
return (React.createElement(UseSignal, { signal: handler.currentStepIndexChanged, initialArgs: handler.currentStepIndex }, () => joyride(handler.currentStepIndex >= 0 ? handler.currentStepIndex : undefined)));
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
/**
|
package/lib/constants.d.ts
CHANGED
package/lib/defaults.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
-
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
3
|
-
import { ITourManager } from './tokens';
|
|
1
|
+
import type { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import type { INotebookTracker } from '@jupyterlab/notebook';
|
|
3
|
+
import type { ITourManager } from './tokens';
|
|
4
4
|
/**
|
|
5
5
|
* Add all default tours
|
|
6
6
|
*
|
package/lib/notebookButton.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/apputils';
|
|
3
|
-
import { Notebook } from '@jupyterlab/notebook';
|
|
4
|
-
import { TranslationBundle } from '@jupyterlab/translation';
|
|
5
|
-
import { INotebookTourManager } from './tokens';
|
|
3
|
+
import type { Notebook } from '@jupyterlab/notebook';
|
|
4
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
5
|
+
import type { INotebookTourManager } from './tokens';
|
|
6
6
|
export declare class TourButton extends ReactWidget {
|
|
7
7
|
/**
|
|
8
8
|
* Construct a new notebook toolbar item for tours
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ErrorObject } from 'ajv';
|
|
2
|
-
import { Notebook } from '@jupyterlab/notebook';
|
|
3
|
-
import { ISignal } from '@lumino/signaling';
|
|
4
|
-
import { INotebookTourManager, ITourManager } from './tokens';
|
|
1
|
+
import type { ErrorObject, ValidateFunction } from 'ajv';
|
|
2
|
+
import type { Notebook } from '@jupyterlab/notebook';
|
|
3
|
+
import type { ISignal } from '@lumino/signaling';
|
|
4
|
+
import type { INotebookTourManager, ITourManager } from './tokens';
|
|
5
5
|
/**
|
|
6
6
|
* The NotebookTourManager is needed to sync Notebook metadata with the TourManager
|
|
7
7
|
*/
|
|
@@ -24,13 +24,19 @@ export declare class NotebookTourManager implements INotebookTourManager {
|
|
|
24
24
|
* @returns the list of errors
|
|
25
25
|
*/
|
|
26
26
|
getNotebookValidationErrors(notebook: Notebook): ErrorObject[];
|
|
27
|
+
/** A signal that emits when notebook tours changes. */
|
|
27
28
|
get notebookToursChanged(): ISignal<INotebookTourManager, Notebook>;
|
|
28
29
|
private _onNotebookDisposed;
|
|
29
30
|
private _cleanNotebookTours;
|
|
30
31
|
/**
|
|
31
|
-
* The metadata changed, and
|
|
32
|
+
* The metadata changed, and therefore maybe tours: remove all of them, and
|
|
33
|
+
* maybe re-add.
|
|
32
34
|
*/
|
|
33
35
|
private _notebookMetadataChanged;
|
|
36
|
+
/**
|
|
37
|
+
* Tour metadata was found:
|
|
38
|
+
*/
|
|
39
|
+
private _updateFromNotebookMetadata;
|
|
34
40
|
/**
|
|
35
41
|
* Actually create a tour from JSON
|
|
36
42
|
*/
|
|
@@ -38,6 +44,12 @@ export declare class NotebookTourManager implements INotebookTourManager {
|
|
|
38
44
|
private _tourManager;
|
|
39
45
|
private _notebookTours;
|
|
40
46
|
private _notebookToursChanged;
|
|
41
|
-
private _validator;
|
|
42
47
|
private _validationErrors;
|
|
43
48
|
}
|
|
49
|
+
/** A namespace for private values. */
|
|
50
|
+
export declare namespace Private {
|
|
51
|
+
/**
|
|
52
|
+
* Get a singleton pre-compiled validator function.
|
|
53
|
+
*/
|
|
54
|
+
function ensureValidator(): Promise<ValidateFunction>;
|
|
55
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import Ajv from 'ajv';
|
|
2
1
|
import { Signal } from '@lumino/signaling';
|
|
3
|
-
import USER_SCHEMA from '../schema/user-tours.json';
|
|
4
2
|
import { notebookTourIcon } from './icons';
|
|
5
3
|
import { NOTEBOOK_PLUGIN_ID, NS } from './tokens';
|
|
4
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
6
5
|
/**
|
|
7
6
|
* The NotebookTourManager is needed to sync Notebook metadata with the TourManager
|
|
8
7
|
*/
|
|
@@ -12,10 +11,6 @@ export class NotebookTourManager {
|
|
|
12
11
|
this._notebookToursChanged = new Signal(this);
|
|
13
12
|
this._validationErrors = new Map();
|
|
14
13
|
this._tourManager = options.tourManager;
|
|
15
|
-
// `jupyter.lab...` keywords custom keywords rejected by default
|
|
16
|
-
// we may be able to do better than `strict: false` by defining
|
|
17
|
-
// custom keywords https://ajv.js.org/keywords.html
|
|
18
|
-
this._validator = new Ajv({ strict: false }).compile(USER_SCHEMA);
|
|
19
14
|
}
|
|
20
15
|
get tourManager() {
|
|
21
16
|
return this._tourManager;
|
|
@@ -31,11 +26,9 @@ export class NotebookTourManager {
|
|
|
31
26
|
if (!notebook.model) {
|
|
32
27
|
return;
|
|
33
28
|
}
|
|
34
|
-
((_a = notebook.model.metadataChanged) !== null && _a !== void 0 ? _a : notebook.model.metadata.changed).connect(() =>
|
|
35
|
-
this._notebookMetadataChanged(notebook);
|
|
36
|
-
});
|
|
29
|
+
((_a = notebook.model.metadataChanged) !== null && _a !== void 0 ? _a : notebook.model.metadata.changed).connect(async () => await this._notebookMetadataChanged(notebook));
|
|
37
30
|
notebook.disposed.connect(this._onNotebookDisposed, this);
|
|
38
|
-
this._notebookMetadataChanged(notebook);
|
|
31
|
+
await this._notebookMetadataChanged(notebook);
|
|
39
32
|
}
|
|
40
33
|
/**
|
|
41
34
|
* Get the list of full tour ids for this notebook
|
|
@@ -59,6 +52,7 @@ export class NotebookTourManager {
|
|
|
59
52
|
getNotebookValidationErrors(notebook) {
|
|
60
53
|
return this._validationErrors.get(notebook) || [];
|
|
61
54
|
}
|
|
55
|
+
/** A signal that emits when notebook tours changes. */
|
|
62
56
|
get notebookToursChanged() {
|
|
63
57
|
return this._notebookToursChanged;
|
|
64
58
|
}
|
|
@@ -71,10 +65,10 @@ export class NotebookTourManager {
|
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
/**
|
|
74
|
-
* The metadata changed, and
|
|
68
|
+
* The metadata changed, and therefore maybe tours: remove all of them, and
|
|
69
|
+
* maybe re-add.
|
|
75
70
|
*/
|
|
76
|
-
_notebookMetadataChanged(notebook) {
|
|
77
|
-
var _a, _b;
|
|
71
|
+
async _notebookMetadataChanged(notebook) {
|
|
78
72
|
const { model } = notebook;
|
|
79
73
|
const metadata = model
|
|
80
74
|
? model.getMetadata
|
|
@@ -82,34 +76,42 @@ export class NotebookTourManager {
|
|
|
82
76
|
: // @ts-expect-error JLab 3 API
|
|
83
77
|
model.metadata.get(NS)
|
|
84
78
|
: null;
|
|
85
|
-
const trans = this._tourManager.translator;
|
|
86
79
|
this._cleanNotebookTours(notebook);
|
|
87
80
|
this._validationErrors.set(notebook, []);
|
|
88
81
|
if (metadata) {
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
await this._updateFromNotebookMetadata(notebook, metadata);
|
|
83
|
+
}
|
|
84
|
+
this._notebookToursChanged.emit(notebook);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Tour metadata was found:
|
|
88
|
+
*/
|
|
89
|
+
async _updateFromNotebookMetadata(notebook, metadata) {
|
|
90
|
+
var _a, _b;
|
|
91
|
+
const { translator } = this._tourManager;
|
|
92
|
+
const _validator = await Private.ensureValidator();
|
|
93
|
+
_validator(metadata);
|
|
94
|
+
const errors = _validator.errors || [];
|
|
95
|
+
this._validationErrors.set(notebook, errors);
|
|
96
|
+
if (errors.length) {
|
|
97
|
+
console.error(translator.__('Validation errors found: fix them in Advanced Settings'));
|
|
98
|
+
console.table(errors);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const tours = (_a = metadata['tours']) !== null && _a !== void 0 ? _a : [];
|
|
102
|
+
for (const tour of this.tourManager.sortTours(tours)) {
|
|
103
|
+
try {
|
|
104
|
+
this._addNotebookTour(notebook, tour);
|
|
105
|
+
this._tourManager.launch([tour.id], false);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.groupCollapsed(translator.__('Error encountered adding notebook tour %1 (%2)', tour.label, tour.id), error);
|
|
109
|
+
console.table(tour.steps);
|
|
110
|
+
console.log((_b = tour.options) !== null && _b !== void 0 ? _b : {});
|
|
111
|
+
console.groupEnd();
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
|
-
this._notebookToursChanged.emit(notebook);
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* Actually create a tour from JSON
|
|
@@ -122,3 +124,27 @@ export class NotebookTourManager {
|
|
|
122
124
|
});
|
|
123
125
|
}
|
|
124
126
|
}
|
|
127
|
+
/** A namespace for private values. */
|
|
128
|
+
export var Private;
|
|
129
|
+
(function (Private) {
|
|
130
|
+
let _validator = null;
|
|
131
|
+
let _loading = null;
|
|
132
|
+
/**
|
|
133
|
+
* Get a singleton pre-compiled validator function.
|
|
134
|
+
*/
|
|
135
|
+
async function ensureValidator() {
|
|
136
|
+
if (!_loading) {
|
|
137
|
+
_loading = new PromiseDelegate();
|
|
138
|
+
const { Ajv } = await import('ajv');
|
|
139
|
+
const schema = await import('../schema/user-tours.json');
|
|
140
|
+
// `jupyter.lab...` keywords custom keywords rejected by default
|
|
141
|
+
// we may be able to do better than `strict: false` by defining
|
|
142
|
+
// custom keywords https://ajv.js.org/keywords.html
|
|
143
|
+
const ajv = new Ajv({ strict: false });
|
|
144
|
+
_validator = ajv.compile(schema);
|
|
145
|
+
_loading.resolve(_validator);
|
|
146
|
+
}
|
|
147
|
+
return _loading.promise;
|
|
148
|
+
}
|
|
149
|
+
Private.ensureValidator = ensureValidator;
|
|
150
|
+
})(Private || (Private = {}));
|
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-tour",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A JupyterLab UI Tour.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
34
|
-
"build:labextension": "jupyter
|
|
35
|
-
"build:labextension:dev": "jupyter
|
|
34
|
+
"build:labextension": "jupyter-builder build --debug .",
|
|
35
|
+
"build:labextension:dev": "jupyter-builder build --debug --development True .",
|
|
36
36
|
"build:lib": "jlpm build:lib:prod --sourceMap",
|
|
37
37
|
"build:lib:prod": "tsc -b src",
|
|
38
38
|
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"clean:labextension": "rimraf jupyterlab_tour/labextension jupyterlab_tour/_version.py",
|
|
42
42
|
"clean:lib": "rimraf lib build/.cache/tsc",
|
|
43
43
|
"clean:lintcache": "rimraf ./build/.cache/eslint ./build/.cache/stylelint",
|
|
44
|
-
"dedupe": "yarn
|
|
44
|
+
"dedupe": "yarn dedupe --check",
|
|
45
45
|
"eslint": "jlpm eslint:check --fix",
|
|
46
46
|
"eslint:check": "eslint . --cache --cache-location=./build/.cache/eslint --ext .ts,.tsx",
|
|
47
47
|
"install:extension": "jlpm build",
|
|
@@ -54,32 +54,32 @@
|
|
|
54
54
|
"stylelint:check": "stylelint --cache --cache-location=./build/.cache/stylelint \"style/**/*.css\"",
|
|
55
55
|
"test": "jest --coverage",
|
|
56
56
|
"watch": "run-p watch:src watch:labextension",
|
|
57
|
-
"watch:labextension": "jupyter
|
|
57
|
+
"watch:labextension": "jupyter-builder watch .",
|
|
58
58
|
"watch:src": "jlpm build:lib -w"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@jupyterlab/application": "^4.
|
|
62
|
-
"@jupyterlab/apputils": "^4.
|
|
63
|
-
"@jupyterlab/mainmenu": "^4.
|
|
64
|
-
"@jupyterlab/notebook": "^4.
|
|
65
|
-
"@jupyterlab/services": "^7.
|
|
66
|
-
"@jupyterlab/settingregistry": "^4.
|
|
67
|
-
"@jupyterlab/statedb": "^4.
|
|
68
|
-
"@jupyterlab/translation": "^4.
|
|
69
|
-
"@jupyterlab/ui-components": "^4.
|
|
61
|
+
"@jupyterlab/application": "^4.3.0",
|
|
62
|
+
"@jupyterlab/apputils": "^4.3.0",
|
|
63
|
+
"@jupyterlab/mainmenu": "^4.3.0",
|
|
64
|
+
"@jupyterlab/notebook": "^4.3.0",
|
|
65
|
+
"@jupyterlab/services": "^7.3.0",
|
|
66
|
+
"@jupyterlab/settingregistry": "^4.3.0",
|
|
67
|
+
"@jupyterlab/statedb": "^4.3.0",
|
|
68
|
+
"@jupyterlab/translation": "^4.3.0",
|
|
69
|
+
"@jupyterlab/ui-components": "^4.3.0",
|
|
70
70
|
"@lumino/coreutils": "^2.1.1",
|
|
71
71
|
"@lumino/disposable": "^2.1.1",
|
|
72
72
|
"@lumino/signaling": "^2.1.1",
|
|
73
|
-
"@lumino/widgets": "^2.
|
|
74
|
-
"ajv": "^8.
|
|
73
|
+
"@lumino/widgets": "^2.3.1",
|
|
74
|
+
"ajv": "^8.17.1",
|
|
75
75
|
"react": "^18.2.0",
|
|
76
|
-
"react-joyride": "^2.
|
|
76
|
+
"react-joyride": "^2.9.3"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@babel/core": "^7.20.12",
|
|
80
80
|
"@babel/preset-env": "^7.20.2",
|
|
81
|
-
"@
|
|
82
|
-
"@jupyterlab/testutils": "^4.
|
|
81
|
+
"@jupyter/builder": "^1.2.1",
|
|
82
|
+
"@jupyterlab/testutils": "^4.3.0",
|
|
83
83
|
"@types/jest": "^29.2.0",
|
|
84
84
|
"@types/json-schema": "^7.0.11",
|
|
85
85
|
"@types/react": "^18.0.26",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
94
94
|
"jest": "^29.2.0",
|
|
95
95
|
"npm-run-all": "^4.1.5",
|
|
96
|
-
"prettier": "^3.
|
|
97
|
-
"rimraf": "^
|
|
96
|
+
"prettier": "^3.6.2",
|
|
97
|
+
"rimraf": "^6.1.2",
|
|
98
98
|
"source-map-loader": "^1.0.2",
|
|
99
99
|
"style-loader": "^3.3.1",
|
|
100
100
|
"stylelint": "^15.10.1",
|
|
@@ -102,7 +102,6 @@
|
|
|
102
102
|
"stylelint-config-standard": "^34.0.0",
|
|
103
103
|
"ts-jest": "^29.1.1",
|
|
104
104
|
"typescript": "~5.0.4",
|
|
105
|
-
"yarn-berry-deduplicate": "^6.1.1",
|
|
106
105
|
"yjs": "^13.5.40"
|
|
107
106
|
},
|
|
108
107
|
"sideEffects": [
|
|
@@ -110,14 +109,25 @@
|
|
|
110
109
|
"style/index.js"
|
|
111
110
|
],
|
|
112
111
|
"resolutions": {
|
|
113
|
-
"@types/node": "^
|
|
112
|
+
"@types/node": "^22.10.1",
|
|
114
113
|
"@types/react": "^18.0.26",
|
|
115
|
-
"react": "^18.2.0"
|
|
114
|
+
"react": "^18.2.0",
|
|
115
|
+
"yjs": "^13.5.40"
|
|
116
116
|
},
|
|
117
117
|
"jupyterlab": {
|
|
118
118
|
"extension": true,
|
|
119
119
|
"schemaDir": "schema",
|
|
120
|
-
"outputDir": "jupyterlab_tour/labextension"
|
|
120
|
+
"outputDir": "jupyterlab_tour/labextension",
|
|
121
|
+
"sharedPackages": {
|
|
122
|
+
"ajv": {
|
|
123
|
+
"bundled": true,
|
|
124
|
+
"singleton": false
|
|
125
|
+
},
|
|
126
|
+
"react-joyride": {
|
|
127
|
+
"bundled": true,
|
|
128
|
+
"singleton": false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
121
131
|
},
|
|
122
132
|
"styleModule": "style/index.js",
|
|
123
133
|
"eslintIgnore": [
|
|
@@ -165,6 +175,13 @@
|
|
|
165
175
|
"args": "none"
|
|
166
176
|
}
|
|
167
177
|
],
|
|
178
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
179
|
+
"error",
|
|
180
|
+
{
|
|
181
|
+
"prefer": "type-imports",
|
|
182
|
+
"fixStyle": "separate-type-imports"
|
|
183
|
+
}
|
|
184
|
+
],
|
|
168
185
|
"@typescript-eslint/no-explicit-any": "off",
|
|
169
186
|
"@typescript-eslint/no-namespace": "off",
|
|
170
187
|
"@typescript-eslint/no-use-before-define": "off",
|
|
@@ -197,6 +214,13 @@
|
|
|
197
214
|
"options": {
|
|
198
215
|
"tabWidth": 4
|
|
199
216
|
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"files": ".github/workflows/*.yml",
|
|
220
|
+
"options": {
|
|
221
|
+
"proseWrap": "never",
|
|
222
|
+
"printWidth": 120
|
|
223
|
+
}
|
|
200
224
|
}
|
|
201
225
|
]
|
|
202
226
|
},
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
1
|
+
import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
import { INotebookTourManager, ITourManager, IUserTourManager } from './tokens';
|
|
3
|
-
declare const _default: (JupyterFrontEndPlugin<
|
|
3
|
+
declare const _default: (JupyterFrontEndPlugin<ITourManager> | JupyterFrontEndPlugin<IUserTourManager> | JupyterFrontEndPlugin<INotebookTourManager> | JupyterFrontEndPlugin<void>)[];
|
|
4
4
|
export default _default;
|
package/lib/plugin.js
CHANGED
|
@@ -28,11 +28,12 @@ const corePlugin = {
|
|
|
28
28
|
};
|
|
29
29
|
function activate(app, palette, menu, translator) {
|
|
30
30
|
const CONFIG_SECTION_NAME = corePlugin.id.replace(/[^\w]/g, '');
|
|
31
|
-
const { commands } = app;
|
|
31
|
+
const { commands, serviceManager } = app;
|
|
32
32
|
translator = translator !== null && translator !== void 0 ? translator : nullTranslator;
|
|
33
33
|
const restoreState = new PromiseDelegate();
|
|
34
34
|
const configReady = ConfigSection.create({
|
|
35
|
-
name: CONFIG_SECTION_NAME
|
|
35
|
+
name: CONFIG_SECTION_NAME,
|
|
36
|
+
serverSettings: serviceManager.serverSettings
|
|
36
37
|
}).catch(error => {
|
|
37
38
|
console.error('Failed to fetch state for jupyterlab-tour.', error);
|
|
38
39
|
});
|
|
@@ -162,10 +163,13 @@ function activateDefaults(app, tourManager, nbTracker) {
|
|
|
162
163
|
if (nbTracker &&
|
|
163
164
|
(app.name !== 'Jupyter Notebook' ||
|
|
164
165
|
window.location.pathname.match(/\/notebooks\/.+$/))) {
|
|
166
|
+
let hasOfferedNotebookTour = false;
|
|
165
167
|
nbTracker.widgetAdded.connect(() => {
|
|
166
|
-
if (tourManager.tours.has(NOTEBOOK_ID)) {
|
|
167
|
-
|
|
168
|
+
if (hasOfferedNotebookTour || !tourManager.tours.has(NOTEBOOK_ID)) {
|
|
169
|
+
return;
|
|
168
170
|
}
|
|
171
|
+
hasOfferedNotebookTour = true;
|
|
172
|
+
void tourManager.launch([NOTEBOOK_ID], false);
|
|
169
173
|
});
|
|
170
174
|
}
|
|
171
175
|
Promise.all([app.restored, tourManager.ready]).then(() => {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"properties": {
|
|
12
12
|
"tours": {
|
|
13
13
|
"type": "array",
|
|
14
|
-
"description": "An array of
|
|
14
|
+
"description": "An array of tours. Each requires an `id`, `label` and `steps[]`, and may have `options`. For `steps` and `options`, see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md",
|
|
15
15
|
"default": [],
|
|
16
16
|
"items": {
|
|
17
17
|
"type": "object",
|
package/lib/tokens.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { ErrorObject } from 'ajv';
|
|
2
|
-
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
3
|
-
import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
|
|
4
|
-
import { Notebook } from '@jupyterlab/notebook';
|
|
1
|
+
import type { ErrorObject } from 'ajv';
|
|
2
|
+
import type { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
3
|
+
import type { ITranslator, TranslationBundle } from '@jupyterlab/translation';
|
|
4
|
+
import type { Notebook } from '@jupyterlab/notebook';
|
|
5
5
|
import { Token } from '@lumino/coreutils';
|
|
6
|
-
import { IDisposable } from '@lumino/disposable';
|
|
7
|
-
import { ISignal } from '@lumino/signaling';
|
|
8
|
-
import { LabIcon, RankedMenu } from '@jupyterlab/ui-components';
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { CallBackProps, Placement, Props as JoyrideProps, Step } from 'react-joyride';
|
|
6
|
+
import type { IDisposable } from '@lumino/disposable';
|
|
7
|
+
import type { ISignal } from '@lumino/signaling';
|
|
8
|
+
import type { LabIcon, RankedMenu } from '@jupyterlab/ui-components';
|
|
9
|
+
import type React from 'react';
|
|
10
|
+
import type { CallBackProps, Placement, Props as JoyrideProps, Step } from 'react-joyride';
|
|
11
11
|
/**
|
|
12
12
|
* Namespace for everything
|
|
13
13
|
*/
|
|
@@ -93,7 +93,7 @@ export interface ITour {
|
|
|
93
93
|
/**
|
|
94
94
|
* Tour options
|
|
95
95
|
*
|
|
96
|
-
* @see https://
|
|
96
|
+
* @see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/props.md
|
|
97
97
|
*
|
|
98
98
|
* #### Notes
|
|
99
99
|
*
|
|
@@ -113,6 +113,11 @@ export interface ITour {
|
|
|
113
113
|
* Translation domain for this tour
|
|
114
114
|
*/
|
|
115
115
|
translation?: string;
|
|
116
|
+
/**
|
|
117
|
+
* When true, the tour step is driven by {@link ITourHandler.currentStepIndex}.
|
|
118
|
+
* Default is false, which leaves Joyride uncontrolled.
|
|
119
|
+
*/
|
|
120
|
+
controlled?: boolean;
|
|
116
121
|
}
|
|
117
122
|
/**
|
|
118
123
|
* Tour handler interface
|
|
@@ -137,8 +142,9 @@ export interface ITourHandler extends IDisposable {
|
|
|
137
142
|
createAndAddStep(target: string, content: React.ReactNode, placement?: StepPlacement, title?: string): Step;
|
|
138
143
|
/**
|
|
139
144
|
* The index of the current step of the tour. Returns -1 if tour isn't active.
|
|
145
|
+
* Setting this moves the tour only when {@link controlled} is true.
|
|
140
146
|
*/
|
|
141
|
-
|
|
147
|
+
currentStepIndex: number;
|
|
142
148
|
/**
|
|
143
149
|
* Is the tour running?
|
|
144
150
|
*/
|
|
@@ -191,6 +197,15 @@ export interface ITourHandler extends IDisposable {
|
|
|
191
197
|
* A signal emitted when the tour step has changed.
|
|
192
198
|
*/
|
|
193
199
|
readonly stepChanged: ISignal<this, CallBackProps>;
|
|
200
|
+
/**
|
|
201
|
+
* When true, Joyride is controlled by {@link currentStepIndex}.
|
|
202
|
+
* When false, Joyride advances on its own.
|
|
203
|
+
*/
|
|
204
|
+
readonly controlled: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* A signal emitted when {@link currentStepIndex} changes.
|
|
207
|
+
*/
|
|
208
|
+
readonly currentStepIndexChanged: ISignal<this, number>;
|
|
194
209
|
/**
|
|
195
210
|
* The array of steps the tour currently contains. Each step will be followed
|
|
196
211
|
* in order as the tour progresses.
|
package/lib/tour.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { ISignal } from '@lumino/signaling';
|
|
3
|
-
import { LabIcon } from '@jupyterlab/ui-components';
|
|
4
|
-
import { CallBackProps, Props as JoyrideProps, Step } from 'react-joyride';
|
|
5
|
-
import { ITourHandler, StepPlacement } from './tokens';
|
|
2
|
+
import type { ISignal } from '@lumino/signaling';
|
|
3
|
+
import type { LabIcon } from '@jupyterlab/ui-components';
|
|
4
|
+
import type { CallBackProps, Props as JoyrideProps, Step } from 'react-joyride';
|
|
5
|
+
import type { ITourHandler, StepPlacement } from './tokens';
|
|
6
6
|
export declare class TourHandler implements ITourHandler {
|
|
7
|
-
constructor(id: string, label: string, options?: Omit<JoyrideProps, 'steps'>, icon?: LabIcon | null, version?: number);
|
|
7
|
+
constructor(id: string, label: string, options?: Omit<JoyrideProps, 'steps'>, icon?: LabIcon | null, version?: number, controlled?: boolean);
|
|
8
8
|
/**
|
|
9
9
|
* The index of the current step of the tour. Returns -1 if tour isn't active.
|
|
10
|
+
* Setting this moves the tour only when {@link controlled} is true.
|
|
10
11
|
*/
|
|
11
12
|
get currentStepIndex(): number;
|
|
13
|
+
set currentStepIndex(index: number);
|
|
12
14
|
/**
|
|
13
15
|
* A signal emitted when all the steps of the tour have been seen and tour is finished.
|
|
14
16
|
*/
|
|
@@ -51,6 +53,14 @@ export declare class TourHandler implements ITourHandler {
|
|
|
51
53
|
* A signal emitted when the tour step has changed.
|
|
52
54
|
*/
|
|
53
55
|
get stepChanged(): ISignal<this, CallBackProps>;
|
|
56
|
+
/**
|
|
57
|
+
* When true, Joyride is controlled by {@link currentStepIndex}.
|
|
58
|
+
*/
|
|
59
|
+
get controlled(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* A signal emitted when {@link currentStepIndex} changes.
|
|
62
|
+
*/
|
|
63
|
+
get currentStepIndexChanged(): ISignal<this, number>;
|
|
54
64
|
/**
|
|
55
65
|
* The array of steps the tour currently contains. Each step will be followed
|
|
56
66
|
* in order as the tour progresses.
|
|
@@ -114,14 +124,15 @@ export declare class TourHandler implements ITourHandler {
|
|
|
114
124
|
private _finished;
|
|
115
125
|
private _started;
|
|
116
126
|
private _stepChanged;
|
|
127
|
+
private _currentStepIndexChanged;
|
|
117
128
|
private _currentStepIndex;
|
|
118
129
|
private _id;
|
|
119
130
|
private _isDisposed;
|
|
120
131
|
private _label;
|
|
121
132
|
private _options;
|
|
122
133
|
private _previousStatus;
|
|
123
|
-
private _previousStepIndex;
|
|
124
134
|
private _steps;
|
|
125
135
|
private _icon;
|
|
126
136
|
private _version;
|
|
137
|
+
private _controlled;
|
|
127
138
|
}
|
package/lib/tour.js
CHANGED
|
@@ -5,7 +5,7 @@ import { TutorialDefaultOptions } from './constants';
|
|
|
5
5
|
import { tourIcon } from './icons';
|
|
6
6
|
// TODO should be IDisposable !! handling signal connection clearance
|
|
7
7
|
export class TourHandler {
|
|
8
|
-
constructor(id, label, options, icon = null, version = -1) {
|
|
8
|
+
constructor(id, label, options, icon = null, version = -1, controlled = false) {
|
|
9
9
|
/**
|
|
10
10
|
* Handle react-joyride callbacks
|
|
11
11
|
*
|
|
@@ -19,7 +19,7 @@ export class TourHandler {
|
|
|
19
19
|
// Handle status changes when they occur
|
|
20
20
|
if (status !== this._previousStatus) {
|
|
21
21
|
this._previousStatus = status;
|
|
22
|
-
this.
|
|
22
|
+
this.currentStepIndex = -1;
|
|
23
23
|
if (status === STATUS.FINISHED) {
|
|
24
24
|
this._finished.emit(data);
|
|
25
25
|
}
|
|
@@ -27,7 +27,7 @@ export class TourHandler {
|
|
|
27
27
|
this._skipped.emit(data);
|
|
28
28
|
}
|
|
29
29
|
else if (status === STATUS.RUNNING) {
|
|
30
|
-
this.
|
|
30
|
+
this.currentStepIndex = 0;
|
|
31
31
|
this._started.emit(data);
|
|
32
32
|
}
|
|
33
33
|
else if (status === STATUS.ERROR) {
|
|
@@ -36,9 +36,8 @@ export class TourHandler {
|
|
|
36
36
|
}
|
|
37
37
|
// Emit step change event
|
|
38
38
|
if (status === STATUS.RUNNING) {
|
|
39
|
-
if (
|
|
40
|
-
this.
|
|
41
|
-
this._currentStepIndex = data.index;
|
|
39
|
+
if (!this._controlled) {
|
|
40
|
+
this.currentStepIndex = index;
|
|
42
41
|
}
|
|
43
42
|
this._stepChanged.emit(data);
|
|
44
43
|
}
|
|
@@ -47,15 +46,17 @@ export class TourHandler {
|
|
|
47
46
|
this._finished = new Signal(this);
|
|
48
47
|
this._started = new Signal(this);
|
|
49
48
|
this._stepChanged = new Signal(this);
|
|
49
|
+
this._currentStepIndexChanged = new Signal(this);
|
|
50
50
|
this._currentStepIndex = -1;
|
|
51
51
|
this._isDisposed = false;
|
|
52
52
|
this._previousStatus = STATUS.READY;
|
|
53
|
-
this._previousStepIndex = -1;
|
|
54
53
|
this._steps = new Array();
|
|
54
|
+
this._controlled = false;
|
|
55
55
|
this._label = label;
|
|
56
56
|
this._id = id;
|
|
57
57
|
this._icon = icon || null;
|
|
58
58
|
this._version = version;
|
|
59
|
+
this._controlled = controlled;
|
|
59
60
|
const { styles, ...others } = options !== null && options !== void 0 ? options : {};
|
|
60
61
|
this._options = { ...TutorialDefaultOptions, ...others };
|
|
61
62
|
if (!this._options.styles) {
|
|
@@ -68,10 +69,18 @@ export class TourHandler {
|
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* The index of the current step of the tour. Returns -1 if tour isn't active.
|
|
72
|
+
* Setting this moves the tour only when {@link controlled} is true.
|
|
71
73
|
*/
|
|
72
74
|
get currentStepIndex() {
|
|
73
75
|
return this._currentStepIndex;
|
|
74
76
|
}
|
|
77
|
+
set currentStepIndex(index) {
|
|
78
|
+
if (index === this._currentStepIndex) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this._currentStepIndex = index;
|
|
82
|
+
this._currentStepIndexChanged.emit(index);
|
|
83
|
+
}
|
|
75
84
|
/**
|
|
76
85
|
* A signal emitted when all the steps of the tour have been seen and tour is finished.
|
|
77
86
|
*/
|
|
@@ -136,6 +145,18 @@ export class TourHandler {
|
|
|
136
145
|
get stepChanged() {
|
|
137
146
|
return this._stepChanged;
|
|
138
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* When true, Joyride is controlled by {@link currentStepIndex}.
|
|
150
|
+
*/
|
|
151
|
+
get controlled() {
|
|
152
|
+
return this._controlled;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A signal emitted when {@link currentStepIndex} changes.
|
|
156
|
+
*/
|
|
157
|
+
get currentStepIndexChanged() {
|
|
158
|
+
return this._currentStepIndexChanged;
|
|
159
|
+
}
|
|
139
160
|
/**
|
|
140
161
|
* The array of steps the tour currently contains. Each step will be followed
|
|
141
162
|
* in order as the tour progresses.
|
package/lib/tourManager.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ISignal } from '@lumino/signaling';
|
|
2
|
-
import { TranslationBundle } from '@jupyterlab/translation';
|
|
1
|
+
import type { ISignal } from '@lumino/signaling';
|
|
2
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
3
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
4
|
-
import { ITour, ITourHandler, ITourManager, ITourManagerOptions } from './tokens';
|
|
4
|
+
import type { ITour, ITourHandler, ITourManager, ITourManagerOptions } from './tokens';
|
|
5
5
|
import { TourHandler } from './tour';
|
|
6
6
|
/**
|
|
7
7
|
* The TourManager is needed to manage creation, removal and launching of Tutorials
|
package/lib/tourManager.js
CHANGED
|
@@ -24,12 +24,13 @@ export class TourManager {
|
|
|
24
24
|
* The tour label will not be translated.
|
|
25
25
|
*/
|
|
26
26
|
this.createTour = (args) => {
|
|
27
|
-
var _a, _b, _c, _d;
|
|
27
|
+
var _a, _b, _c, _d, _e;
|
|
28
28
|
const { id, label } = args;
|
|
29
29
|
const addToHelpMenu = (_a = args.hasHelpEntry) !== null && _a !== void 0 ? _a : true;
|
|
30
30
|
const options = (_b = args.options) !== null && _b !== void 0 ? _b : {};
|
|
31
31
|
const icon = (_c = args.icon) !== null && _c !== void 0 ? _c : null;
|
|
32
32
|
const version = (_d = args.version) !== null && _d !== void 0 ? _d : 0;
|
|
33
|
+
const controlled = (_e = args.controlled) !== null && _e !== void 0 ? _e : false;
|
|
33
34
|
if (this._tours.has(id)) {
|
|
34
35
|
throw new Error(this._trans.__("Error creating new tour. TourHandler id's must be unique.\nTutorial with the id: '%1' already exists.", id));
|
|
35
36
|
}
|
|
@@ -38,7 +39,7 @@ export class TourManager {
|
|
|
38
39
|
options.locale = this._locale;
|
|
39
40
|
}
|
|
40
41
|
// Create tour and add it to help menu if needed
|
|
41
|
-
const newTutorial = new TourHandler(id, label, options, icon, version);
|
|
42
|
+
const newTutorial = new TourHandler(id, label, options, icon, version, controlled);
|
|
42
43
|
if (this._menu && addToHelpMenu) {
|
|
43
44
|
const options = {
|
|
44
45
|
args: {
|
|
@@ -173,7 +174,8 @@ export class TourManager {
|
|
|
173
174
|
hasHelpEntry: tour.hasHelpEntry === false ? false : true,
|
|
174
175
|
options: tour.options,
|
|
175
176
|
icon: tour.icon ? LabIcon.resolve({ icon: tour.icon }) : undefined,
|
|
176
|
-
version: tour.version
|
|
177
|
+
version: tour.version,
|
|
178
|
+
controlled: tour.controlled
|
|
177
179
|
});
|
|
178
180
|
tour.steps.forEach(step => {
|
|
179
181
|
const translatedStep = { ...step };
|
|
@@ -229,6 +231,11 @@ export class TourManager {
|
|
|
229
231
|
: !this._state.map(s => s.id).includes(tour.id));
|
|
230
232
|
}
|
|
231
233
|
const startTours = () => {
|
|
234
|
+
tourList.forEach(tour => {
|
|
235
|
+
if (tour.controlled) {
|
|
236
|
+
tour.currentStepIndex = 0;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
232
239
|
this._activeTours = tourList;
|
|
233
240
|
this._tourLaunched.emit(tourList);
|
|
234
241
|
};
|
package/lib/userTourManager.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-tour",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A JupyterLab UI Tour.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
34
|
-
"build:labextension": "jupyter
|
|
35
|
-
"build:labextension:dev": "jupyter
|
|
34
|
+
"build:labextension": "jupyter-builder build --debug .",
|
|
35
|
+
"build:labextension:dev": "jupyter-builder build --debug --development True .",
|
|
36
36
|
"build:lib": "jlpm build:lib:prod --sourceMap",
|
|
37
37
|
"build:lib:prod": "tsc -b src",
|
|
38
38
|
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"clean:labextension": "rimraf jupyterlab_tour/labextension jupyterlab_tour/_version.py",
|
|
42
42
|
"clean:lib": "rimraf lib build/.cache/tsc",
|
|
43
43
|
"clean:lintcache": "rimraf ./build/.cache/eslint ./build/.cache/stylelint",
|
|
44
|
-
"dedupe": "yarn
|
|
44
|
+
"dedupe": "yarn dedupe --check",
|
|
45
45
|
"eslint": "jlpm eslint:check --fix",
|
|
46
46
|
"eslint:check": "eslint . --cache --cache-location=./build/.cache/eslint --ext .ts,.tsx",
|
|
47
47
|
"install:extension": "jlpm build",
|
|
@@ -54,32 +54,32 @@
|
|
|
54
54
|
"stylelint:check": "stylelint --cache --cache-location=./build/.cache/stylelint \"style/**/*.css\"",
|
|
55
55
|
"test": "jest --coverage",
|
|
56
56
|
"watch": "run-p watch:src watch:labextension",
|
|
57
|
-
"watch:labextension": "jupyter
|
|
57
|
+
"watch:labextension": "jupyter-builder watch .",
|
|
58
58
|
"watch:src": "jlpm build:lib -w"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@jupyterlab/application": "^4.
|
|
62
|
-
"@jupyterlab/apputils": "^4.
|
|
63
|
-
"@jupyterlab/mainmenu": "^4.
|
|
64
|
-
"@jupyterlab/notebook": "^4.
|
|
65
|
-
"@jupyterlab/services": "^7.
|
|
66
|
-
"@jupyterlab/settingregistry": "^4.
|
|
67
|
-
"@jupyterlab/statedb": "^4.
|
|
68
|
-
"@jupyterlab/translation": "^4.
|
|
69
|
-
"@jupyterlab/ui-components": "^4.
|
|
61
|
+
"@jupyterlab/application": "^4.3.0",
|
|
62
|
+
"@jupyterlab/apputils": "^4.3.0",
|
|
63
|
+
"@jupyterlab/mainmenu": "^4.3.0",
|
|
64
|
+
"@jupyterlab/notebook": "^4.3.0",
|
|
65
|
+
"@jupyterlab/services": "^7.3.0",
|
|
66
|
+
"@jupyterlab/settingregistry": "^4.3.0",
|
|
67
|
+
"@jupyterlab/statedb": "^4.3.0",
|
|
68
|
+
"@jupyterlab/translation": "^4.3.0",
|
|
69
|
+
"@jupyterlab/ui-components": "^4.3.0",
|
|
70
70
|
"@lumino/coreutils": "^2.1.1",
|
|
71
71
|
"@lumino/disposable": "^2.1.1",
|
|
72
72
|
"@lumino/signaling": "^2.1.1",
|
|
73
|
-
"@lumino/widgets": "^2.
|
|
74
|
-
"ajv": "^8.
|
|
73
|
+
"@lumino/widgets": "^2.3.1",
|
|
74
|
+
"ajv": "^8.17.1",
|
|
75
75
|
"react": "^18.2.0",
|
|
76
|
-
"react-joyride": "^2.
|
|
76
|
+
"react-joyride": "^2.9.3"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@babel/core": "^7.20.12",
|
|
80
80
|
"@babel/preset-env": "^7.20.2",
|
|
81
|
-
"@
|
|
82
|
-
"@jupyterlab/testutils": "^4.
|
|
81
|
+
"@jupyter/builder": "^1.2.1",
|
|
82
|
+
"@jupyterlab/testutils": "^4.3.0",
|
|
83
83
|
"@types/jest": "^29.2.0",
|
|
84
84
|
"@types/json-schema": "^7.0.11",
|
|
85
85
|
"@types/react": "^18.0.26",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
94
94
|
"jest": "^29.2.0",
|
|
95
95
|
"npm-run-all": "^4.1.5",
|
|
96
|
-
"prettier": "^3.
|
|
97
|
-
"rimraf": "^
|
|
96
|
+
"prettier": "^3.6.2",
|
|
97
|
+
"rimraf": "^6.1.2",
|
|
98
98
|
"source-map-loader": "^1.0.2",
|
|
99
99
|
"style-loader": "^3.3.1",
|
|
100
100
|
"stylelint": "^15.10.1",
|
|
@@ -102,7 +102,6 @@
|
|
|
102
102
|
"stylelint-config-standard": "^34.0.0",
|
|
103
103
|
"ts-jest": "^29.1.1",
|
|
104
104
|
"typescript": "~5.0.4",
|
|
105
|
-
"yarn-berry-deduplicate": "^6.1.1",
|
|
106
105
|
"yjs": "^13.5.40"
|
|
107
106
|
},
|
|
108
107
|
"sideEffects": [
|
|
@@ -110,14 +109,25 @@
|
|
|
110
109
|
"style/index.js"
|
|
111
110
|
],
|
|
112
111
|
"resolutions": {
|
|
113
|
-
"@types/node": "^
|
|
112
|
+
"@types/node": "^22.10.1",
|
|
114
113
|
"@types/react": "^18.0.26",
|
|
115
|
-
"react": "^18.2.0"
|
|
114
|
+
"react": "^18.2.0",
|
|
115
|
+
"yjs": "^13.5.40"
|
|
116
116
|
},
|
|
117
117
|
"jupyterlab": {
|
|
118
118
|
"extension": true,
|
|
119
119
|
"schemaDir": "schema",
|
|
120
|
-
"outputDir": "jupyterlab_tour/labextension"
|
|
120
|
+
"outputDir": "jupyterlab_tour/labextension",
|
|
121
|
+
"sharedPackages": {
|
|
122
|
+
"ajv": {
|
|
123
|
+
"bundled": true,
|
|
124
|
+
"singleton": false
|
|
125
|
+
},
|
|
126
|
+
"react-joyride": {
|
|
127
|
+
"bundled": true,
|
|
128
|
+
"singleton": false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
121
131
|
},
|
|
122
132
|
"styleModule": "style/index.js",
|
|
123
133
|
"eslintIgnore": [
|
|
@@ -165,6 +175,13 @@
|
|
|
165
175
|
"args": "none"
|
|
166
176
|
}
|
|
167
177
|
],
|
|
178
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
179
|
+
"error",
|
|
180
|
+
{
|
|
181
|
+
"prefer": "type-imports",
|
|
182
|
+
"fixStyle": "separate-type-imports"
|
|
183
|
+
}
|
|
184
|
+
],
|
|
168
185
|
"@typescript-eslint/no-explicit-any": "off",
|
|
169
186
|
"@typescript-eslint/no-namespace": "off",
|
|
170
187
|
"@typescript-eslint/no-use-before-define": "off",
|
|
@@ -197,6 +214,13 @@
|
|
|
197
214
|
"options": {
|
|
198
215
|
"tabWidth": 4
|
|
199
216
|
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"files": ".github/workflows/*.yml",
|
|
220
|
+
"options": {
|
|
221
|
+
"proseWrap": "never",
|
|
222
|
+
"printWidth": 120
|
|
223
|
+
}
|
|
200
224
|
}
|
|
201
225
|
]
|
|
202
226
|
},
|
package/schema/user-tours.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"properties": {
|
|
12
12
|
"tours": {
|
|
13
13
|
"type": "array",
|
|
14
|
-
"description": "An array of
|
|
14
|
+
"description": "An array of tours. Each requires an `id`, `label` and `steps[]`, and may have `options`. For `steps` and `options`, see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md",
|
|
15
15
|
"default": [],
|
|
16
16
|
"items": {
|
|
17
17
|
"type": "object",
|