Skip to content

Commit b057fec

Browse files
authored
Error Help - Part 1 (#10590)
This is the first set of front-end changes for the "Error Help" feature. It adds a "Help me understand" button to the error list, which sends a request to our backend for assistance on the current errors in the error list. In blocks, it then displays help in the form of an editor tour. In text, it simply adds a small chunk of explanatory text to the top of the error list.
1 parent a5e9bab commit b057fec

File tree

13 files changed

+567
-85
lines changed

13 files changed

+567
-85
lines changed

localtypings/pxtarget.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ declare namespace pxt {
504504
errorList?: boolean; // error list experiment
505505
embedBlocksInSnapshot?: boolean; // embed blocks xml in right-click snapshot
506506
blocksErrorList?: boolean; // blocks error list
507+
aiErrorHelp?: boolean; // Enable AI assistance for errors
507508
identity?: boolean; // login with identity providers
508509
assetEditor?: boolean; // enable asset editor view (in blocks/text toggle)
509510
disableMemoryWorkspaceWarning?: boolean; // do not warn the user when switching to in memory workspace
@@ -1342,10 +1343,17 @@ declare namespace pxt.tour {
13421343
interface BubbleStep {
13431344
title: string;
13441345
description: string;
1345-
targetQuery: string;
1346+
targetQuery?: string;
13461347
location: BubbleLocation;
13471348
sansQuery?: string; // Use this to exclude an element from the cutout
13481349
sansLocation?: BubbleLocation; // relative location of element to exclude
1350+
onStepBegin?: () => void;
1351+
bubbleStyle?: "yellow"; // Currently just have default (unset) & yellow styles. May add more in the future...
1352+
}
1353+
interface TourConfig {
1354+
steps: BubbleStep[];
1355+
showConfetti?: boolean;
1356+
numberFinalStep?: boolean; // The last step will only be included in the step count if this is true.
13491357
}
13501358
const enum BubbleLocation {
13511359
Above,

localtypings/pxteditor.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -820,10 +820,11 @@ declare namespace pxt.editor {
820820
screenshoting?: boolean;
821821
extensionsVisible?: boolean;
822822
isMultiplayerGame?: boolean; // Arcade: Does the current project contain multiplayer blocks?
823-
onboarding?: pxt.tour.BubbleStep[];
823+
activeTourConfig?: pxt.tour.TourConfig;
824824
navigateRegions?: boolean;
825825
feedback?: FeedbackState;
826826
themePickerOpen?: boolean;
827+
errorListNote?: string;
827828
}
828829

829830
export interface EditorState {
@@ -1058,7 +1059,8 @@ declare namespace pxt.editor {
10581059
showLightbox(): void;
10591060
hideLightbox(): void;
10601061
showOnboarding(): void;
1061-
hideOnboarding(): void;
1062+
showTour(config: pxt.tour.TourConfig): void;
1063+
closeTour(): void;
10621064
showNavigateRegions(): void;
10631065
hideNavigateRegions(): void;
10641066
showKeymap(show: boolean): void;
@@ -1073,7 +1075,7 @@ declare namespace pxt.editor {
10731075
showFeedbackDialog(kind: ocv.FeedbackKind): void;
10741076
showTurnBackTimeDialogAsync(): Promise<void>;
10751077

1076-
showLoginDialog(continuationHash?: string): void;
1078+
showLoginDialog(continuationHash?: string, dialogMessages?: { signInMessage?: string; signUpMessage?: string }): void;
10771079
showProfileDialog(location?: string): void;
10781080

10791081
showImportUrlDialog(): void;

react-common/components/controls/TeachingBubble.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ export interface TeachingBubbleProps extends ContainerProps {
2020
targetContent: pxt.tour.BubbleStep;
2121
stepNumber: number;
2222
totalSteps: number;
23+
hasPrevious: boolean;
24+
hasNext: boolean;
2325
onClose: () => void;
2426
parentElement?: Element;
2527
activeTarget?: boolean; // if true, the target is clickable
28+
showConfetti?: boolean;
29+
forceHideSteps?: boolean;
2630
onNext: () => void;
2731
onBack: () => void;
2832
onFinish: () => void;
@@ -44,7 +48,10 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
4448
stepNumber,
4549
totalSteps,
4650
parentElement,
47-
activeTarget
51+
activeTarget,
52+
forceHideSteps,
53+
hasPrevious,
54+
hasNext
4855
} = props;
4956

5057
const margin = 10;
@@ -57,22 +64,15 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
5764
}
5865

5966
useEffect(() => {
67+
if (targetContent.onStepBegin) {
68+
targetContent.onStepBegin();
69+
}
6070
positionBubbleAndCutout();
61-
setStepsVisibility();
6271
window.addEventListener("resize", positionBubbleAndCutout);
6372
return () => {
6473
window.removeEventListener("resize", positionBubbleAndCutout);
6574
}
66-
}, [stepNumber]);
67-
68-
const setStepsVisibility = () => {
69-
const steps = document.querySelector(".teaching-bubble-steps") as HTMLElement;
70-
if (stepNumber > totalSteps || totalSteps === 1) {
71-
steps.style.visibility = "hidden";
72-
} else {
73-
steps.style.visibility = "visible";
74-
}
75-
}
75+
}, [stepNumber, targetContent]);
7676

7777
const positionBubbleAndCutout = () => {
7878
const bubble = document.getElementById(id);
@@ -82,7 +82,7 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
8282
bubbleArrowOutline.style.border = "none";
8383
const bubbleBounds = bubble.getBoundingClientRect();
8484
let cutoutBounds: CutoutBounds;
85-
if (targetContent.targetQuery === "nothing") {
85+
if (!targetContent.targetQuery || targetContent.targetQuery === "nothing") {
8686
cutoutBounds = {
8787
top: window.innerHeight / 2,
8888
bottom: 0,
@@ -348,8 +348,6 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
348348
element.style.left = left + "px";
349349
}
350350

351-
const hasPrevious = stepNumber > 1;
352-
const hasNext = stepNumber < totalSteps + 1;
353351
const hasSteps = totalSteps > 1;
354352
const closeLabel = lf("Close");
355353
const backLabel = lf("Back");
@@ -358,11 +356,12 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
358356

359357
const classes = classList(
360358
"teaching-bubble-container",
361-
className
359+
className,
360+
targetContent.bubbleStyle
362361
);
363362

364363
return ReactDOM.createPortal(<FocusTrap className={classes} onEscape={onClose}>
365-
{stepNumber === totalSteps + 1 && <Confetti />}
364+
{props.showConfetti && <Confetti />}
366365
<div className="teaching-bubble-cutout" />
367366
<div className="teaching-bubble-arrow" />
368367
<div className="teaching-bubble-arrow-outline" />
@@ -384,7 +383,7 @@ export const TeachingBubble = (props: TeachingBubbleProps) => {
384383
<strong aria-live="polite">{targetContent.title}</strong>
385384
<p aria-live="polite">{targetContent.description}</p>
386385
<div className={`teaching-bubble-footer ${!hasSteps ? "no-steps" : ""}`}>
387-
{hasSteps && <div className="teaching-bubble-steps" aria-live="polite">
386+
{hasSteps && <div className={classList("teaching-bubble-steps", forceHideSteps && "hidden")} aria-live="polite">
388387
{stepNumber} of {totalSteps}
389388
</div>}
390389
<div className="teaching-bubble-navigation">

react-common/styles/onboarding/TeachingBubble.less

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
.teaching-bubble-container {
2+
--teaching-bubble-foreground: var(--pxt-tertiary-foreground);
3+
--teaching-bubble-background: var(--pxt-tertiary-background);
4+
}
5+
6+
.teaching-bubble-container.yellow {
7+
--teaching-bubble-foreground: var(--pxt-colors-yellow-foreground);
8+
--teaching-bubble-background: var(--pxt-colors-yellow-background);
9+
}
10+
111
.teaching-bubble-container {
212
position: fixed;
313
top: 0;
@@ -30,17 +40,16 @@
3040
position: fixed;
3141
max-width: 25rem;
3242
min-width: 18.75rem;
33-
background: var(--pxt-tertiary-background);
34-
color: var(--pxt-tertiary-foreground);
43+
background: var(--teaching-bubble-background);
44+
color: var(--teaching-bubble-foreground);
3545
box-shadow: 0 0 0 0.1rem;
3646
border-radius: 0.5rem;
3747
padding: 1rem;
3848
z-index: @modalDimmerZIndex;
3949

4050
.common-button {
41-
4251
&:focus {
43-
outline: .1rem solid var(--pxt-tertiary-foreground);
52+
outline: 0.1rem solid var(--teaching-bubble-foreground);
4453
filter: grayscale(.15) brightness(.85) contrast(1.3);
4554
}
4655
&.tertiary:focus::after, &:focus::after {
@@ -54,15 +63,15 @@
5463
width: 0;
5564
height: 0;
5665
z-index: @modalDimmerZIndex + 1;
57-
color: var(--pxt-tertiary-background);
66+
color: var(--teaching-bubble-background);
5867
}
5968

6069
.teaching-bubble-arrow-outline {
6170
position: fixed;
6271
width: 0;
6372
height: 0;
6473
z-index: @modalDimmerZIndex;
65-
color: var(--pxt-tertiary-foreground);
74+
color: var(--teaching-bubble-foreground);
6675
}
6776

6877
.teaching-bubble-content {
@@ -81,25 +90,29 @@
8190

8291
.teaching-bubble-steps {
8392
font-size: .9rem;
84-
color: var(--pxt-tertiary-foreground);
93+
color: var(--teaching-bubble-foreground);
94+
95+
&.hide {
96+
visibility: hidden;
97+
}
8598
}
8699

87100
.common-button.tertiary {
88101
padding: .25rem .5rem;
89102
margin: 0 .5rem 0 0;
90-
border: .1rem solid var(--pxt-tertiary-foreground);
91-
background: var(--pxt-tertiary-background);
92-
color: var(--pxt-tertiary-foreground);
103+
border: 0.1rem solid var(--teaching-bubble-foreground) !important;
104+
background: var(--teaching-bubble-background) !important;
105+
color: var(--teaching-bubble-foreground) !important;
93106

94107
&.inverted {
95108
margin-right: 0;
96-
background: var(--pxt-tertiary-foreground);
97-
color: var(--pxt-tertiary-background);
109+
background: var(--teaching-bubble-foreground) !important;
110+
color: var(--teaching-bubble-background) !important;
98111
}
99112
}
100113

101114
.common-button:focus-visible {
102-
outline: 2px solid var(--pxt-tertiary-foreground);
115+
outline: 2px solid var(--teaching-bubble-foreground);
103116
outline-offset: 3px;
104117
}
105118

@@ -114,7 +127,7 @@
114127
top: 0.5rem;
115128
padding: 0.5rem 0 0.25rem 0;
116129
background: transparent;
117-
color: var(--pxt-tertiary-foreground);
130+
color: var(--teaching-bubble-foreground);
118131
margin: 0;
119132

120133
i.right {

theme/errorList.less

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
padding: 0.75em 1em;
1616
height: @errorListHeaderHeight;
1717
margin-bottom: 0;
18+
display: flex;
19+
flex-direction: row;
20+
justify-content: flex-start;
21+
align-items: center;
1822

1923
&:hover {
2024
cursor: pointer;
@@ -38,6 +42,39 @@
3842
font-size: 1.4em;
3943
}
4044

45+
.error-help-container {
46+
margin: 0 1rem;
47+
48+
.error-help-button {
49+
padding: 0.7rem;
50+
line-height: 0.8rem;
51+
}
52+
53+
.error-help-loader {
54+
display: flex;
55+
flex-direction: row;
56+
justify-content: flex-start;
57+
align-items: center;
58+
width: fit-content;
59+
color: var(--pxt-secondary-background); // Inverted to match the button bkg color
60+
cursor: progress;
61+
62+
// Match button
63+
padding: 0.7rem;
64+
font-weight: 400;
65+
line-height: 0.8rem;
66+
font-size: 16px;
67+
68+
.analyzing-label {
69+
margin: 0 1rem;
70+
}
71+
}
72+
}
73+
74+
.filler {
75+
flex-grow: 1;
76+
}
77+
4178
.toggleButton {
4279
float: right;
4380
position: relative;
@@ -108,6 +145,14 @@
108145
}
109146
}
110147
}
148+
149+
.note {
150+
margin: 1rem;
151+
padding: 0.5rem;
152+
border-radius: 0.2rem;
153+
background-color: var(--pxt-neutral-alpha10);
154+
white-space: pre-line; // Preserve new lines
155+
}
111156
}
112157

113158
// collapsed error list

webapp/src/app.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export class ProjectView
211211
simState: SimState.Stopped,
212212
autoRun: this.autoRunOnStart(),
213213
isMultiplayerGame: false,
214-
onboarding: undefined,
214+
activeTourConfig: undefined,
215215
mute: pxt.editor.MuteState.Unmuted,
216216
feedback: {showing: false, kind: "generic"} // state that tracks if the feedback modal is showing and what kind
217217
};
@@ -222,7 +222,7 @@ export class ProjectView
222222
this.hidePackageDialog = this.hidePackageDialog.bind(this);
223223
this.hwDebug = this.hwDebug.bind(this);
224224
this.hideLightbox = this.hideLightbox.bind(this);
225-
this.hideOnboarding = this.hideOnboarding.bind(this);
225+
this.closeTour = this.closeTour.bind(this);
226226
this.hideFeedback = this.hideFeedback.bind(this);
227227
this.openSimSerial = this.openSimSerial.bind(this);
228228
this.openDeviceSerial = this.openDeviceSerial.bind(this);
@@ -244,6 +244,7 @@ export class ProjectView
244244
this.showThemePicker = this.showThemePicker.bind(this);
245245
this.hideThemePicker = this.hideThemePicker.bind(this);
246246
this.setColorThemeById = this.setColorThemeById.bind(this);
247+
this.showLoginDialog = this.showLoginDialog.bind(this);
247248

248249
// add user hint IDs and callback to hint manager
249250
if (pxt.BrowserUtils.useOldTutorialLayout()) this.hintManager.addHint(ProjectView.tutorialCardId, this.tutorialCardHintCallback.bind(this));
@@ -4598,8 +4599,8 @@ export class ProjectView
45984599
}
45994600
}
46004601

4601-
showLoginDialog(continuationHash?: string) {
4602-
this.loginDialog.show(continuationHash);
4602+
showLoginDialog(continuationHash?: string, dialogMessages?: { signInMessage?: string; signUpMessage?: string }) {
4603+
this.loginDialog.show(continuationHash, dialogMessages);
46034604
}
46044605

46054606
showProfileDialog(location?: string) {
@@ -5267,17 +5268,24 @@ export class ProjectView
52675268
}
52685269

52695270
///////////////////////////////////////////////////////////
5270-
//////////// Onboarding /////////////
5271+
//////////// Tours /////////////
52715272
///////////////////////////////////////////////////////////
52725273

5273-
hideOnboarding() {
5274-
this.setState({ onboarding: undefined });
5274+
closeTour() {
5275+
this.setState({ activeTourConfig: undefined });
52755276
}
52765277

52775278
async showOnboarding() {
52785279
const tourSteps: pxt.tour.BubbleStep[] = await parseTourStepsAsync(pxt.appTarget.appTheme?.tours?.editor)
5279-
this.setState({ onboarding: tourSteps })
5280+
const config: pxt.tour.TourConfig = {
5281+
steps: tourSteps,
5282+
showConfetti: true,
5283+
}
5284+
this.showTour(config);
5285+
}
52805286

5287+
async showTour(config: pxt.tour.TourConfig) {
5288+
this.setState({ activeTourConfig: config });
52815289
}
52825290

52835291
///////////////////////////////////////////////////////////
@@ -5554,8 +5562,8 @@ export class ProjectView
55545562
{hideMenuBar ? <div id="editorlogo"><a className="poweredbylogo"></a></div> : undefined}
55555563
{lightbox ? <sui.Dimmer isOpen={true} active={lightbox} portalClassName={'tutorial'} className={'ui modal'}
55565564
shouldFocusAfterRender={false} closable={true} onClose={this.hideLightbox} /> : undefined}
5557-
{this.state.onboarding && <Tour tourSteps={this.state.onboarding} onClose={this.hideOnboarding} />}
55585565
{this.state.navigateRegions && <NavigateRegionsOverlay parent={this}/>}
5566+
{this.state.activeTourConfig && <Tour config={this.state.activeTourConfig} onClose={this.closeTour} />}
55595567
{this.state.themePickerOpen && <ThemePickerModal themes={this.themeManager.getAllColorThemes()} onThemeClicked={theme => this.setColorThemeById(theme?.id, true)} onClose={this.hideThemePicker} />}
55605568
</div>
55615569
);

0 commit comments

Comments
 (0)