Skip to content

Commit 725efa5

Browse files
authored
adding a few tutorial options and fixing toolbox filters for variables/functions (#10881) (#10882)
1 parent 62951a3 commit 725efa5

5 files changed

Lines changed: 96 additions & 34 deletions

File tree

localtypings/pxtarget.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ declare namespace pxt.tutorial {
12041204
globalBlockConfig?: TutorialBlockConfig; // concatenated `blockconfig.global` sections. Contains block configs applicable to all tutorial steps
12051205
globalValidationConfig?: CodeValidationConfig; // concatenated 'validation.global' sections. Contains validation config applicable to all steps
12061206
simTheme?: Partial<pxt.PackageConfig>;
1207+
hiddenNamespaces?: string[]; // list of categories to put in the toolbox filters of the tutorial project's pxt.json
12071208
}
12081209

12091210
interface TutorialMetadata {
@@ -1219,6 +1220,7 @@ declare namespace pxt.tutorial {
12191220
autoexpandOff?: boolean; // INTERNAL TESTING ONLY
12201221
preferredEditor?: string; // preferred editor for opening the tutorial
12211222
hideDone?: boolean; // Do not show a "Done" button at the end of the tutorial
1223+
hideReplaceMyCode?: boolean; // hide the "Replace Code" button in the tutorial
12221224
}
12231225

12241226
interface TutorialBlockConfigEntry {
@@ -1317,6 +1319,7 @@ declare namespace pxt.tutorial {
13171319
globalBlockConfig?: TutorialBlockConfig; // concatenated `blockconfig.global` sections. Contains block configs applicable to all tutorial steps
13181320
globalValidationConfig?: CodeValidationConfig // concatenated 'validation.global' sections. Contains validation config applicable to all steps
13191321
simTheme?: Partial<pxt.PackageConfig>;
1322+
hiddenNamespaces?: string[]; // list of categories to put in the toolbox filters of the tutorial project's pxt.json
13201323
}
13211324
interface TutorialCompletionInfo {
13221325
// id of the tutorial

pxtlib/tutorial.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ namespace pxt.tutorial {
1919
jres,
2020
assetJson,
2121
customTs,
22-
simThemeJson
22+
simThemeJson,
23+
hiddenNamespaces
2324
} = computeBodyMetadata(body);
2425

2526
// For python HOC, hide the toolbox (we don't support flyoutOnly mode).
@@ -66,12 +67,13 @@ namespace pxt.tutorial {
6667
customTs,
6768
globalBlockConfig,
6869
globalValidationConfig,
69-
simTheme
70+
simTheme,
71+
hiddenNamespaces
7072
};
7173
}
7274

7375
export function getMetadataRegex(): RegExp {
74-
return /``` *(sim|block|blocks|filterblocks|spy|ghost|typescript|ts|js|javascript|template|python|jres|assetjson|customts|simtheme|python-template|ts-template|typescript-template|js-template|javascript-template)\s*\n([\s\S]*?)\n```/gmi;
76+
return /``` *(sim|block|blocks|filterblocks|spy|ghost|typescript|ts|js|javascript|template|python|jres|assetjson|customts|simtheme|python-template|ts-template|typescript-template|js-template|javascript-template|hiddennamespaces)\s*\n([\s\S]*?)\n```/gmi;
7577
}
7678

7779
function computeBodyMetadata(body: string) {
@@ -88,6 +90,7 @@ namespace pxt.tutorial {
8890
let assetJson: string;
8991
let customTs: string;
9092
let simThemeJson: string;
93+
let hiddenNamespaces: string[];
9194
// Concatenate all blocks in separate code blocks and decompile so we can detect what blocks are used (for the toolbox)
9295
body
9396
.replace(/((?!.)\s)+/g, "\n")
@@ -147,6 +150,10 @@ namespace pxt.tutorial {
147150
customTs = m2;
148151
m2 = "";
149152
break;
153+
case "hiddennamespaces":
154+
hiddenNamespaces = (m2 as string).split(/\s/m).map(s => s.trim()).filter(s => !!s);
155+
m2 = "";
156+
break;
150157
}
151158
code.push(language === "python" ? `\n${m2}\n` : `{\n${m2}\n}`);
152159
idx++
@@ -163,7 +170,8 @@ namespace pxt.tutorial {
163170
jres,
164171
assetJson,
165172
customTs,
166-
simThemeJson
173+
simThemeJson,
174+
hiddenNamespaces
167175
};
168176

169177
function checkTutorialEditor(expected: string) {
@@ -387,7 +395,7 @@ ${code}
387395
/* Remove hidden snippets from text */
388396
function stripHiddenSnippets(str: string): string {
389397
if (!str) return str;
390-
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|simtheme|customts|blockconfig\.local|blockconfig\.global|validation\.local|validation\.global)\s*\n([\s\S]*?)\n```/gmi;
398+
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|simtheme|customts|hiddennamespaces|blockconfig\.local|blockconfig\.global|validation\.local|validation\.global)\s*\n([\s\S]*?)\n```/gmi;
391399
return str.replace(hiddenSnippetRegex, '').trim();
392400
}
393401

@@ -479,6 +487,7 @@ ${code}
479487
globalBlockConfig: tutorialInfo.globalBlockConfig,
480488
globalValidationConfig: tutorialInfo.globalValidationConfig,
481489
simTheme: tutorialInfo.simTheme,
490+
hiddenNamespaces: tutorialInfo.hiddenNamespaces,
482491
};
483492

484493
return { options: tutorialOptions, editor: tutorialInfo.editor };

webapp/src/app.tsx

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,7 @@ export class ProjectView
17221722
await this.loadTutorialCustomTsAsync();
17231723
await this.loadTutorialTemplateCodeAsync();
17241724
await this.loadTutorialBlockConfigsAsync();
1725+
await this.loadTutorialHiddenCategoriesAsync();
17251726

17261727
const main = pkg.getEditorPkg(pkg.mainPkg);
17271728

@@ -2017,8 +2018,11 @@ export class ProjectView
20172018
if (!header || !header.tutorial) {
20182019
return;
20192020
}
2020-
else if (!header.tutorial.templateCode || header.tutorial.templateLoaded) {
2021-
if (header.tutorial.mergeCarryoverCode && header.tutorial.mergeHeaderId) {
2021+
const hasCodeCarryover = header.tutorial.mergeCarryoverCode && header.tutorial.mergeHeaderId;
2022+
const hideReplaceMyCode = header.tutorial.metadata?.hideReplaceMyCode || pxt.appTarget.appTheme.hideReplaceMyCode;
2023+
2024+
if (!header.tutorial.templateCode && !(hasCodeCarryover && hideReplaceMyCode) || header.tutorial.templateLoaded) {
2025+
if (hasCodeCarryover) {
20222026
pxt.warn(lf("Refusing to carry code between tutorials because the loaded tutorial \"{0}\" does not contain a template code block.", header.tutorial.tutorial));
20232027
}
20242028
return;
@@ -2029,33 +2033,36 @@ export class ProjectView
20292033
// Mark that the template has been loaded so that we don't overwrite the
20302034
// user code if the tutorial is re-opened
20312035
header.tutorial.templateLoaded = true;
2036+
20322037
let currentText = await workspace.getTextAsync(header.id);
20332038

2034-
// If we're starting in the asset editor, always load into TS
2035-
const preferredEditor = header.tutorial.metadata?.preferredEditor;
2036-
if (preferredEditor && filenameForEditor(preferredEditor) === pxt.ASSETS_FILE) {
2037-
currentText[pxt.MAIN_TS] = template;
2038-
}
2039+
if (template) {
2040+
// If we're starting in the asset editor, always load into TS
2041+
const preferredEditor = header.tutorial.metadata?.preferredEditor;
2042+
if (preferredEditor && filenameForEditor(preferredEditor) === pxt.ASSETS_FILE) {
2043+
currentText[pxt.MAIN_TS] = template;
2044+
}
20392045

2040-
const projectname = projectNameForEditor(preferredEditor || header.editor);
2046+
const projectname = projectNameForEditor(preferredEditor || header.editor);
20412047

2042-
if (projectname === pxt.PYTHON_PROJECT_NAME && header.tutorial.templateLanguage === "python") {
2043-
currentText[pxt.MAIN_PY] = template;
2044-
}
2045-
else if (projectname === pxt.JAVASCRIPT_PROJECT_NAME) {
2046-
currentText[pxt.MAIN_TS] = template;
2047-
}
2048-
else if (projectname === pxt.PYTHON_PROJECT_NAME) {
2049-
const pyCode = await compiler.decompilePythonSnippetAsync(template)
2050-
if (pyCode) {
2051-
currentText[pxt.MAIN_PY] = pyCode;
2048+
if (projectname === pxt.PYTHON_PROJECT_NAME && header.tutorial.templateLanguage === "python") {
2049+
currentText[pxt.MAIN_PY] = template;
20522050
}
2053-
}
2054-
else {
2055-
const resp = await compiler.decompileBlocksSnippetAsync(template)
2056-
const blockXML = resp.outfiles[pxt.MAIN_BLOCKS];
2057-
if (blockXML) {
2058-
currentText[pxt.MAIN_BLOCKS] = blockXML
2051+
else if (projectname === pxt.JAVASCRIPT_PROJECT_NAME) {
2052+
currentText[pxt.MAIN_TS] = template;
2053+
}
2054+
else if (projectname === pxt.PYTHON_PROJECT_NAME) {
2055+
const pyCode = await compiler.decompilePythonSnippetAsync(template)
2056+
if (pyCode) {
2057+
currentText[pxt.MAIN_PY] = pyCode;
2058+
}
2059+
}
2060+
else {
2061+
const resp = await compiler.decompileBlocksSnippetAsync(template)
2062+
const blockXML = resp.outfiles[pxt.MAIN_BLOCKS];
2063+
if (blockXML) {
2064+
currentText[pxt.MAIN_BLOCKS] = blockXML
2065+
}
20592066
}
20602067
}
20612068

@@ -2157,6 +2164,27 @@ export class ProjectView
21572164
return Promise.resolve();
21582165
}
21592166

2167+
private async loadTutorialHiddenCategoriesAsync(): Promise<void> {
2168+
const mainPkg = pkg.mainEditorPkg();
2169+
const header = mainPkg.header;
2170+
if (!header || !header.tutorial || !header.tutorial.hiddenNamespaces) {
2171+
return;
2172+
}
2173+
2174+
await mainPkg.updateConfigAsync(config => {
2175+
if (!config.toolboxFilter) {
2176+
config.toolboxFilter = {
2177+
namespaces: {},
2178+
blocks: {}
2179+
};
2180+
}
2181+
2182+
for (const category of header.tutorial.hiddenNamespaces) {
2183+
config.toolboxFilter.namespaces[category] = "hidden";
2184+
}
2185+
});
2186+
}
2187+
21602188
async resetTutorialTemplateCode(keepAssets: boolean): Promise<void> {
21612189
const mainPkg = pkg.mainEditorPkg();
21622190
const header = mainPkg.header;

webapp/src/components/tutorial/TutorialContainer.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ export function TutorialContainer(props: TutorialContainerProps) {
257257
stepContentRef.current = ref;
258258
}
259259

260+
const showReplaceMyCode =
261+
hasTemplate && currentStep == firstNonModalStep && preferredEditor !== "asset" &&
262+
!pxt.appTarget.appTheme.hideReplaceMyCode && !props.tutorialOptions.metadata?.hideReplaceMyCode
263+
260264
return <div className="tutorial-container" ref={containerRef}>
261265
{!isHorizontal && stepCounter}
262266
<div className={classList("tutorial-content", hasHint && "has-hint")} ref={contentRef} onScroll={updateScrollGradient}>
@@ -283,8 +287,9 @@ export function TutorialContainer(props: TutorialContainerProps) {
283287
tutorialId={tutorialId}
284288
currentStep={currentStep}
285289
attemptsWithError={stepErrorAttemptCount} />}
286-
{hasTemplate && currentStep == firstNonModalStep && preferredEditor !== "asset" && !pxt.appTarget.appTheme.hideReplaceMyCode &&
287-
<TutorialResetCode tutorialId={tutorialId} currentStep={visibleStep} resetTemplateCode={parent.resetTutorialTemplateCode} />}
290+
{showReplaceMyCode &&
291+
<TutorialResetCode tutorialId={tutorialId} currentStep={visibleStep} resetTemplateCode={parent.resetTutorialTemplateCode} />
292+
}
288293
{showScrollGradient && <div className="tutorial-scroll-gradient" />}
289294
{isModal && !hideModal && <Modal isOpen={isModal} closeIcon={false} header={currentStepInfo.title || name} buttons={modalActions}
290295
className="hintdialog" onClose={onModalClose} dimmer={true}

webapp/src/toolboxeditor.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,33 @@ export abstract class ToolboxEditor extends srceditor.Editor {
5757
}
5858

5959
protected shouldShowCustomCategory(ns: string) {
60-
const filters = this.parent.state.editorState && this.parent.state.editorState.filters;
60+
let filters = this.parent.state.editorState && this.parent.state.editorState.filters;
61+
62+
const projectFilter = getProjectToolboxFilters();
63+
64+
if (projectFilter) {
65+
if (filters) {
66+
// tutorial filters override project filters
67+
pxt.U.jsonMergeFrom(projectFilter, filters);
68+
}
69+
70+
filters = projectFilter;
71+
}
72+
6173
if (filters) {
6274
// These categories are special and won't have any children so we need to check the filters manually
6375
if (ns === "variables" && (!filters.blocks ||
6476
filters.blocks["variables_set"] ||
6577
filters.blocks["variables_get"] ||
6678
filters.blocks["variables_change"]) &&
67-
(!filters.namespaces || filters.namespaces["variables"] !== pxt.editor.FilterState.Disabled)) {
79+
(!filters.namespaces || !shouldHideCategory("variables", filters.namespaces))) {
6880
return true;
6981
} else if (ns === "functions" && (!filters.blocks ||
7082
filters.blocks["function_definition"] ||
7183
filters.blocks["function_call"] ||
7284
filters.blocks["procedures_defnoreturn"] ||
7385
filters.blocks["procedures_callnoreturn"]) &&
74-
(!filters.namespaces || filters.namespaces["functions"] !== pxt.editor.FilterState.Disabled)) {
86+
(!filters.namespaces || !shouldHideCategory("functions", filters.namespaces))) {
7587
return true;
7688
} else {
7789
return false;
@@ -391,3 +403,8 @@ export abstract class ToolboxEditor extends srceditor.Editor {
391403
}
392404
}
393405
}
406+
407+
408+
function shouldHideCategory(category: string, filters: {[index: string]: pxt.editor.FilterState}): boolean {
409+
return filters[category] == pxt.editor.FilterState.Hidden || filters[category] == pxt.editor.FilterState.Disabled;
410+
}

0 commit comments

Comments
 (0)