@theia/getting-started 1.76.0-next.16 → 1.76.0-next.18

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.
Files changed (36) hide show
  1. package/README.md +44 -0
  2. package/lib/browser/getting-started-frontend-module.d.ts.map +1 -1
  3. package/lib/browser/getting-started-frontend-module.js +3 -0
  4. package/lib/browser/getting-started-frontend-module.js.map +1 -1
  5. package/lib/browser/getting-started-widget.d.ts +0 -7
  6. package/lib/browser/getting-started-widget.d.ts.map +1 -1
  7. package/lib/browser/getting-started-widget.js +1 -29
  8. package/lib/browser/getting-started-widget.js.map +1 -1
  9. package/lib/browser/walkthrough-section.d.ts +2 -2
  10. package/lib/browser/walkthrough-section.d.ts.map +1 -1
  11. package/lib/browser/walkthrough-section.js +7 -5
  12. package/lib/browser/walkthrough-section.js.map +1 -1
  13. package/lib/browser/walkthrough-section.spec.js +17 -1
  14. package/lib/browser/walkthrough-section.spec.js.map +1 -1
  15. package/lib/browser/walkthrough-service.d.ts +26 -5
  16. package/lib/browser/walkthrough-service.d.ts.map +1 -1
  17. package/lib/browser/walkthrough-service.js +77 -22
  18. package/lib/browser/walkthrough-service.js.map +1 -1
  19. package/lib/browser/walkthrough-service.spec.js +173 -57
  20. package/lib/browser/walkthrough-service.spec.js.map +1 -1
  21. package/lib/common/walkthrough-provider.d.ts +22 -0
  22. package/lib/common/walkthrough-provider.d.ts.map +1 -0
  23. package/lib/common/walkthrough-provider.js +27 -0
  24. package/lib/common/walkthrough-provider.js.map +1 -0
  25. package/lib/common/walkthrough-types.d.ts +18 -5
  26. package/lib/common/walkthrough-types.d.ts.map +1 -1
  27. package/package.json +8 -8
  28. package/src/browser/getting-started-frontend-module.ts +3 -0
  29. package/src/browser/getting-started-widget.tsx +31 -101
  30. package/src/browser/style/index.css +69 -35
  31. package/src/browser/walkthrough-section.spec.tsx +29 -1
  32. package/src/browser/walkthrough-section.tsx +7 -5
  33. package/src/browser/walkthrough-service.spec.ts +207 -57
  34. package/src/browser/walkthrough-service.ts +82 -25
  35. package/src/common/walkthrough-provider.ts +38 -0
  36. package/src/common/walkthrough-types.ts +20 -5
@@ -21,14 +21,16 @@ import { StorageService } from '@theia/core/lib/browser/storage-service';
21
21
  import { CommandRegistry } from '@theia/core/lib/common/command';
22
22
  import { PreferenceService } from '@theia/core/lib/common/preferences';
23
23
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
24
- import { DeployedPlugin, PluginIdentifiers, PluginMetadata, WalkthroughContribution, WalkthroughStepContribution } from '@theia/plugin-ext/lib/common/plugin-protocol';
24
+ import { DeployedPlugin, PluginIdentifiers, PluginMetadata } from '@theia/plugin-ext/lib/common/plugin-protocol';
25
25
 
26
26
  import { OpenerService, open } from '@theia/core/lib/browser/opener-service';
27
+ import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
27
28
  import { ILogger } from '@theia/core/lib/common/logger';
28
29
  import { MessageService } from '@theia/core/lib/common/message-service';
29
30
  import { nls } from '@theia/core/lib/common/nls';
30
31
  import { URI } from '@theia/core/lib/common/uri';
31
- import { Walkthrough, WalkthroughStep } from '../common/walkthrough-types';
32
+ import { Walkthrough, WalkthroughDefinition, WalkthroughStep, WalkthroughStepDefinition } from '../common/walkthrough-types';
33
+ import { WalkthroughProvider } from '../common/walkthrough-provider';
32
34
  import { GettingStartedPreferences } from '../common/getting-started-preferences';
