diff --git a/docs/writing-docs/tutorials/control-options.md b/docs/writing-docs/tutorials/control-options.md
index 1511497d5778..025be0e4b473 100644
--- a/docs/writing-docs/tutorials/control-options.md
+++ b/docs/writing-docs/tutorials/control-options.md
@@ -62,6 +62,14 @@ For text-based tutorials, you can choose to hide the toolbox altogether. This is
### @hideToolbox true
```
+### Hide Done
+
+If you do not wish for your tutorial's final step to display a "Done" button, which sends the user back to the main editor, you can hide it by specifying **@hideDone** in the metadata. The default is ``false``.
+
+```
+### @hideDone true
+```
+
## Special blocks
### Templates
diff --git a/localtypings/pxtarget.d.ts b/localtypings/pxtarget.d.ts
index feb1d9700a61..d0f1f29e7f15 100644
--- a/localtypings/pxtarget.d.ts
+++ b/localtypings/pxtarget.d.ts
@@ -1205,6 +1205,7 @@ declare namespace pxt.tutorial {
codeStop?: string; // command to run when code stops (MINECRAFT HOC ONLY)
autoexpandOff?: boolean; // INTERNAL TESTING ONLY
preferredEditor?: string; // preferred editor for opening the tutorial
+ hideDone?: boolean; // Do not show a "Done" button at the end of the tutorial
}
interface TutorialBlockConfigEntry {
diff --git a/package.json b/package.json
index d145f350562b..fb191be60c2d 100644
--- a/package.json
+++ b/package.json
@@ -164,6 +164,7 @@
"test:err": "gulp testerr",
"test:fmt": "gulp testfmt",
"test:lang": "gulp testlang",
+ "test:tutorials": "gulp testtutorials",
"update": "gulp update",
"watch-streamer": "cd docs/static/streamer && tsc -t es6 --watch",
"prepare": "node ./scripts/npm-prepare.js",
diff --git a/tests/tutorial-test/baselines/hideDone.json b/tests/tutorial-test/baselines/hideDone.json
new file mode 100644
index 000000000000..a46227a2e28a
--- /dev/null
+++ b/tests/tutorial-test/baselines/hideDone.json
@@ -0,0 +1,23 @@
+{
+ "editor": "blocksprj",
+ "title": "Hide Done",
+ "steps": [
+ {
+ "contentMd": "Tutorials can choose to hide the done button on the final step. This metadata is parsed and removed.",
+ "headerContentMd": "Tutorials can choose to hide the done button on the final step. This metadata is parsed and removed."
+ },
+ {
+ "contentMd": "Tutorial parsing for hints, steps, etc should function exactly as before.\n\n```blocks\nlet x = 8;\nlet y = x + 2;\n```",
+ "headerContentMd": "Tutorial parsing for hints, steps, etc should function exactly as before.",
+ "hintContentMd": "```blocks\nlet x = 8;\nlet y = x + 2;\n```"
+ }
+ ],
+ "activities": null,
+ "code": [
+ "{\nlet x = 8;\nlet y = x + 2;\n}",
+ "{\nbasic.showIcon(IconNames.Square)\n}"
+ ],
+ "metadata": {
+ "hideDone": true
+ }
+}
diff --git a/tests/tutorial-test/baselines/hideToolbox.json b/tests/tutorial-test/baselines/hideToolbox.json
new file mode 100644
index 000000000000..02a569e2a970
--- /dev/null
+++ b/tests/tutorial-test/baselines/hideToolbox.json
@@ -0,0 +1,23 @@
+{
+ "editor": "blocksprj",
+ "title": "Hide Toolbox",
+ "steps": [
+ {
+ "contentMd": "Tutorials can choose to hide the toolbox. This metadata is parsed and removed.",
+ "headerContentMd": "Tutorials can choose to hide the toolbox. This metadata is parsed and removed."
+ },
+ {
+ "contentMd": "Tutorial parsing for hints, steps, etc should function exactly as before.\n\n```blocks\nlet x = 8;\nlet y = x + 2;\n```",
+ "headerContentMd": "Tutorial parsing for hints, steps, etc should function exactly as before.",
+ "hintContentMd": "```blocks\nlet x = 8;\nlet y = x + 2;\n```"
+ }
+ ],
+ "activities": null,
+ "code": [
+ "{\nlet x = 8;\nlet y = x + 2;\n}",
+ "{\nbasic.showIcon(IconNames.Square)\n}"
+ ],
+ "metadata": {
+ "hideToolbox": true
+ }
+}
diff --git a/tests/tutorial-test/cases/hideDone.md b/tests/tutorial-test/cases/hideDone.md
new file mode 100644
index 000000000000..6596085a2ff4
--- /dev/null
+++ b/tests/tutorial-test/cases/hideDone.md
@@ -0,0 +1,20 @@
+# Hide Done
+
+### @hideDone true
+
+## Introduction
+
+Tutorials can choose to hide the done button on the final step. This metadata is parsed and removed.
+
+## Step with hint
+
+Tutorial parsing for hints, steps, etc should function exactly as before.
+
+```blocks
+let x = 8;
+let y = x + 2;
+```
+
+```ghost
+basic.showIcon(IconNames.Square)
+```
diff --git a/tests/tutorial-test/cases/hideToolbox.md b/tests/tutorial-test/cases/hideToolbox.md
new file mode 100644
index 000000000000..4bf31af59ad6
--- /dev/null
+++ b/tests/tutorial-test/cases/hideToolbox.md
@@ -0,0 +1,20 @@
+# Hide Toolbox
+
+### @hideToolbox true
+
+## Introduction
+
+Tutorials can choose to hide the toolbox. This metadata is parsed and removed.
+
+## Step with hint
+
+Tutorial parsing for hints, steps, etc should function exactly as before.
+
+```blocks
+let x = 8;
+let y = x + 2;
+```
+
+```ghost
+basic.showIcon(IconNames.Square)
+```
diff --git a/webapp/src/components/tutorial/TutorialContainer.tsx b/webapp/src/components/tutorial/TutorialContainer.tsx
index 4f2d26957bad..e3b2f995b1c3 100644
--- a/webapp/src/components/tutorial/TutorialContainer.tsx
+++ b/webapp/src/components/tutorial/TutorialContainer.tsx
@@ -49,7 +49,7 @@ export function TutorialContainer(props: TutorialContainerProps) {
const showBack = currentStep !== 0;
const showNext = currentStep !== steps.length - 1;
- const showDone = !showNext && !pxt.appTarget.appTheme.lockedEditor && !hideIteration;
+ const isDone = !showNext && !pxt.appTarget.appTheme.lockedEditor && !hideIteration;
const showImmersiveReader = pxt.appTarget.appTheme.immersiveReader;
const isHorizontal = props.tutorialSimSidebar || pxt.BrowserUtils.isTabletSize();
@@ -235,10 +235,11 @@ export function TutorialContainer(props: TutorialContainerProps) {
})
}
+ const hideDone = tutorialOptions.metadata?.hideDone;
const doneButtonLabel = lf("Finish the tutorial.");
const nextButtonLabel = lf("Go to the next step of the tutorial.");
- const nextButton = showDone
- ?
+ const nextButton = isDone
+ ? hideDone ? null :
: