jupyterlab-tour 3.1.3 → 4.0.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
@@ -1,17 +1,20 @@
1
1
  # jupyterlab-tour
2
2
 
3
+ [![Extension status](https://img.shields.io/badge/status-ready-success 'ready to be used')](https://jupyterlab-contrib.github.io/)
3
4
  [![Github Actions Status](https://github.com/jupyterlab-contrib/jupyterlab-tour/workflows/Build/badge.svg)](https://github.com/jupyterlab-contrib/jupyterlab-tour/actions?query=workflow%3ABuild)
4
- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-tour.git/master?urlpath=lab)
5
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-tour/main?urlpath=lab)
5
6
  [![npm](https://img.shields.io/npm/v/jupyterlab-tour)](https://www.npmjs.com/package/jupyterlab-tour)
6
7
  [![PyPI](https://img.shields.io/pypi/v/jupyterlab-tour)](https://pypi.org/project/jupyterlab-tour)
7
8
  [![conda-forge](https://img.shields.io/conda/vn/conda-forge/jupyterlab-tour)](https://anaconda.org/conda-forge/jupyterlab-tour)
8
9
 
9
10
  A JupyterLab UI Tour based on [react-joyride](https://docs.react-joyride.com).
10
11
 
11
- ![demo](https://raw.githubusercontent.com/jupyterlab-contrib/jupyterlab-tour/master/doc/tourDemo.gif)
12
+ ![demo](https://raw.githubusercontent.com/jupyterlab-contrib/jupyterlab-tour/main/doc/tourDemo.gif)
12
13
 
13
14
  ## Install
14
15
 
16
+ To install the extension, execute:
17
+
15
18
  ```bash
16
19
  pip install jupyterlab-tour
17
20
  ```
@@ -30,31 +33,38 @@ This extension has the following features:
30
33
  - Welcome tour
31
34
  - Notebook tour
32
35
  - User-defined features in Settings
33
- - Toast proposing to start a tour - to experienced users the need to exit each time the tour.
34
- - If a tour has already be seen by the user, this is saved in the state database. So you can start tour on event only if the user have not seen it; e.g. the welcome tour is launched at JupyterLab start except if the user have seen it.
36
+ - Toast proposing to start a Tour
37
+ - If a Tour has already be seen by the user, this is saved in the state database. So you
38
+ can start Tour on event only if the user have not seen it; e.g. the _Welcome Tour_ is
39
+ launched at JupyterLab start except if the user have seen it.
35
40
 
36
41
  > The state is cleaned if this extension is updated
37
42
 
38
43
  - Tooltip are styled using JupyterLab theming system
39
44
  - Commands to _add_ and _launch_ tours
40
- - Through the tour manager (`ITourManager` extension token), add, modify or delete a tour
41
- - Connect to tour events through signals
42
- - Override the default style (and options) for the tour separately
45
+ - Through the _Tour Manager_ (`ITourManager` extension token), add, modify or delete a
46
+ Tour
47
+ - Connect to Tour events through signals
48
+ - Override the default style (and options) for the Tour separately
43
49
 
44
- This extension is inspired by [@cdat/jupyterlab-tutorial](https://github.com/CDAT/jupyterlab-tutorial) licensed under BSD 3-Clause License with Copyright (c) 2020, Lawrence Livermore National Security, LLC.
50
+ This extension is inspired by
51
+ [@cdat/jupyterlab-tutorial](https://github.com/CDAT/jupyterlab-tutorial) licensed under
52
+ BSD 3-Clause License with Copyright (c) 2020, Lawrence Livermore National Security, LLC.
45
53
 
46
54
  ## Requirements
47
55
 
48
- - JupyterLab >= 3.0
56
+ - JupyterLab >= 3.6
49
57
 
50
- For JupyterLab 2.x, have look [there](https://github.com/jupyterlab-contrib/jupyterlab-tour/tree/2.x).
58
+ For JupyterLab 2.x, have look
59
+ [there](https://github.com/jupyterlab-contrib/jupyterlab-tour/tree/2.x).
51
60
 
52
- > For developers, the API has changed between v3 (for JupyterLab 3) and v2 (for JupyterLab 2).
61
+ > For developers, the API has changed between v3 (for JupyterLab 3) and v2 (for
62
+ > JupyterLab 2).
53
63
 
54
- ## How to add a tour with Advanced Settings
64
+ ## How to add a Tour with Advanced Settings
55
65
 
56
- As a user of JupyterLab, after you've installed `jupyterlab-tour`, you can create
57
- your own _Tours_ as data.
66
+ As a user of JupyterLab, after you've installed `jupyterlab-tour`, you can create your
67
+ own _Tours_ as data.
58
68
 
59
69
  - Open the JupyterLab _Advanced Settings_ panel <kbd>Ctrl+,</kbd>
60
70
  - Select _Tours_ from list of settings groups
@@ -70,20 +80,18 @@ overlay when pressed:
70
80
  ```json5
71
81
  // json5 can have comments
72
82
  {
73
- "tours": [
83
+ tours: [
74
84
  {
75
- "id": "my-tour",
76
- "label": "My First Tour",
85
+ id: 'my-tour',
86
+ label: 'My First Tour',
77
87
  // steps are required, and have many, many options
78
- "steps": [
79
- {"target": "#jp-MainLogo", "content": "Look at this!"}
80
- ],
88
+ steps: [{ target: '#jp-MainLogo', content: 'Look at this!' }],
81
89
  // below here not required!
82
- "options": {
83
- "styles": {
84
- "options": {
90
+ options: {
91
+ styles: {
92
+ options: {
85
93
  // you can use jupyterlab theme variables
86
- "backgroundColor": "var(--jp-warn-color0)"
94
+ backgroundColor: 'var(--jp-warn-color0)'
87
95
  }
88
96
  }
89
97
  }
@@ -92,42 +100,65 @@ overlay when pressed:
92
100
  }
93
101
  ```
94
102
 
103
+ ## How to add a Tour to a Notebook
104
+
105
+ The same JSON used to create a Tour in _Advanced Settings_ can be added to a Notebook.
106
+
107
+ - Open the Notebook
108
+ - Open the _Property Inspector_ sidebar (the "gears" icon)
109
+ - Open _Advanced Tools_
110
+ - Create a key in _Notebook Metadata_ like:
111
+
112
+ ```json
113
+ {
114
+ "jupyterlab-tour": {
115
+ "tours": []
116
+ }
117
+ }
118
+ ```
119
+
120
+ Now, when the notebook is opened, a "pin" icon will be visible in the _Notebook Toolbar_
121
+ which will allow lauching one (or all) of the tours saved in the Notebook.
122
+
95
123
  ### Shipping a Tour to Binder
96
124
 
97
- On Binder, and elsewhere, you can store the above (_without_ comments) in
98
- an [overrides.json] file and put it in the _right place_,
99
- e.g. `{sys.prefix}/share/jupyter/lab/settings/overrides.json`. When JupyterLab is
100
- next opened, those overrides will become the defaults, and your tour will be available.
125
+ On Binder, and elsewhere, you can store the above (_without_ comments) in an
126
+ [overrides.json] file and put it in the _right place_, e.g.
127
+ `{sys.prefix}/share/jupyter/lab/settings/overrides.json`. When JupyterLab is next
128
+ opened, those overrides will become the defaults, and your Tour will be available.
101
129
 
102
130
  An example `overrides.json` might look like:
103
- ```json5
131
+
132
+ ```json
104
133
  {
105
134
  "jupyterlab-tour:user-tours": {
106
- "tours": [
107
- // that tour up there!
108
- ]
135
+ "tours": []
109
136
  }
110
137
  }
111
138
  ```
112
139
 
113
- [overrides.json]: https://jupyterlab.readthedocs.io/en/stable/user/directories.html#overrides-json
140
+ [overrides.json]:
141
+ https://jupyterlab.readthedocs.io/en/stable/user/directories.html#overrides-json
114
142
 
115
- ## How to add tour for my JupyterLab extension
143
+ ## How to add Tour for my JupyterLab extension
116
144
 
117
- As an extension developer, there are two methods to add a tour: the easiest is to use JupyterLab commands and the advanced version is to request this
118
- extension token `ITourManager`.
145
+ As an extension developer, there are two methods to add a tour: the easiest is to use
146
+ JupyterLab commands and the advanced version is to request this extension token
147
+ `ITourManager`.
119
148
 
120
- ### Add easily a tour
149
+ ### Add a Tour with TypeScript
121
150
 
122
151
  ```ts
123
152
  const { commands } = app;
124
- // Add a tour - returns the tour or null if something went wrong
153
+ // Add a Tour - returns the Tour or null if something went wrong
125
154
  const tour = (await app.commands.execute('jupyterlab-tour:add', {
126
- tour: { // Tour must be of type ITour - see src/tokens.ts
155
+ tour: {
156
+ // Tour must be of type ITour - see src/tokens.ts
127
157
  id: 'test-jupyterlab-tour:welcome',
128
158
  label: 'Welcome Tour',
129
159
  hasHelpEntry: true,
130
- steps: [ // Step must be of type IStep - see src/tokens.ts
160
+ steps: [
161
+ // Step must be of type IStep - see src/tokens.ts
131
162
  {
132
163
  content:
133
164
  'The following tutorial will point out some of the main UI components within JupyterLab.',
@@ -142,46 +173,48 @@ const tour = (await app.commands.execute('jupyterlab-tour:add', {
142
173
  target: '#jp-main-dock-panel',
143
174
  title: 'Main Content'
144
175
  }
145
- ],
176
+ ]
146
177
  // can also define `options`
147
178
  }
148
179
  })) as ITourHandler;
149
- if ( tour ) {
180
+ if (tour) {
150
181
  app.commands.execute('jupyterlab-tour:launch', {
151
182
  id: 'test-jupyterlab-tour:welcome',
152
- force: false // Optional, if false the tour will start only if the user have not seen or skipped it
153
- })
183
+ force: false // Optional, if false the Tour will start only if the user have not seen or skipped it
184
+ });
154
185
  }
155
186
  ```
156
187
 
157
- > One example is available on [Mamba navigator](https://github.com/mamba-org/gator/blob/master/packages/labextension/src/index.ts#L76).
188
+ > One example is available on
189
+ > [Mamba navigator](https://github.com/mamba-org/gator/blob/master/packages/labextension/src/index.ts#L76).
158
190
  > Test it on [binder](https://mybinder.org/v2/gh/mamba-org/gator/master?urlpath=lab).
159
191
 
160
- If you want to react to step changes to trigger elements of the UI (like opening sidebar), you can connect
161
- to the `stepChanged` signal. Building from the previous example, this snippet will open the filebrowser
162
- after the first step.
192
+ If you want to react to step changes to trigger elements of the UI (like opening
193
+ sidebar), you can connect to the `stepChanged` signal. Building from the previous
194
+ example, this snippet will open the filebrowser after the first step.
163
195
 
164
196
  ```ts
165
- tour.stepChanged.connect((_, data) => {
166
- switch (data.type) {
167
- case 'step:after':
168
- if (data.step.target === '#jp-main-dock-panel') {
169
- commands.execute('filebrowser:activate');
170
- }
171
- break;
172
- }
173
- });
197
+ tour.stepChanged.connect((_, data) => {
198
+ switch (data.type) {
199
+ case 'step:after':
200
+ if (data.step.target === '#jp-main-dock-panel') {
201
+ commands.execute('filebrowser:activate');
202
+ }
203
+ break;
204
+ }
205
+ });
174
206
  ```
175
207
 
176
- > `data` is an object of type [`CallbackProps`](https://docs.react-joyride.com/callback).
208
+ > `data` is an object of type
209
+ > [`CallbackProps`](https://docs.react-joyride.com/callback).
177
210
 
178
- ## Disabling the User and Default Tours
211
+ ## Disabling the User, Notebook, and Default Tours
179
212
 
180
- If you _only_ wish to see the default _Welcome_ and _Notebook_ tours, or ones
181
- defined by users, they can be disabled via the command line or a well-known file.
213
+ If you _only_ wish to see the default _Welcome_ and _Notebook_ tours, or ones defined by
214
+ users, they can be disabled via the command line or a well-known file.
182
215
 
183
- The examples below disable all tours not provided by third-party extensions.
184
- Adding `jupyterlab-tour:plugin` to either of these will disable tours altogether!
216
+ The examples below disable all tours not provided by third-party extensions. Adding
217
+ `jupyterlab-tour:plugin` to either of these will disable tours altogether!
185
218
 
186
219
  ### Disabling Tours from the Command Line
187
220
 
@@ -189,6 +222,7 @@ From the command line, run:
189
222
 
190
223
  ```bash
191
224
  jupyter labextension disable "jupyterlab-tour:user-tours"
225
+ jupyter labextension disable "jupyterlab-tour:notebook-tours"
192
226
  jupyter labextension disable "jupyterlab-tour:default-tours"
193
227
  ```
194
228
 
@@ -202,16 +236,19 @@ Create a [pageConfig.json] and put it in _the right place_, e.g.
202
236
  {
203
237
  "disabledExtensions": {
204
238
  "jupyterlab-tour:user-tours": true,
205
- "jupyterlab-tour:default-tours": true,
239
+ "jupyterlab-tour:notebook-tours": true,
240
+ "jupyterlab-tour:default-tours": true
206
241
  }
207
242
  }
208
243
  ```
209
244
 
210
- [pageConfig.json]: https://jupyterlab.readthedocs.io/en/stable/user/directories.html#labconfig-directories
211
-
245
+ [pageConfig.json]:
246
+ https://jupyterlab.readthedocs.io/en/stable/user/directories.html#labconfig-directories
212
247
 
213
248
  ## Uninstall
214
249
 
250
+ To remove the extension, execute:
251
+
215
252
  ```bash
216
253
  pip uninstall jupyterlab-tour
217
254
  ```
@@ -228,34 +265,76 @@ conda remove -c conda-forge jupyterlab-tour
228
265
 
229
266
  Note: You will need NodeJS to build the extension package.
230
267
 
231
- The `jlpm` command is JupyterLab's pinned version of
232
- [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
233
- `yarn` or `npm` in lieu of `jlpm` below.
268
+ The `jlpm` command is JupyterLab's pinned version of [yarn](https://yarnpkg.com/) that
269
+ is installed with JupyterLab. You may use `yarn` or `npm` in lieu of `jlpm` below.
234
270
 
235
271
  ```bash
236
272
  # Clone the repo to your local environment
237
273
  # Change directory to the jupyterlab-tour directory
238
274
  # Install package in development mode
239
- pip install -e .
275
+ pip install -e "."
240
276
  # Link your development version of the extension with JupyterLab
241
277
  jupyter labextension develop . --overwrite
242
278
  # Rebuild extension Typescript source after making changes
243
- jlpm run build
279
+ jlpm build
244
280
  ```
245
281
 
246
- You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
282
+ You can watch the source directory and run JupyterLab at the same time in different
283
+ terminals to watch for changes in the extension's source and automatically rebuild the
284
+ extension.
247
285
 
248
286
  ```bash
249
287
  # Watch the source directory in one terminal, automatically rebuilding when needed
250
- jlpm run watch
288
+ jlpm watch
251
289
  # Run JupyterLab in another terminal
252
290
  jupyter lab
253
291
  ```
254
292
 
255
- With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).
293
+ With the watch command running, every saved change will immediately be built locally and
294
+ available in your running JupyterLab. Refresh JupyterLab to load the change in your
295
+ browser (you may need to wait several seconds for the extension to be rebuilt).
256
296
 
257
- By default, the `jlpm run build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
297
+ By default, the `jlpm build` command generates the source maps for this extension to
298
+ make it easier to debug using the browser dev tools. To also generate source maps for
299
+ the JupyterLab core extensions, you can run the following command:
258
300
 
259
301
  ```bash
260
302
  jupyter lab build --minimize=False
261
303
  ```
304
+
305
+ ### Development uninstall
306
+
307
+ ```bash
308
+ pip uninstall jupyterlab-tour
309
+ ```
310
+
311
+ In development mode, you will also need to remove the symlink created by
312
+ `jupyter labextension develop` command. To find its location, you can run
313
+ `jupyter labextension list` to figure out where the `labextensions` folder is located.
314
+ Then you can remove the symlink named `jupyterlab-tour` within that folder.
315
+
316
+ ### Testing the extension
317
+
318
+ #### Frontend tests
319
+
320
+ This extension is using [Jest](https://jestjs.io/) for JavaScript code testing.
321
+
322
+ To execute them, execute:
323
+
324
+ ```sh
325
+ jlpm
326
+ jlpm test
327
+ ```
328
+
329
+ #### Integration tests
330
+
331
+ This extension uses [Playwright](https://playwright.dev/docs/intro) for the integration
332
+ tests (aka user level tests). More precisely, the JupyterLab helper
333
+ [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle
334
+ testing the extension in JupyterLab.
335
+
336
+ More information are provided within the [ui-tests](./ui-tests/README.md) README.
337
+
338
+ ### Packaging the extension
339
+
340
+ See [RELEASE](RELEASE.md)
@@ -0,0 +1 @@
1
+ import 'jest';
@@ -0,0 +1,151 @@
1
+ import { Notebook, NotebookModel, NotebookPanel } from '@jupyterlab/notebook';
2
+ import { CommandRegistry } from '@lumino/commands';
3
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
4
+ import { CodeMirrorEditorFactory, CodeMirrorMimeTypeService, EditorLanguageRegistry } from '@jupyterlab/codemirror';
5
+ import { Signal } from '@lumino/signaling';
6
+ import 'jest';
7
+ import { CommandIDs } from '../constants';
8
+ import plugin from '../index';
9
+ import { NS } from '../tokens';
10
+ const DEFAULT_TOURS_SIZE = 2;
11
+ const [corePlugin, userPlugin, notebookPlugin, defaultsPlugin] = plugin;
12
+ function mockApp() {
13
+ return {
14
+ commands: new CommandRegistry(),
15
+ restored: Promise.resolve(),
16
+ docRegistry: new DocumentRegistry()
17
+ };
18
+ }
19
+ function aTour() {
20
+ return {
21
+ id: 'test-jupyterlab-tour:welcome',
22
+ label: 'Welcome Tour',
23
+ hasHelpEntry: true,
24
+ steps: [
25
+ {
26
+ content: 'Tours of Jupyter',
27
+ placement: 'auto',
28
+ target: '#jp-MainLogo',
29
+ title: 'Jupyter'
30
+ },
31
+ {
32
+ content: 'The following tour will point out some of the main UI components within JupyterLab.',
33
+ placement: 'right',
34
+ target: '#jp-main-dock-panel',
35
+ title: 'Welcome to Jupyter Lab!'
36
+ },
37
+ {
38
+ content: 'This is the main content area where notebooks and other content can be viewed and edited.',
39
+ placement: 'center',
40
+ target: '#jp-main-dock-panel',
41
+ title: 'Main Content'
42
+ }
43
+ ]
44
+ };
45
+ }
46
+ function mockSettingRegistry() {
47
+ const settings = {
48
+ composite: { tours: [aTour()] }
49
+ };
50
+ settings['changed'] = new Signal(settings);
51
+ return {
52
+ load: async () => settings
53
+ };
54
+ }
55
+ function mockNbTracker() {
56
+ const nbTracker = {};
57
+ nbTracker.widgetAdded = new Signal(nbTracker);
58
+ return nbTracker;
59
+ }
60
+ describe(corePlugin.id, () => {
61
+ describe('activation', () => {
62
+ it('should create add-tour command', () => {
63
+ var _a;
64
+ const app = mockApp();
65
+ corePlugin.activate(app);
66
+ expect((_a = app.commands) === null || _a === void 0 ? void 0 : _a.hasCommand(CommandIDs.addTour)).toEqual(true);
67
+ });
68
+ });
69
+ describe('commands', () => {
70
+ describe(`${CommandIDs.addTour}`, () => {
71
+ it('should add a tour command', async () => {
72
+ var _a;
73
+ const app = mockApp();
74
+ const manager = corePlugin.activate(app);
75
+ expect(manager.tours.size).toEqual(0);
76
+ const tour = await ((_a = app.commands) === null || _a === void 0 ? void 0 : _a.execute(CommandIDs.addTour, {
77
+ tour: aTour()
78
+ }));
79
+ expect(manager.tours.size).toEqual(1);
80
+ expect(tour).toBeTruthy();
81
+ expect(manager.tours.get(tour.id)).toBeTruthy();
82
+ });
83
+ });
84
+ });
85
+ });
86
+ describe(userPlugin.id, () => {
87
+ describe('activation', () => {
88
+ it('should have userTours', async () => {
89
+ const app = mockApp();
90
+ const manager = corePlugin.activate(app);
91
+ const settings = mockSettingRegistry();
92
+ const userManager = userPlugin.activate(app, settings, manager);
93
+ await userManager.ready;
94
+ expect(userManager.tourManager.tours.size).toBe(1);
95
+ });
96
+ });
97
+ describe('settings', () => {
98
+ it('should react to settings', async () => {
99
+ const app = mockApp();
100
+ const manager = corePlugin.activate(app);
101
+ const settingsRegistry = mockSettingRegistry();
102
+ const userManager = userPlugin.activate(app, settingsRegistry, manager);
103
+ await userManager.ready;
104
+ const settings = await settingsRegistry.load('whatever');
105
+ settings.composite = { tours: [] };
106
+ settings.changed.emit(void 0);
107
+ expect(userManager.tourManager.tours.size).toBe(0);
108
+ });
109
+ });
110
+ });
111
+ describe(notebookPlugin.id, () => {
112
+ describe('activation', () => {
113
+ it('should activate', async () => {
114
+ const app = mockApp();
115
+ const manager = corePlugin.activate(app);
116
+ const nbTracker = mockNbTracker();
117
+ const notebookTourManager = notebookPlugin.activate(app, nbTracker, manager);
118
+ const languages = new EditorLanguageRegistry();
119
+ const factoryService = new CodeMirrorEditorFactory({
120
+ languages
121
+ });
122
+ const mimeTypeService = new CodeMirrorMimeTypeService(languages);
123
+ const editorFactory = factoryService.newInlineEditor;
124
+ const notebook = new Notebook({
125
+ rendermime: null,
126
+ mimeTypeService,
127
+ contentFactory: new NotebookPanel.ContentFactory({ editorFactory })
128
+ });
129
+ const model = new NotebookModel();
130
+ notebook.model = model;
131
+ notebookTourManager.addNotebook(notebook);
132
+ expect(notebookTourManager.tourManager.tours.size).toBe(0);
133
+ model.setMetadata(NS, {
134
+ tours: [aTour()]
135
+ });
136
+ expect(notebookTourManager.tourManager.tours.size).toBe(1);
137
+ model.deleteMetadata(NS);
138
+ expect(notebookTourManager.tourManager.tours.size).toBe(0);
139
+ });
140
+ });
141
+ });
142
+ describe(defaultsPlugin.id, () => {
143
+ describe('activation', () => {
144
+ it('should activate', () => {
145
+ const app = mockApp();
146
+ const manager = corePlugin.activate(app);
147
+ defaultsPlugin.activate(app, manager);
148
+ expect(manager.tours.size).toEqual(DEFAULT_TOURS_SIZE);
149
+ });
150
+ });
151
+ });
package/lib/components.js CHANGED
@@ -37,7 +37,7 @@ class Tour extends React.Component {
37
37
  };
38
38
  }
39
39
  render() {
40
- return this.props.tours && this.props.tours[this.state.index] ? (React.createElement(ReactJoyride, Object.assign({ key: this.props.tours[this.state.index].id }, this.props.tours[this.state.index].options, { callback: this._handleJoyrideCallback, run: this.state.run, steps: this.props.tours[this.state.index].steps }))) : null;
40
+ return this.props.tours && this.props.tours[this.state.index] ? (React.createElement(ReactJoyride, { key: this.props.tours[this.state.index].id, ...this.props.tours[this.state.index].options, callback: this._handleJoyrideCallback, run: this.state.run, steps: this.props.tours[this.state.index].steps })) : null;
41
41
  }
42
42
  }
43
43
  /**