Skip to content

Commit 2c49bce

Browse files
tutorial
1 parent 27c7a59 commit 2c49bce

6 files changed

Lines changed: 139 additions & 112 deletions

File tree

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
{
135135
"label": "VSCode: Watch tutorial",
136136
"type": "npm",
137-
"script": "watch:tailwind",
137+
"script": "watch",
138138
"isBackground": true,
139139
"presentation": {
140140
"reveal": "never"
Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import type { TutorialState } from "@cursorless/common";
2-
import type { FunctionComponent } from "preact";
32
import { useEffect, useState } from "preact/hooks";
43
import type { WebviewApi } from "vscode-webview";
5-
import { TutorialStep } from "./TutorialStep";
64
import { Command } from "./Command";
5+
import { TutorialStep } from "./TutorialStep";
76

87
interface Props {
98
vscode: WebviewApi<undefined>;
109
}
1110

12-
export const App: FunctionComponent<Props> = ({ vscode }) => {
11+
export function App({ vscode }: Props) {
1312
const [state, setState] = useState<TutorialState>();
1413

1514
useEffect(() => {
@@ -26,7 +25,7 @@ export const App: FunctionComponent<Props> = ({ vscode }) => {
2625

2726
if (state == null) {
2827
// Just show nothing while we're waiting for initial state
29-
return <></>;
28+
return null;
3029
}
3130

3231
switch (state.type) {
@@ -37,10 +36,11 @@ export const App: FunctionComponent<Props> = ({ vscode }) => {
3736
To start a tutorial, say <Command spokenForm="tutorial <number>" />,
3837
or click one of the following tutorials:
3938
</p>
40-
<ol className="mt-2 list-decimal">
39+
<ol className="mt-2">
4140
{state.tutorials.map((tutorial) => (
4241
<li key={tutorial.id} className="mb-1">
4342
<button
43+
className="btn btn-link p-0 text-decoration-none"
4444
onClick={() =>
4545
vscode.postMessage({
4646
type: "start",
@@ -61,42 +61,40 @@ export const App: FunctionComponent<Props> = ({ vscode }) => {
6161
);
6262

6363
case "doingTutorial":
64-
return state.hasErrors ? (
65-
<div>
66-
<h1 className="text-(--vscode-walkthrough-stepTitle\.foreground)">
67-
Error
68-
</h1>
69-
<p>
70-
{state.requiresTalonUpdate ? (
71-
<>
64+
if (state.hasErrors) {
65+
return (
66+
<div>
67+
<h1 className="has-error">Error</h1>
68+
{state.requiresTalonUpdate && (
69+
<p>
7270
Please{" "}
7371
<a
7472
href="https://www.cursorless.org/docs/user/updating/#updating-the-talon-side"
75-
className="text-blue-400"
73+
className="link-primary"
7674
>
7775
update cursorless-talon
7876
</a>
79-
</>
80-
) : (
81-
""
77+
</p>
8278
)}
83-
</p>
84-
</div>
85-
) : (
86-
<TutorialStep state={state} vscode={vscode} />
87-
);
79+
</div>
80+
);
81+
}
82+
return <TutorialStep state={state} vscode={vscode} />;
8883
}
89-
};
84+
}
9085

91-
const TutorialProgressIndicator: FunctionComponent<{
86+
function TutorialProgressIndicator({
87+
currentStep,
88+
stepCount,
89+
}: {
9290
currentStep: number;
9391
stepCount: number;
94-
}> = ({ currentStep, stepCount }) => {
92+
}) {
9593
if (currentStep === 0) {
9694
return null;
9795
}
9896
if (currentStep === stepCount - 1) {
99-
return <span className="mr-1"></span>;
97+
return <span className="me-1"></span>;
10098
}
101-
return <span className="mr-1">🕗</span>;
102-
};
99+
return <span className="me-1">🕗</span>;
100+
}
Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
1-
import type { FunctionComponent } from "preact";
2-
3-
interface ProgressBarProps {
1+
interface Props {
42
currentStep: number;
53
stepCount: number;
64
}
75

86
/**
97
* A progress bar that shows the current step in a tutorial.
10-
*
11-
* From https://flowbite.com/docs/components/progress/
128
*/
13-
export const ProgressBar: FunctionComponent<ProgressBarProps> = ({
14-
currentStep,
15-
stepCount,
16-
}) => {
9+
export function ProgressBar({ currentStep, stepCount }: Props) {
1710
const progress = ((currentStep + 1) / stepCount) * 100;
1811
return (
19-
<div className="h-2.5 w-full rounded-full bg-(--vscode-welcomePage-progress\.background)">
20-
<div
21-
className="h-2.5 rounded-full bg-(--vscode-welcomePage-progress\.foreground)"
22-
style={{ width: `${progress}%` }}
23-
></div>
12+
<div className="tutorial-progress progress w-100 rounded-pill">
13+
<div className="progress-bar" style={{ width: `${progress}%` }}></div>
2414
</div>
2515
);
26-
};
16+
}
Lines changed: 72 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,105 @@
11
import type { ActiveTutorialNoErrorsState } from "@cursorless/common";
2-
import type { FunctionComponent } from "preact";
32
import type { WebviewApi } from "vscode-webview";
43
import { ArrowLeftIcon } from "./ArrowLeftIcon";
54
import { ArrowRightIcon } from "./ArrowRightIcon";
65
import { CloseIcon } from "./CloseIcon";
76
import { Command } from "./Command";
87
import { ProgressBar } from "./ProgressBar";
98

10-
interface TutorialStepProps {
9+
interface Props {
1110
state: ActiveTutorialNoErrorsState;
1211
vscode: WebviewApi<undefined>;
1312
}
1413

15-
export const TutorialStep: FunctionComponent<TutorialStepProps> = ({
16-
state,
17-
vscode,
18-
}) => {
19-
return (
20-
<div>
21-
<div className="mt-2 mb-2 flex items-center gap-[0.2em]">
14+
export function TutorialStep({ state, vscode }: Props) {
15+
const renderProgress = () => {
16+
return (
17+
<div className="mt-2 mb-2 d-flex align-items-center gap-1">
2218
<ProgressBar
2319
currentStep={state.stepNumber}
2420
stepCount={state.stepCount}
2521
/>
2622
<button
23+
className="btn btn-link p-0 d-inline-flex"
2724
onClick={() =>
2825
vscode.postMessage({
2926
type: "list",
3027
})
3128
}
3229
>
33-
<span>
34-
<CloseIcon />
35-
</span>
30+
<CloseIcon />
3631
</button>
3732
</div>
38-
{state.preConditionsMet ? (
39-
<>
40-
{state.stepContent.map((paragraph, i) => (
41-
<div key={i} className="mt-1">
42-
{paragraph.map((fragment, j) => {
43-
switch (fragment.type) {
44-
case "string":
45-
return <span key={j}>{fragment.value}</span>;
46-
case "command":
47-
return <Command spokenForm={fragment.value} />;
48-
case "term":
49-
return <span>"{fragment.value}"</span>;
50-
default: {
51-
// Ensure we handle all cases
52-
const _unused: never = fragment;
53-
}
54-
}
55-
})}
56-
</div>
57-
))}
58-
<div className="mt-2 flex w-full flex-row justify-between">
59-
<button
60-
onClick={() =>
61-
vscode.postMessage({
62-
type: "previous",
63-
})
64-
}
65-
>
66-
<span>
67-
<ArrowLeftIcon size={12} />
68-
</span>
69-
</button>
70-
<span className="text-2xs">
71-
{state.stepNumber + 1} / {state.stepCount}{" "}
72-
</span>
73-
<button
74-
onClick={() =>
75-
vscode.postMessage({
76-
type: "next",
77-
})
78-
}
79-
>
80-
<span>
81-
<ArrowRightIcon size={12} />
82-
</span>
83-
</button>
84-
</div>
85-
</>
86-
) : (
33+
);
34+
};
35+
36+
const renderStepContent = () => {
37+
if (!state.preConditionsMet) {
38+
return (
8739
<>
8840
<div>Whoops! Looks like you've stepped off the beaten path.</div>
8941
<div className="mt-1">
9042
Feel free to keep playing, then say{" "}
9143
<Command spokenForm="tutorial resume" /> to resume the tutorial.
9244
</div>
9345
</>
94-
)}
95-
</div>
46+
);
47+
}
48+
49+
return (
50+
<>
51+
{state.stepContent.map((paragraph, i) => (
52+
<div key={i} className="mt-1">
53+
{paragraph.map((fragment, j) => {
54+
switch (fragment.type) {
55+
case "string":
56+
return <span key={j}>{fragment.value}</span>;
57+
case "command":
58+
return <Command spokenForm={fragment.value} />;
59+
case "term":
60+
return <span>"{fragment.value}"</span>;
61+
default: {
62+
// Ensure we handle all cases
63+
const _unused: never = fragment;
64+
}
65+
}
66+
})}
67+
</div>
68+
))}
69+
70+
<div className="mt-2 d-flex w-100 align-items-center justify-content-between">
71+
<button
72+
className="btn btn-link p-0 d-inline-flex"
73+
onClick={() =>
74+
vscode.postMessage({
75+
type: "previous",
76+
})
77+
}
78+
>
79+
<ArrowLeftIcon size={12} />
80+
</button>
81+
<span className="tutorial-step-counter">
82+
{state.stepNumber + 1} / {state.stepCount}{" "}
83+
</span>
84+
<button
85+
className="btn btn-link p-0 d-inline-flex"
86+
onClick={() =>
87+
vscode.postMessage({
88+
type: "next",
89+
})
90+
}
91+
>
92+
<ArrowRightIcon size={12} />
93+
</button>
94+
</div>
95+
</>
96+
);
97+
};
98+
99+
return (
100+
<>
101+
{renderProgress()}
102+
{renderStepContent()}
103+
</>
96104
);
97-
};
105+
}
Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1-
@import "tailwindcss";
1+
@import "bootstrap/dist/css/bootstrap.min.css";
22

3-
@config "../tailwind.config.mjs";
3+
:root {
4+
--bs-body-color: var(--vscode-foreground);
5+
--bs-body-bg: var(--vscode-sideBar-background);
6+
}
7+
8+
body {
9+
padding: 0 0.5rem;
10+
}
11+
12+
code,
13+
.btn-link {
14+
color: inherit;
15+
}
16+
17+
.btn-link:hover,
18+
.btn-link:focus-visible {
19+
color: var(--vscode-textLink-activeForeground);
20+
}
21+
22+
.tutorial-progress {
23+
height: 0.625rem;
24+
background-color: var(--vscode-welcomePage-progress\.background);
25+
}
26+
27+
.tutorial-progress > .progress-bar {
28+
background-color: var(--vscode-welcomePage-progress\.foreground);
29+
}
30+
31+
.tutorial-step-counter {
32+
font-size: 0.625rem;
33+
}
434

5-
@theme {
6-
--text-2xs: 0.625rem;
35+
h1.has-error {
36+
color: var(--vscode-walkthrough-stepTitle\.foreground);
737
}

packages/cursorless-vscode-tutorial-webview/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render } from "preact";
22
import { App } from "./App";
3+
import "./index.css";
34

45
render(<App vscode={acquireVsCodeApi()} />, getRoot());
56

0 commit comments

Comments
 (0)