33
35
  import { WalkthroughCommands } from '../common/walkthrough-commands';
34
36
 
@@ -77,6 +79,9 @@ export class WalkthroughService implements Disposable {
77
79
  @inject(WalkthroughViewEventSource)
78
80
  protected readonly viewEventSource: WalkthroughViewEventSource;
79
81
 
82
+ @inject(ContributionProvider) @named(WalkthroughProvider)
83
+ protected readonly walkthroughProviders: ContributionProvider<WalkthroughProvider>;
84
+
80
85
  @inject(OpenerService)
81
86
  protected readonly openerService: OpenerService;
82
87
 
@@ -116,12 +121,18 @@ export class WalkthroughService implements Disposable {
116
121
  // Plugins are deployed while the progress is still being read. Registering a walkthrough before
117
122
  // that would report its completed steps as pending, so every sync waits for the progress.
118
123
  this.progressReady = this.loadProgress().then(() => {
119
- this.syncWalkthroughsFromPlugins();
124
+ this.syncWalkthroughs();
120
125
  this.establishPluginBaseline();
121
126
  });
122
127
 
123
128
  this.toDispose.push(this.pluginSupport.onDidChangePlugins(() => this.handlePluginsChanged()));
124
129
 
130
+ for (const provider of this.walkthroughProviders.getContributions()) {
131
+ if (provider.onDidChange) {
132
+ this.toDispose.push(provider.onDidChange(() => this.handleProvidersChanged()));
133
+ }
134
+ }
135
+
125
136
  this.toDispose.push(this.commandRegistry.onDidExecuteCommand(e => {
126
137
  this.handleCompletionEvent(`onCommand:${e.commandId}`);
127
138
  }));
@@ -142,11 +153,16 @@ export class WalkthroughService implements Disposable {
142
153
  }));
143
154
  }
144
155
 
