Skip to content

Commit fd7eecc

Browse files
authored
Enhance tour version (#81)
* Hide empty value for select * Use config to preserve tour states * Fix code and tests
1 parent 02261db commit fd7eecc

File tree

11 files changed

+328
-132
lines changed

11 files changed

+328
-132
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"@jupyterlab/apputils": "^4.0.0",
6363
"@jupyterlab/mainmenu": "^4.0.0",
6464
"@jupyterlab/notebook": "^4.0.0",
65+
"@jupyterlab/services": "^7.0.0",
6566
"@jupyterlab/settingregistry": "^4.0.0",
6667
"@jupyterlab/statedb": "^4.0.0",
6768
"@jupyterlab/translation": "^4.0.0",

schema/user-tours.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
4646
"description": "Other options for the tour",
4747
"$ref": "#/definitions/Props"
4848
},
49+
"version": {
50+
"type": "integer",
51+
"title": "The tour version",
52+
"description": "The tour version (prefer calendar versioning YYYYMMDD) to determine if an user should see it again or not.",
53+
"minimum": 0
54+
},
4955
"translation": {
5056
"description": "Translation domain containing strings for this tour",
5157
"type": "string"

src/__tests__/plugin.spec.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
NotebookPanel
77
} from '@jupyterlab/notebook';
88
import { ISettingRegistry } from '@jupyterlab/settingregistry';
9-
import { StateDB } from '@jupyterlab/statedb';
109
import { CommandRegistry } from '@lumino/commands';
1110
import { ReadonlyJSONObject } from '@lumino/coreutils';
1211
import { DocumentRegistry } from '@jupyterlab/docregistry';
@@ -90,8 +89,7 @@ describe(corePlugin.id, () => {
9089
describe('activation', () => {
9190
it('should create add-tour command', () => {
9291
const app = mockApp();
93-
const stateDB = new StateDB();
94-
corePlugin.activate(app as any, stateDB);
92+
corePlugin.activate(app as any);
9593

9694
expect(app.commands?.hasCommand(CommandIDs.addTour)).toEqual(true);
9795
});
@@ -101,8 +99,7 @@ describe(corePlugin.id, () => {
10199
describe(`${CommandIDs.addTour}`, () => {
102100
it('should add a tour command', async () => {
103101
const app = mockApp();
104-
const stateDB = new StateDB();
105-
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
102+
const manager = corePlugin.activate(app as any) as ITourManager;
106103
expect(manager.tours.size).toEqual(0);
107104

108105
const tour = await app.commands?.execute(CommandIDs.addTour, {
@@ -121,8 +118,7 @@ describe(userPlugin.id, () => {
121118
describe('activation', () => {
122119
it('should have userTours', async () => {
123120
const app = mockApp();
124-
const stateDB = new StateDB();
125-
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
121+
const manager = corePlugin.activate(app as any) as ITourManager;
126122
const settings = mockSettingRegistry();
127123
const userManager = userPlugin.activate(
128124
app as any,
@@ -137,8 +133,7 @@ describe(userPlugin.id, () => {
137133
describe('settings', () => {
138134
it('should react to settings', async () => {
139135
const app = mockApp();
140-
const stateDB = new StateDB();
141-
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
136+
const manager = corePlugin.activate(app as any) as ITourManager;
142137
const settingsRegistry = mockSettingRegistry();
143138
const userManager = userPlugin.activate(
144139
app as any,
@@ -158,8 +153,7 @@ describe(notebookPlugin.id, () => {
158153
describe('activation', () => {
159154
it('should activate', async () => {
160155
const app = mockApp();
161-
const stateDB = new StateDB();
162-
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
156+
const manager = corePlugin.activate(app as any) as ITourManager;
163157
const nbTracker = mockNbTracker();
164158
const notebookTourManager = notebookPlugin.activate(
165159
app as any,
@@ -195,8 +189,7 @@ describe(defaultsPlugin.id, () => {
195189
describe('activation', () => {
196190
it('should activate', () => {
197191
const app = mockApp();
198-
const stateDB = new StateDB();
199-
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
192+
const manager = corePlugin.activate(app as any) as ITourManager;
200193
defaultsPlugin.activate(app as any, manager);
201194
expect(manager.tours.size).toEqual(DEFAULT_TOURS_SIZE);
202195
});

src/defaults.tsx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ namespace DefaultTours {
2323
): void {
2424
const trans = manager.translator;
2525

26-
const welcomeTour = manager.createTour(
27-
WELCOME_ID,
28-
trans.__('Welcome Tour'),
29-
true,
30-
undefined,
31-
defaultTourIcon
32-
);
26+
const welcomeTour = manager.createTour({
27+
id: WELCOME_ID,
28+
label: trans.__('Welcome Tour'),
29+
hasHelpEntry: true,
30+
icon: defaultTourIcon,
31+
version: 20231107
32+
});
3333

3434
welcomeTour.options = {
3535
...welcomeTour.options,
@@ -292,13 +292,13 @@ namespace DefaultTours {
292292
appName = 'JupyterLab'
293293
): void {
294294
const trans = manager.translator;
295-
const notebookTour = manager.createTour(
296-
NOTEBOOK_ID,
297-
trans.__('Notebook Tour'),
298-
true,
299-
undefined,
300-
defaultNotebookTourIcon
301-
);
295+
const notebookTour = manager.createTour({
296+
id: NOTEBOOK_ID,
297+
label: trans.__('Notebook Tour'),
298+
hasHelpEntry: true,
299+
icon: defaultNotebookTourIcon,
300+
version: 20231107
301+
});
302302

303303
notebookTour.options = {
304304
...notebookTour.options,
@@ -541,11 +541,12 @@ namespace DefaultTours {
541541
): void {
542542
const trans = manager.translator;
543543

544-
const welcomeTour = manager.createTour(
545-
WELCOME_ID,
546-
trans.__('Welcome Tour'),
547-
true
548-
);
544+
const welcomeTour = manager.createTour({
545+
id: WELCOME_ID,
546+
label: trans.__('Welcome Tour'),
547+
hasHelpEntry: true,
548+
version: 20231107
549+
});
549550

550551
welcomeTour.options = {
551552
...welcomeTour.options,

src/notebookButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class TourButton extends ReactWidget {
5656
title={title}
5757
value=""
5858
>
59-
<option value=""></option>
59+
<option style={{ display: 'none' }} value=""></option>
6060
{errors.length ? (
6161
<option>
6262
{trans.__('Tour metadata is not valid: see the browser console!')}

src/plugin.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { IMainMenu, MainMenu } from '@jupyterlab/mainmenu';
99
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
1010
import { ISettingRegistry } from '@jupyterlab/settingregistry';
11-
import { IStateDB } from '@jupyterlab/statedb';
11+
import { ConfigSection } from '@jupyterlab/services';
1212
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
1313

1414
import { Widget } from '@lumino/widgets';
@@ -24,6 +24,8 @@ import {
2424
INotebookTourManager,
2525
ITourHandler,
2626
ITourManager,
27+
ITourState,
28+
ITourTracker,
2729
IUserTourManager,
2830
NOTEBOOK_PLUGIN_ID,
2931
NS,
@@ -33,6 +35,7 @@ import {
3335
import { TourHandler } from './tour';
3436
import { TourManager } from './tourManager';
3537
import { UserTourManager } from './userTourManager';
38+
import { PromiseDelegate } from '@lumino/coreutils';
3639

3740
/**
3841
* Initialization data for the jupyterlab-tour extension.
@@ -41,24 +44,50 @@ const corePlugin: JupyterFrontEndPlugin<ITourManager> = {
4144
id: PLUGIN_ID,
4245
autoStart: true,
4346
activate,
44-
requires: [IStateDB],
4547
optional: [ICommandPalette, IMainMenu, ITranslator],
4648
provides: ITourManager
4749
};
4850

4951
function activate(
5052
app: JupyterFrontEnd,
51-
stateDB: IStateDB,
52-
palette?: ICommandPalette,
53-
menu?: MainMenu,
54-
translator?: ITranslator
53+
palette: ICommandPalette | null,
54+
menu: MainMenu | null,
55+
translator: ITranslator | null
5556
): ITourManager {
57+
const CONFIG_SECTION_NAME = corePlugin.id.replace(/[^\w]/g, '');
5658
const { commands } = app;
5759

5860
translator = translator ?? nullTranslator;
5961

62+
const restoreState = new PromiseDelegate<ITourState[]>();
63+
const configReady = ConfigSection.create({
64+
name: CONFIG_SECTION_NAME
65+
}).catch(error => {
66+
console.error('Failed to fetch state for jupyterlab-tour.', error);
67+
});
68+
const tracker: ITourTracker = Object.freeze({
69+
restored: restoreState.promise,
70+
save: async (state: ITourState[]) => {
71+
await restoreState.promise;
72+
(await configReady)?.update({ state } as any);
73+
}
74+
});
75+
6076
// Create tour manager
61-
const manager = new TourManager(stateDB, translator, menu);
77+
const manager = new TourManager({ helpMenu: menu?.helpMenu, tracker, translator });
78+
void Promise.all([
79+
app.restored,
80+
// Use config instead of state to store independently of the workspace
81+
// if a tour has been displayed or not.
82+
configReady
83+
])
84+
.then(async ([_, config]) => {
85+
restoreState.resolve((config?.data['state'] ?? []) as any);
86+
})
87+
.catch(error => {
88+
console.error('Failed to load tour states.', error);
89+
restoreState.reject(error);
90+
});
6291
const trans = manager.translator;
6392

6493
commands.addCommand(CommandIDs.launch, {
@@ -93,6 +122,8 @@ function activate(
93122
}
94123
}
95124

125+
await manager.ready;
126+
96127
manager.launch([id], force);
97128
}
98129
});
@@ -212,7 +243,7 @@ function activateDefaults(
212243
});
213244
}
214245

215-
app.restored.then(() => {
246+
Promise.all([app.restored, tourManager.ready]).then(() => {
216247
if (
217248
tourManager.tours.has(WELCOME_ID) &&
218249
(app.name !== 'Jupyter Notebook' ||

0 commit comments

Comments
 (0)