@codingame/monaco-vscode-welcome-service-override 36.2.7 → 37.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -0
- package/package.json +3 -2
- package/vscode/src/vs/workbench/contrib/onboarding/browser/media/spotlight.css +5 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/onboarding.contribution.js +37 -7
- package/vscode/src/vs/workbench/contrib/onboarding/browser/onboardingService.d.ts +2 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/onboardingService.js +36 -5
- package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/runOnboardingStep.d.ts +17 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/runOnboardingStep.js +32 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/sequencePresentation.d.ts +10 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/sequencePresentation.js +154 -0
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/onboardingTarget.d.ts +7 -1
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/onboardingTarget.js +5 -1
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightOverlay.d.ts +13 -9
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightOverlay.js +60 -15
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.d.ts +5 -1
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.js +222 -49
- package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightTypes.d.ts +23 -5
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingPresentation.d.ts +4 -0
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenario.d.ts +9 -8
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenarioService.d.ts +7 -0
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenarioService.js +2 -1
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingSequence.d.ts +45 -0
- package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingSequence.js +29 -0
- package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.d.ts +1 -0
- package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.js +158 -0
- package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.d.ts +105 -0
- package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.js +765 -0
- package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css +564 -0
- package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/media/variationA.css +2 -149
- package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.d.ts +5 -5
- package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.js +61 -131
- package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/welcomeOnboarding.contribution.js +1 -1
- package/vscode/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.js +10 -10
- package/vscode/src/vs/workbench/contrib/welcomeViews/common/viewsWelcomeContribution.js +2 -2
- package/vscode/src/vs/workbench/contrib/welcomeViews/common/viewsWelcomeExtensionPoint.js +8 -8
|
@@ -33,6 +33,7 @@ class SpotlightOverlay extends Disposable {
|
|
|
33
33
|
this._onDidSkip = this._register(( new Emitter()));
|
|
34
34
|
this.onDidSkip = this._onDidSkip.event;
|
|
35
35
|
this._options = {};
|
|
36
|
+
this._hasShown = false;
|
|
36
37
|
this._root = append(this._container, $(".spotlight-overlay"));
|
|
37
38
|
this._root.style.display = "none";
|
|
38
39
|
this._blockers = [
|
|
@@ -63,8 +64,8 @@ class SpotlightOverlay extends Disposable {
|
|
|
63
64
|
...defaultButtonStyles,
|
|
64
65
|
secondary: true
|
|
65
66
|
})));
|
|
66
|
-
this._skipButton.label = ( localize(
|
|
67
|
-
this._skipButton.setTitle(( localize(
|
|
67
|
+
this._skipButton.label = ( localize(14872, "End Tour"));
|
|
68
|
+
this._skipButton.setTitle(( localize(14873, "End Tour (Esc)")));
|
|
68
69
|
this._register(
|
|
69
70
|
this._skipButton.onDidClick(() => this._onDidSkip.fire(OnboardingDismissReason.SkipButton))
|
|
70
71
|
);
|
|
@@ -72,12 +73,12 @@ class SpotlightOverlay extends Disposable {
|
|
|
72
73
|
...defaultButtonStyles,
|
|
73
74
|
secondary: true
|
|
74
75
|
})));
|
|
75
|
-
this._backButton.label = ( localize(
|
|
76
|
+
this._backButton.label = ( localize(14874, "Back"));
|
|
76
77
|
this._register(this._backButton.onDidClick(() => this._onDidClickPrevious.fire()));
|
|
77
78
|
this._nextButton = this._register(( new Button(actions, {
|
|
78
79
|
...defaultButtonStyles
|
|
79
80
|
})));
|
|
80
|
-
this._nextButton.label = ( localize(
|
|
81
|
+
this._nextButton.label = ( localize(14875, "Next"));
|
|
81
82
|
this._register(this._nextButton.onDidClick(() => this._onDidClickNext.fire("button")));
|
|
82
83
|
for (const button of [this._skipButton, this._backButton, this._nextButton]) {
|
|
83
84
|
this._register(
|
|
@@ -92,13 +93,16 @@ class SpotlightOverlay extends Disposable {
|
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
95
|
show(target, content, options = {}) {
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
if (!this._hasShown) {
|
|
97
|
+
this._hasShown = true;
|
|
97
98
|
this._previousFocus = isHTMLElement(getActiveElement()) ? getActiveElement() : undefined;
|
|
98
99
|
}
|
|
99
100
|
this._target = target;
|
|
100
101
|
this._options = options;
|
|
101
102
|
this._renderContent(content);
|
|
103
|
+
const externalUiParticipates = !!options.targetOverlayVisible || !!options.allowTargetInteraction || !!options.advanceOnTargetClick || !!options.hideNext;
|
|
104
|
+
this._root.classList.toggle("target-overlay-visible", externalUiParticipates);
|
|
105
|
+
this._callout.setAttribute("aria-modal", externalUiParticipates ? "false" : "true");
|
|
102
106
|
this._root.style.display = "";
|
|
103
107
|
this._stepListeners.clear();
|
|
104
108
|
const targetWindow = getWindow(this._container);
|
|
@@ -119,15 +123,45 @@ class SpotlightOverlay extends Disposable {
|
|
|
119
123
|
this._scheduledLayout = undefined;
|
|
120
124
|
}));
|
|
121
125
|
const advanceOnTargetClick = !!options.advanceOnTargetClick;
|
|
122
|
-
|
|
126
|
+
const advanceOnly = options.advanceOnTargetClick === "advanceOnly";
|
|
127
|
+
const hideNext = options.hideNext ?? advanceOnTargetClick;
|
|
128
|
+
this._nextButton.element.style.display = hideNext ? "none" : "";
|
|
123
129
|
if (advanceOnTargetClick) {
|
|
124
|
-
this._stepListeners.add(
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
this._stepListeners.add(addDisposableListener(target, EventType.CLICK, event => {
|
|
131
|
+
if (advanceOnly) {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
event.stopImmediatePropagation();
|
|
134
|
+
}
|
|
135
|
+
this._onDidClickNext.fire("target");
|
|
136
|
+
}, advanceOnly));
|
|
137
|
+
}
|
|
138
|
+
if (advanceOnly) {
|
|
139
|
+
const onTargetKey = event => {
|
|
140
|
+
const key = ( new StandardKeyboardEvent(event));
|
|
141
|
+
if (key.equals(KeyCode.Enter) || key.equals(KeyCode.Space)) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
event.stopImmediatePropagation();
|
|
144
|
+
if (event.type === EventType.KEY_UP) {
|
|
145
|
+
this._onDidClickNext.fire("target");
|
|
146
|
+
}
|
|
147
|
+
} else if (event.type === EventType.KEY_DOWN) {
|
|
148
|
+
this._onKeyDown(event);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
this._stepListeners.add(addDisposableListener(target, EventType.KEY_DOWN, onTargetKey, true));
|
|
152
|
+
this._stepListeners.add(addDisposableListener(target, EventType.KEY_UP, onTargetKey, true));
|
|
153
|
+
} else if (options.allowTargetInteraction || advanceOnTargetClick || options.hideNext) {
|
|
127
154
|
this._stepListeners.add(addDisposableListener(target, EventType.KEY_DOWN, e => this._onKeyDown(e)));
|
|
128
155
|
}
|
|
129
156
|
this.layout();
|
|
130
|
-
(
|
|
157
|
+
(hideNext ? target : this._nextButton.element).focus();
|
|
158
|
+
}
|
|
159
|
+
hide() {
|
|
160
|
+
this._stepListeners.clear();
|
|
161
|
+
this._root.style.display = "none";
|
|
162
|
+
this._root.classList.remove("target-overlay-visible");
|
|
163
|
+
this._target = undefined;
|
|
164
|
+
this._options = {};
|
|
131
165
|
}
|
|
132
166
|
layout() {
|
|
133
167
|
const target = this._target;
|
|
@@ -311,9 +345,10 @@ class SpotlightOverlay extends Disposable {
|
|
|
311
345
|
} else {
|
|
312
346
|
this._description.textContent = content.description;
|
|
313
347
|
}
|
|
314
|
-
this._counter.textContent = ( localize(
|
|
348
|
+
this._counter.textContent = content.stepCount > 1 ? ( localize(14876, "{0} of {1}", content.stepIndex + 1, content.stepCount)) : "";
|
|
349
|
+
this._skipButton.element.style.display = content.isLastStep ? "none" : "";
|
|
315
350
|
this._backButton.element.style.display = content.canGoBack ? "" : "none";
|
|
316
|
-
this._nextButton.label = content.isLastStep ? ( localize(
|
|
351
|
+
this._nextButton.label = content.nextButtonLabel ?? (content.isLastStep ? ( localize(14877, "Done")) : ( localize(14875, "Next")));
|
|
317
352
|
}
|
|
318
353
|
_onKeyDown(e) {
|
|
319
354
|
const event = ( new StandardKeyboardEvent(e));
|
|
@@ -346,14 +381,24 @@ class SpotlightOverlay extends Disposable {
|
|
|
346
381
|
focusable[nextIndex].focus();
|
|
347
382
|
}
|
|
348
383
|
_collectFocusable() {
|
|
349
|
-
const
|
|
384
|
+
const targetFocusables =
|
|
385
|
+
(this._options.allowTargetInteraction || this._options.advanceOnTargetClick || this._options.hideNext) && this._target ? [this._target, ...this._target.querySelectorAll(
|
|
386
|
+
"a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
|
|
387
|
+
)] : [];
|
|
350
388
|
const descriptionFocusables = Array.from(
|
|
351
389
|
this._description.querySelectorAll(
|
|
352
390
|
"a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
|
|
353
391
|
)
|
|
354
392
|
);
|
|
355
393
|
const buttons = ( [this._skipButton, this._backButton, this._nextButton].filter(button => button.element.style.display !== "none").map(button => button.element));
|
|
356
|
-
return [...
|
|
394
|
+
return [...targetFocusables, ...descriptionFocusables, ...buttons].filter(element => this._isTabbable(element));
|
|
395
|
+
}
|
|
396
|
+
_isTabbable(element) {
|
|
397
|
+
if (!element.isConnected || element.getAttribute("aria-hidden") === "true" || element.tabIndex === -1 || element.hasAttribute("disabled")) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
const style = getWindow(this._container).getComputedStyle(element);
|
|
401
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
357
402
|
}
|
|
358
403
|
scheduleLayout() {
|
|
359
404
|
if (this._scheduledLayout) {
|
package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.d.ts
CHANGED
|
@@ -4,19 +4,23 @@ import { IWorkbenchLayoutService } from "@codingame/monaco-vscode-api/vscode/vs/
|
|
|
4
4
|
import { IHostService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/services/host/browser/host.service";
|
|
5
5
|
import { IOnboardingPresentation, IOnboardingRunContext } from "../../common/onboardingPresentation.js";
|
|
6
6
|
import { IOnboardingRunResult, IOnboardingScenario } from "../../common/onboardingScenario.js";
|
|
7
|
+
import { IOnboardingSequenceStep, IOnboardingSequenceStepContext, IOnboardingSequenceStepPresentation, IOnboardingSequenceStepResult } from "../../common/onboardingSequence.js";
|
|
7
8
|
/**
|
|
8
9
|
* Renders {@link ISpotlightPayload} scenarios: it dims the window (including the
|
|
9
10
|
* native window controls), walks the steps, and shows an anchored callout for
|
|
10
11
|
* each. Implements the engine's {@link IOnboardingPresentation} contract so the
|
|
11
12
|
* scenario engine can drive it without knowing anything about spotlights.
|
|
12
13
|
*/
|
|
13
|
-
export declare class SpotlightPresentation extends Disposable implements IOnboardingPresentation {
|
|
14
|
+
export declare class SpotlightPresentation extends Disposable implements IOnboardingPresentation, IOnboardingSequenceStepPresentation {
|
|
14
15
|
private readonly layoutService;
|
|
15
16
|
private readonly hostService;
|
|
16
17
|
private readonly contextKeyService;
|
|
17
18
|
readonly kind = "spotlight";
|
|
19
|
+
readonly countsAsVisualStep = true;
|
|
18
20
|
constructor(layoutService: IWorkbenchLayoutService, hostService: IHostService, contextKeyService: IContextKeyService);
|
|
19
21
|
run(scenario: IOnboardingScenario, context: IOnboardingRunContext): Promise<IOnboardingRunResult>;
|
|
22
|
+
runStep(sequenceStep: IOnboardingSequenceStep, context: IOnboardingSequenceStepContext): Promise<IOnboardingSequenceStepResult>;
|
|
23
|
+
private _runPayload;
|
|
20
24
|
private _resolveTarget;
|
|
21
25
|
private _waitForTargetReady;
|
|
22
26
|
private _getActiveFiniteAnimations;
|
package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
|
|
3
3
|
import { timeout } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
|
|
4
|
+
import { CancellationTokenSource } from '@codingame/monaco-vscode-api/vscode/vs/base/common/cancellation';
|
|
4
5
|
import { onUnexpectedError } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors';
|
|
5
6
|
import { Disposable, DisposableStore, toDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
|
|
6
7
|
import { IContextKeyService } from '@codingame/monaco-vscode-api/vscode/vs/platform/contextkey/common/contextkey.service';
|
|
7
8
|
import { IWorkbenchLayoutService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/layout/browser/layoutService.service';
|
|
8
9
|
import { IHostService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/host/browser/host.service';
|
|
9
10
|
import { OnboardingDismissReason, OnboardingOutcome } from '../../common/onboardingScenario.js';
|
|
10
|
-
import { findOnboardingTarget } from './onboardingTarget.js';
|
|
11
|
+
import { findOnboardingTarget, openOnboardingTarget } from './onboardingTarget.js';
|
|
11
12
|
import { SpotlightOverlay } from './spotlightOverlay.js';
|
|
12
13
|
import { SPOTLIGHT_PRESENTATION_KIND } from './spotlightTypes.js';
|
|
13
14
|
|
|
@@ -21,9 +22,102 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
21
22
|
this.hostService = hostService;
|
|
22
23
|
this.contextKeyService = contextKeyService;
|
|
23
24
|
this.kind = SPOTLIGHT_PRESENTATION_KIND;
|
|
25
|
+
this.countsAsVisualStep = true;
|
|
24
26
|
}
|
|
25
27
|
async run(scenario, context) {
|
|
26
28
|
const payload = scenario.presentation.payload;
|
|
29
|
+
return this._runPayload(payload, context);
|
|
30
|
+
}
|
|
31
|
+
async runStep(sequenceStep, context) {
|
|
32
|
+
const step = sequenceStep.payload;
|
|
33
|
+
if (step.when && !this.contextKeyService.contextMatchesRules(step.when)) {
|
|
34
|
+
return {
|
|
35
|
+
action: "skipStep",
|
|
36
|
+
shown: false
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await step.onBeforeShow?.();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
onUnexpectedError(error);
|
|
43
|
+
}
|
|
44
|
+
if (context.cancellationToken.isCancellationRequested) {
|
|
45
|
+
return {
|
|
46
|
+
action: "abort",
|
|
47
|
+
shown: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const target = await this._resolveTarget(
|
|
51
|
+
context.targetWindow,
|
|
52
|
+
step.targetId,
|
|
53
|
+
context.cancellationToken,
|
|
54
|
+
step.missingTarget
|
|
55
|
+
);
|
|
56
|
+
if (!target) {
|
|
57
|
+
return context.cancellationToken.isCancellationRequested || step.missingTarget?.kind === "abort" ? {
|
|
58
|
+
action: "abort",
|
|
59
|
+
shown: false
|
|
60
|
+
} : {
|
|
61
|
+
action: "skipStep",
|
|
62
|
+
shown: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
await this._waitForTargetReady(context.targetWindow, target);
|
|
66
|
+
if (context.cancellationToken.isCancellationRequested) {
|
|
67
|
+
return {
|
|
68
|
+
action: "abort",
|
|
69
|
+
shown: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const store = ( new DisposableStore());
|
|
73
|
+
try {
|
|
74
|
+
const container = this.layoutService.getContainer(context.targetWindow);
|
|
75
|
+
const overlay = store.add(( new SpotlightOverlay(container)));
|
|
76
|
+
this.hostService.setWindowDimmed(context.targetWindow, true);
|
|
77
|
+
store.add(
|
|
78
|
+
toDisposable(() => this.hostService.setWindowDimmed(context.targetWindow, false))
|
|
79
|
+
);
|
|
80
|
+
store.add(this.layoutService.onDidLayoutContainer(() => overlay.scheduleLayout()));
|
|
81
|
+
const end = await this._runStep(
|
|
82
|
+
overlay,
|
|
83
|
+
context,
|
|
84
|
+
step,
|
|
85
|
+
target,
|
|
86
|
+
context.visualStepIndex,
|
|
87
|
+
context.visualStepCount,
|
|
88
|
+
context.canGoBack,
|
|
89
|
+
context.isLastVisualStep
|
|
90
|
+
);
|
|
91
|
+
overlay.hide();
|
|
92
|
+
switch (end.action) {
|
|
93
|
+
case "next":
|
|
94
|
+
return {
|
|
95
|
+
action: "next",
|
|
96
|
+
shown: true,
|
|
97
|
+
dismissReason: end.via === "target" ? OnboardingDismissReason.TargetClick : OnboardingDismissReason.Completed
|
|
98
|
+
};
|
|
99
|
+
case "back":
|
|
100
|
+
return {
|
|
101
|
+
action: "back",
|
|
102
|
+
shown: true
|
|
103
|
+
};
|
|
104
|
+
case "skip":
|
|
105
|
+
return {
|
|
106
|
+
action: "skipSequence",
|
|
107
|
+
shown: true,
|
|
108
|
+
dismissReason: end.reason
|
|
109
|
+
};
|
|
110
|
+
case "abort":
|
|
111
|
+
return {
|
|
112
|
+
action: "abort",
|
|
113
|
+
shown: true
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
store.dispose();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async _runPayload(payload, context) {
|
|
27
121
|
const steps = payload?.steps ?? [];
|
|
28
122
|
const stepCount = steps.length;
|
|
29
123
|
if (stepCount === 0) {
|
|
@@ -37,6 +131,7 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
37
131
|
}
|
|
38
132
|
let lastStepIndex = 0;
|
|
39
133
|
let shown = false;
|
|
134
|
+
const skippedStepIndexes = ( new Set());
|
|
40
135
|
const store = ( new DisposableStore());
|
|
41
136
|
try {
|
|
42
137
|
const container = this.layoutService.getContainer(context.targetWindow);
|
|
@@ -46,8 +141,10 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
46
141
|
toDisposable(() => this.hostService.setWindowDimmed(context.targetWindow, false))
|
|
47
142
|
);
|
|
48
143
|
let aborted = false;
|
|
144
|
+
const targetResolutionCancellation = store.add(( new CancellationTokenSource()));
|
|
49
145
|
store.add(context.onAbort(() => {
|
|
50
146
|
aborted = true;
|
|
147
|
+
targetResolutionCancellation.cancel();
|
|
51
148
|
}));
|
|
52
149
|
store.add(this.layoutService.onDidLayoutContainer(() => overlay.scheduleLayout()));
|
|
53
150
|
let index = 0;
|
|
@@ -55,6 +152,7 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
55
152
|
while (index >= 0 && index < stepCount && !aborted) {
|
|
56
153
|
const step = steps[index];
|
|
57
154
|
if (step.when && !this.contextKeyService.contextMatchesRules(step.when)) {
|
|
155
|
+
skippedStepIndexes.add(index);
|
|
58
156
|
index += direction;
|
|
59
157
|
continue;
|
|
60
158
|
}
|
|
@@ -66,21 +164,36 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
66
164
|
if (aborted) {
|
|
67
165
|
break;
|
|
68
166
|
}
|
|
69
|
-
const target = await this._resolveTarget(
|
|
167
|
+
const target = await this._resolveTarget(
|
|
168
|
+
context.targetWindow,
|
|
169
|
+
step.targetId,
|
|
170
|
+
targetResolutionCancellation.token,
|
|
171
|
+
step.missingTarget
|
|
172
|
+
);
|
|
70
173
|
if (aborted) {
|
|
71
174
|
break;
|
|
72
175
|
}
|
|
73
176
|
if (!target) {
|
|
177
|
+
if (step.missingTarget?.kind === "abort") {
|
|
178
|
+
aborted = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
skippedStepIndexes.add(index);
|
|
74
182
|
index += direction;
|
|
75
183
|
continue;
|
|
76
184
|
}
|
|
185
|
+
skippedStepIndexes.delete(index);
|
|
77
186
|
await this._waitForTargetReady(context.targetWindow, target);
|
|
78
187
|
if (aborted) {
|
|
79
188
|
break;
|
|
80
189
|
}
|
|
81
190
|
lastStepIndex = Math.max(lastStepIndex, index);
|
|
82
191
|
shown = true;
|
|
83
|
-
const
|
|
192
|
+
const skippedBefore = Array.from(skippedStepIndexes).filter(skippedIndex => skippedIndex < index).length;
|
|
193
|
+
const displayStepIndex = index - skippedBefore;
|
|
194
|
+
const displayStepCount = stepCount - skippedStepIndexes.size;
|
|
195
|
+
const end = await this._runStep(overlay, context, step, target, displayStepIndex, displayStepCount);
|
|
196
|
+
overlay.hide();
|
|
84
197
|
switch (end.action) {
|
|
85
198
|
case "next":
|
|
86
199
|
if (index === stepCount - 1) {
|
|
@@ -118,13 +231,16 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
118
231
|
};
|
|
119
232
|
}
|
|
120
233
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
234
|
+
if (aborted) {
|
|
235
|
+
return {
|
|
236
|
+
outcome: OnboardingOutcome.Aborted,
|
|
237
|
+
shown,
|
|
238
|
+
dismissReason: OnboardingDismissReason.Aborted,
|
|
239
|
+
lastStepIndex,
|
|
240
|
+
stepCount
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
128
244
|
outcome: OnboardingOutcome.Completed,
|
|
129
245
|
shown,
|
|
130
246
|
dismissReason: OnboardingDismissReason.Completed,
|
|
@@ -135,11 +251,25 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
135
251
|
store.dispose();
|
|
136
252
|
}
|
|
137
253
|
}
|
|
138
|
-
async _resolveTarget(targetWindow, targetId) {
|
|
139
|
-
|
|
254
|
+
async _resolveTarget(targetWindow, targetId, cancellationToken, behavior) {
|
|
255
|
+
if (cancellationToken.isCancellationRequested) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
140
258
|
let element = findOnboardingTarget(targetWindow, targetId);
|
|
141
|
-
|
|
142
|
-
|
|
259
|
+
if (element || behavior?.kind === "skip" || behavior?.kind === "abort") {
|
|
260
|
+
return element;
|
|
261
|
+
}
|
|
262
|
+
const timeoutMs = behavior?.kind === "wait" ? Math.max(0, behavior.timeoutMs) : TARGET_RESOLVE_TIMEOUT;
|
|
263
|
+
const deadline = Date.now() + timeoutMs;
|
|
264
|
+
while (!element && Date.now() < deadline && !cancellationToken.isCancellationRequested) {
|
|
265
|
+
try {
|
|
266
|
+
await timeout(TARGET_POLL_INTERVAL, cancellationToken);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (cancellationToken.isCancellationRequested) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
143
273
|
element = findOnboardingTarget(targetWindow, targetId);
|
|
144
274
|
}
|
|
145
275
|
return element;
|
|
@@ -162,42 +292,85 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
|
|
|
162
292
|
}
|
|
163
293
|
return animations;
|
|
164
294
|
}
|
|
165
|
-
_runStep(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
295
|
+
async _runStep(
|
|
296
|
+
overlay,
|
|
297
|
+
context,
|
|
298
|
+
step,
|
|
299
|
+
target,
|
|
300
|
+
index,
|
|
301
|
+
stepCount,
|
|
302
|
+
canGoBack = index > 0,
|
|
303
|
+
isLastStep = index === stepCount - 1
|
|
304
|
+
) {
|
|
305
|
+
const stepStore = ( new DisposableStore());
|
|
306
|
+
let ended = false;
|
|
307
|
+
let resolveStep;
|
|
308
|
+
const result = ( new Promise(resolve => resolveStep = resolve));
|
|
309
|
+
const done = end => {
|
|
310
|
+
if (ended) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
ended = true;
|
|
314
|
+
stepStore.dispose();
|
|
315
|
+
resolveStep(end);
|
|
316
|
+
};
|
|
317
|
+
stepStore.add(overlay.onDidClickNext(via => done({
|
|
318
|
+
action: "next",
|
|
319
|
+
via
|
|
320
|
+
})));
|
|
321
|
+
stepStore.add(overlay.onDidClickPrevious(() => done({
|
|
322
|
+
action: "back"
|
|
323
|
+
})));
|
|
324
|
+
stepStore.add(overlay.onDidSkip(reason => done({
|
|
325
|
+
action: "skip",
|
|
326
|
+
reason
|
|
327
|
+
})));
|
|
328
|
+
stepStore.add(context.onAbort(() => done({
|
|
329
|
+
action: "abort"
|
|
330
|
+
})));
|
|
331
|
+
const content = {
|
|
332
|
+
title: step.title,
|
|
333
|
+
description: step.description,
|
|
334
|
+
nextButtonLabel: step.nextButtonLabel,
|
|
335
|
+
stepIndex: index,
|
|
336
|
+
stepCount,
|
|
337
|
+
canGoBack,
|
|
338
|
+
isLastStep
|
|
339
|
+
};
|
|
340
|
+
overlay.show(target, content, {
|
|
341
|
+
placement: step.placement,
|
|
342
|
+
allowTargetInteraction: step.allowTargetInteraction,
|
|
343
|
+
advanceOnTargetClick: step.advanceOnTargetClick,
|
|
344
|
+
hideNext: step.advanceWhen ? true : step.hideNext,
|
|
345
|
+
targetOverlayVisible: step.openTarget,
|
|
346
|
+
padding: step.padding
|
|
347
|
+
});
|
|
348
|
+
context.onDidShow?.();
|
|
349
|
+
if (step.advanceWhen) {
|
|
350
|
+
const keys = ( new Set(( step.advanceWhen.keys())));
|
|
351
|
+
const advanceIfSatisfied = () => {
|
|
352
|
+
if (this.contextKeyService.contextMatchesRules(step.advanceWhen)) {
|
|
353
|
+
done({
|
|
354
|
+
action: "next",
|
|
355
|
+
via: "condition"
|
|
356
|
+
});
|
|
357
|
+
}
|
|
193
358
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
359
|
+
stepStore.add(this.contextKeyService.onDidChangeContext(event => {
|
|
360
|
+
if (event.affectsSome(keys)) {
|
|
361
|
+
advanceIfSatisfied();
|
|
362
|
+
}
|
|
363
|
+
}));
|
|
364
|
+
advanceIfSatisfied();
|
|
365
|
+
}
|
|
366
|
+
if (step.openTarget && !ended) {
|
|
367
|
+
try {
|
|
368
|
+
await openOnboardingTarget(target);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
onUnexpectedError(error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
201
374
|
}
|
|
202
375
|
};
|
|
203
376
|
SpotlightPresentation = ( __decorate([( __param(0, IWorkbenchLayoutService)), ( __param(1, IHostService)), ( __param(2, IContextKeyService))], SpotlightPresentation));
|
|
@@ -4,6 +4,17 @@ import { ContextKeyExpression } from "@codingame/monaco-vscode-api/vscode/vs/pla
|
|
|
4
4
|
export declare const SPOTLIGHT_PRESENTATION_KIND = "spotlight";
|
|
5
5
|
/** Preferred placement of the callout relative to the spotlighted target. */
|
|
6
6
|
export type SpotlightPlacement = "above" | "below" | "left" | "right" | "auto";
|
|
7
|
+
/** `advanceOnly` consumes target activation instead of running the target's action. */
|
|
8
|
+
export type SpotlightTargetClickBehavior = boolean | "advanceOnly";
|
|
9
|
+
/** Behavior when a spotlight target is not rendered when its step is reached. */
|
|
10
|
+
export type SpotlightMissingTargetBehavior = {
|
|
11
|
+
readonly kind: "skip";
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "abort";
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "wait";
|
|
16
|
+
readonly timeoutMs: number;
|
|
17
|
+
};
|
|
7
18
|
/**
|
|
8
19
|
* A single step in a spotlight tour. Steps are pure data; the spotlight
|
|
9
20
|
* presentation turns them into the dim overlay, the cut-out highlight and the
|
|
@@ -22,17 +33,24 @@ export interface ISpotlightStep {
|
|
|
22
33
|
readonly title: string;
|
|
23
34
|
/** Callout body (localized string or markdown). */
|
|
24
35
|
readonly description: string | IMarkdownString;
|
|
36
|
+
/** Localized primary button label, replacing the default Next or Done. */
|
|
37
|
+
readonly nextButtonLabel?: string;
|
|
25
38
|
/** Preferred placement of the callout. Defaults to `'auto'`. */
|
|
26
39
|
readonly placement?: SpotlightPlacement;
|
|
27
40
|
/** When present and unsatisfied, the step is skipped. */
|
|
28
41
|
readonly when?: ContextKeyExpression;
|
|
42
|
+
/** Defaults to waiting two seconds before skipping; `abort` ends the run immediately if the target is missing. */
|
|
43
|
+
readonly missingTarget?: SpotlightMissingTargetBehavior;
|
|
44
|
+
/** Opens or expands the target through its owner before the step begins. */
|
|
45
|
+
readonly openTarget?: boolean;
|
|
29
46
|
/** Allow the spotlighted element to remain interactive. Defaults to `false`. */
|
|
30
47
|
readonly allowTargetInteraction?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
/** Advances on target activation; `advanceOnly` consumes the activation without running its action. */
|
|
49
|
+
readonly advanceOnTargetClick?: SpotlightTargetClickBehavior;
|
|
50
|
+
/** Overrides hiding Next when advancing on target clicks. `advanceWhen` always hides Next. */
|
|
51
|
+
readonly hideNext?: boolean;
|
|
52
|
+
/** Hides Next and advances once this context expression becomes satisfied. */
|
|
53
|
+
readonly advanceWhen?: ContextKeyExpression;
|
|
36
54
|
/** Extra padding (px) around the target when cutting the highlight hole. */
|
|
37
55
|
readonly padding?: number;
|
|
38
56
|
/**
|
|
@@ -8,6 +8,10 @@ import { IOnboardingRunResult, IOnboardingScenario } from "./onboardingScenario.
|
|
|
8
8
|
export interface IOnboardingRunContext {
|
|
9
9
|
/** The window hosting the UI the scenario targets (usually the main window). */
|
|
10
10
|
readonly targetWindow: Window;
|
|
11
|
+
/**
|
|
12
|
+
* Reports that the presentation rendered its first visible element. Called at most once per run.
|
|
13
|
+
*/
|
|
14
|
+
readonly onDidShow?: () => void;
|
|
11
15
|
/**
|
|
12
16
|
* Fires when the engine wants the presentation to abort the current run
|
|
13
17
|
* (e.g. the application is shutting down). The presentation should resolve
|
|
@@ -39,17 +39,17 @@ export interface IOnboardingPresentationRef<TPayload = unknown> {
|
|
|
39
39
|
* onboarding would be shown — otherwise the scorecard counts activity from before the
|
|
40
40
|
* experiment could have had any effect.
|
|
41
41
|
*
|
|
42
|
-
* The treatment flags that tell the client *which*
|
|
42
|
+
* The treatment flags that tell the client *which* variant belongs to a given tour resolve
|
|
43
43
|
* asynchronously, after the assignment context has already been committed to telemetry, so
|
|
44
44
|
* they are too late to block the very first events. This prefix solves that race: because it
|
|
45
45
|
* is a *static, well-known* string, the client can pre-emptively exclude **any**
|
|
46
46
|
* assignment-context entry that starts with it from the very first event, and then
|
|
47
|
-
* selectively unblock the one specific
|
|
47
|
+
* selectively unblock the one specific variant once the user reaches the onboarding moment.
|
|
48
48
|
*
|
|
49
49
|
* ### Contract for experiment authors
|
|
50
|
-
* - Every onboarding experiment configured in ExP MUST
|
|
51
|
-
*
|
|
52
|
-
* -
|
|
50
|
+
* - Every onboarding experiment configured in ExP MUST use variant names beginning with this
|
|
51
|
+
* prefix.
|
|
52
|
+
* - Each arm's variant name MUST be returned by the experiment's
|
|
53
53
|
* {@link IOnboardingExperiment.assignmentContextIdFlag} treatment flag.
|
|
54
54
|
*
|
|
55
55
|
* The engine enforces this defensively: an id that lacks the prefix is rejected (the experiment
|
|
@@ -77,9 +77,8 @@ export interface IOnboardingExperiment {
|
|
|
77
77
|
*/
|
|
78
78
|
readonly behaviorFlag: string;
|
|
79
79
|
/**
|
|
80
|
-
* Name of the string treatment flag whose value is
|
|
81
|
-
*
|
|
82
|
-
* property and that the scorecard keys on. The value MUST start with
|
|
80
|
+
* Name of the string treatment flag whose value is the current arm's ExP variant name. The
|
|
81
|
+
* telemetry entry appends `:<allocationId>`; the value itself MUST start with
|
|
83
82
|
* {@link ONBOARDING_ASSIGNMENT_CONTEXT_PREFIX}; an id without the prefix is rejected (the
|
|
84
83
|
* experiment is treated as inactive) so it can never leak into telemetry ungated.
|
|
85
84
|
*/
|
|
@@ -115,6 +114,8 @@ export interface IOnboardingScenario<TPayload = unknown> {
|
|
|
115
114
|
* {@link IOnboardingExperiment}.
|
|
116
115
|
*/
|
|
117
116
|
readonly experiment?: IOnboardingExperiment;
|
|
117
|
+
/** Variations exposed through the onboarding developer-mode variation override. */
|
|
118
|
+
readonly developerModeVariations?: readonly string[];
|
|
118
119
|
/** When `true`, the scenario runs on every eligible session instead of once per user. */
|
|
119
120
|
readonly repeatable?: boolean;
|
|
120
121
|
}
|
|
@@ -26,6 +26,8 @@ export declare const ONBOARDING_ENABLED_CONFIG = "onboarding.enabled";
|
|
|
26
26
|
* available ids are discoverable (and toggleable) in the settings editor.
|
|
27
27
|
*/
|
|
28
28
|
export declare const ONBOARDING_DEVELOPER_MODE_CONFIG = "onboarding.developerMode";
|
|
29
|
+
/** Developer override for a scenario's experiment-selected variation. */
|
|
30
|
+
export declare const ONBOARDING_DEVELOPER_MODE_VARIATIONS_CONFIG = "onboarding.developerModeVariations";
|
|
29
31
|
/**
|
|
30
32
|
* The shape of the {@link ONBOARDING_DEVELOPER_MODE_CONFIG} setting: a map of
|
|
31
33
|
* scenario/tour id to whether developer mode is enabled for that scenario.
|
|
@@ -33,9 +35,14 @@ export declare const ONBOARDING_DEVELOPER_MODE_CONFIG = "onboarding.developerMod
|
|
|
33
35
|
export type OnboardingDeveloperMode = {
|
|
34
36
|
readonly [scenarioId: string]: boolean;
|
|
35
37
|
};
|
|
38
|
+
/** The shape of the {@link ONBOARDING_DEVELOPER_MODE_VARIATIONS_CONFIG} setting. */
|
|
39
|
+
export type OnboardingDeveloperModeVariations = {
|
|
40
|
+
readonly [scenarioId: string]: string;
|
|
41
|
+
};
|
|
36
42
|
/**
|
|
37
43
|
* Whether onboarding developer mode is enabled for the given scenario/tour id.
|
|
38
44
|
* Reads {@link ONBOARDING_DEVELOPER_MODE_CONFIG} and returns `true` only when the
|
|
39
45
|
* setting is an object whose entry for `scenarioId` is explicitly `true`.
|
|
40
46
|
*/
|
|
41
47
|
export declare function isOnboardingDeveloperModeEnabled(configurationService: IConfigurationService, scenarioId: string): boolean;
|
|
48
|
+
export declare function getOnboardingDeveloperModeVariation(configurationService: IConfigurationService, scenarioId: string): string | undefined;
|