156
+ protected async handleProvidersChanged(): Promise<void> {
157
+ await this.progressReady;
158
+ this.syncWalkthroughs();
159
+ }
160
+
145
161
  protected async handlePluginsChanged(): Promise<void> {
146
162
  await this.progressReady;
147
163
  const previousIds = this.knownPluginIds;
148
164
  const baselineEstablished = this.pluginBaselineEstablished;
149
- this.syncWalkthroughsFromPlugins();
165
+ this.syncWalkthroughs();
150
166
  this.establishPluginBaseline();
151
167
  if (!baselineEstablished) {
152
168
  // The plugins deployed while the application was starting are not new installations.
@@ -262,9 +278,39 @@ export class WalkthroughService implements Disposable {
262
278
  await this.storageService.setData(WALKTHROUGH_PROGRESS_KEY, this.progressState);
263
279
  }
264
280
 
265
- protected syncWalkthroughsFromPlugins(): void {
266
- const plugins = this.pluginSupport.plugins;
281
+ /**
282
+ * Register the walkthroughs of both sources - plugins and {@link WalkthroughProvider} contributions - and
283
+ * drop the ones that are gone.
284
+ *
285
+ * Both sources are collected before anything is dropped, so that they cannot delete each other's walkthroughs.
286
+ */
287
+ protected syncWalkthroughs(): void {
267
288
  const seenIds = new Set<string>();
289
+ this.collectPluginWalkthroughs(seenIds);
290
+ this.collectProvidedWalkthroughs(seenIds);
291
+ this.removeUnseenWalkthroughs(seenIds);
292
+ }
293
+
294
+ protected collectProvidedWalkthroughs(seenIds: Set<string>): void {
295
+ for (const provider of this.walkthroughProviders.getContributions()) {
296
+ try {
297
+ for (const definition of provider.getWalkthroughs()) {
298
+ if (seenIds.has(definition.id)) {
299
+ this.logger.warn(`The walkthrough '${definition.id}' is contributed more than once; only the first contribution is used.`);
300
+ continue;
301
+ }
302
+ seenIds.add(definition.id);
303
+ this.registerIfChanged(definition.id, definition, () => this.registerWalkthrough(definition.id, definition));
304
+ }
305
+ } catch (error) {
306
+ // A failing provider must not stop the other walkthroughs, nor reject the sync it runs in.
307
+ this.logger.error('Could not collect the walkthroughs of a provider.', error);
308
+ }
309
+ }
310
+ }
311
+
312
+ protected collectPluginWalkthroughs(seenIds: Set<string>): void {
313
+ const plugins = this.pluginSupport.plugins;
268
314
 
269
315
  for (const pluginMeta of plugins) {
270
316
  if (pluginMeta.outOfSync) {
@@ -284,15 +330,27 @@ export class WalkthroughService implements Disposable {
284
330
  for (const contribution of deployed.contributes.walkthroughs) {
285
331
  const fullId = `${contribution.pluginId}.${contribution.id}`;
286
332
  seenIds.add(fullId);
287
- // Re-register when the definition changed, for instance because the plugin was updated.
288
- const signature = JSON.stringify(contribution);
289
- if (this.contributionSignatures.get(fullId) !== signature) {
290
- this.contributionSignatures.set(fullId, signature);
291
- this.registerWalkthrough(contribution);
292
- }
333
+ this.registerIfChanged(fullId, contribution, () => this.registerWalkthrough(fullId, contribution, contribution.pluginId, contribution.pluginIcon));
293
334
  }
294
335
  }
336
+ }
295
337
 
338
+ /**
339
+ * Register a walkthrough anew whenever its definition changed, for instance because the contributing plugin
340
+ * was updated. An unchanged definition keeps the walkthrough as it is, progress included.
341
+ */
342
+ protected registerIfChanged(id: string, definition: WalkthroughDefinition, register: () => void): void {
343
+ const signature = JSON.stringify(definition);
344
+ if (this.contributionSignatures.get(id) !== signature) {
345
+ this.contributionSignatures.set(id, signature);
346
+ register();
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Drop every walkthrough that none of the sources contributed any more.
352
+ */
353
+ protected removeUnseenWalkthroughs(seenIds: Set<string>): void {
296
354
  let changed = false;
297
355
  for (const id of this.walkthroughs.keys()) {
298
356
  if (!seenIds.has(id)) {
@@ -311,24 +369,23 @@ export class WalkthroughService implements Disposable {
311
369
  }
312
370
  }
313
371
 
314
- protected registerWalkthrough(contribution: WalkthroughContribution): void {
315
- const fullId = `${contribution.pluginId}.${contribution.id}`;
316
- const completedSteps = this.progressState.completedSteps[fullId] || [];
372
+ protected registerWalkthrough(id: string, definition: WalkthroughDefinition, pluginId?: string, pluginIcon?: string): void {
373
+ const completedSteps = this.progressState.completedSteps[id] || [];
317
374
 
318
- const steps: WalkthroughStep[] = contribution.steps.map(step => this.toWalkthroughStep(step, completedSteps));
375
+ const steps: WalkthroughStep[] = definition.steps.map(step => this.toWalkthroughStep(step, completedSteps));
319
376
 
320
377
  const walkthrough: Walkthrough = {
321
- id: fullId,
322
- title: contribution.title,
323
- description: contribution.description,
378
+ id,
379
+ title: definition.title,
380
+ description: definition.description,
324
381
  steps,
325
- when: contribution.when,
326
- icon: contribution.icon,
327
- pluginId: contribution.pluginId,
328
- pluginIcon: contribution.pluginIcon
382
+ when: definition.when,
383
+ icon: definition.icon,
384
+ pluginId,
385
+ pluginIcon
329
386
  };
330
387
 
331
- this.walkthroughs.set(fullId, walkthrough);
388
+ this.walkthroughs.set(id, walkthrough);
332
389
  this.contextKeys = undefined;
333
390
  // A context key that is already set has to complete its steps right away; without this, a step keyed on a
334
391
  // static context - `onContext:isLinux` for instance - would wait for a change that never comes.
@@ -336,7 +393,7 @@ export class WalkthroughService implements Disposable {
336
393
  this.onDidChangeWalkthroughsEmitter.fire();
337
394
  }
338
395
 
339
- protected toWalkthroughStep(step: WalkthroughStepContribution, completedSteps: string[]): WalkthroughStep {
396
+ protected toWalkthroughStep(step: WalkthroughStepDefinition, completedSteps: string[]): WalkthroughStep {
340
397
  return {
341
398
  id: step.id,
342
399
  title: step.title,
@@ -0,0 +1,38 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { Event } from '@theia/core/lib/common/event';
18
+ import { WalkthroughDefinition } from './walkthrough-types';
19
+
20
+ /**
21
+ * Contribution point for walkthroughs provided by a Theia extension, next to the walkthroughs that plugins
22
+ * contribute through their `contributes.walkthroughs` manifest section.
23
+ *
24
+ * Both sources are treated alike: contributed walkthroughs are listed on the welcome page, keep their progress,
25
+ * and can be opened with the `walkthrough.open` command.
26
+ */
27
+ export const WalkthroughProvider = Symbol('WalkthroughProvider');
28
+ export interface WalkthroughProvider {
29
+ /**
30
+ * The walkthroughs of this provider. Called again whenever {@link onDidChange} fires.
31
+ *
32
+ * The ids are used as they are, so they have to be unique across the application. A walkthrough contributed
33
+ * by a plugin is identified by `<pluginId>.<walkthroughId>`, which a provider must not imitate.
34
+ */
35
+ getWalkthroughs(): WalkthroughDefinition[];
36
+ /** Fired when the result of {@link getWalkthroughs} changed. */
37
+ readonly onDidChange?: Event<void>;
38
+ }
@@ -16,24 +16,39 @@
16
16
 
17
17
  import { WalkthroughStepMedia } from '@theia/plugin-ext/lib/common/plugin-protocol';
18
18
 
19
- export interface WalkthroughStep {
19
+ /**
20
+ * A step as it is contributed, before the service tracks its completion.
21
+ */
22
+ export interface WalkthroughStepDefinition {
20
23
  id: string;
21
24
  title: string;
22
25
  description: string;
23
26
  media?: WalkthroughStepMedia;
24
27
  completionEvents?: string[];
25
28
  when?: string;
26
- isComplete: boolean;
27
29
  }
28
30
 
29
- export interface Walkthrough {
31
+ /**
32
+ * A walkthrough as it is contributed, either by a plugin or by a `WalkthroughProvider`.
33
+ */
34
+ export interface WalkthroughDefinition {
30
35
  id: string;
31
36
  title: string;
32
37
  description: string;
33
- steps: WalkthroughStep[];
38
+ steps: WalkthroughStepDefinition[];
34
39
  when?: string;
40
+ /** Name of the codicon shown on the card and in the detail header, without the `codicon-` prefix. */
35
41
  icon?: string;
36
- pluginId: string;
42
+ }
43
+
44
+ export interface WalkthroughStep extends WalkthroughStepDefinition {
45
+ isComplete: boolean;
46
+ }
47
+
48
+ export interface Walkthrough extends WalkthroughDefinition {
49
+ steps: WalkthroughStep[];
50
+ /** Id of the contributing plugin, or `undefined` for a walkthrough contributed by a Theia extension. */
51
+ pluginId?: string;
37
52
  /** Icon of the contributing extension, as a backend relative path. */
38
53
  pluginIcon?: string;
39
54
  }