diff --git a/package.json b/package.json index 8f7cc4d..e9ff236 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,11 @@ "command": "makecode.testBlocks", "title": "%makecode.testBlocks.title%", "category": "%makecode.category.title%" + }, + { + "command": "makecode.testTutorial", + "title": "%makecode.testTutorial.title%", + "category": "%makecode.category.title%" } ], "viewsContainers": { diff --git a/package.nls.json b/package.nls.json index ec52900..c58ec73 100644 --- a/package.nls.json +++ b/package.nls.json @@ -29,5 +29,6 @@ "makecode.configuration.simWatcherDebounceDescription": "Controls the timeout in milliseconds between a file being saved and the simulator being rebuilt.", "makecode.viewsWelcome.welcomeMessage": "You need to open a folder to use the MakeCode Arcade extension!\n[Open Arcade Docs](command:makecode.openHelpDocs)", "makecode.openHelpDocs.title": "Open Arcade Docs", - "makecode.testBlocks.title": "Test extension in blocks editor" + "makecode.testBlocks.title": "Test Extension in Blocks Editor", + "makecode.testTutorial.title": "Preview Tutorial in Blocks Editor" } diff --git a/src/web/editor.ts b/src/web/editor.ts index 45f371e..16bae32 100644 --- a/src/web/editor.ts +++ b/src/web/editor.ts @@ -35,7 +35,7 @@ export class MakeCodeEditor { public static currentEditor: MakeCodeEditor | undefined; public simStateTimer: any; - public static createOrShow() { + public static createOrShow(tutorialUri?: vscode.Uri) { let column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : vscode.ViewColumn.One; column = column! < 9 ? column! + 1 : column; @@ -44,6 +44,10 @@ export class MakeCodeEditor { undefined /** keep current column **/, true ); + MakeCodeEditor.currentEditor.startWatching( + MakeCodeEditor.currentEditor.folder || activeWorkspace(), + tutorialUri + ); return; } @@ -56,7 +60,7 @@ export class MakeCodeEditor { retainContextWhenHidden: true }); - MakeCodeEditor.currentEditor = new MakeCodeEditor(panel); + MakeCodeEditor.currentEditor = new MakeCodeEditor(panel, tutorialUri); } public static register(context: vscode.ExtensionContext) { @@ -81,10 +85,13 @@ export class MakeCodeEditor { protected folder: vscode.WorkspaceFolder | undefined; protected extHeaderId: string | undefined; protected testHeaderId: string | undefined; + protected tutorialUri?: vscode.Uri; - constructor(panel: vscode.WebviewPanel) { + constructor(panel: vscode.WebviewPanel, tutorialUri?: vscode.Uri) { this.panel = panel; + this.tutorialUri = tutorialUri; + this.panel.webview.onDidReceiveMessage(message => { this.handleEditorMessage(message); }); @@ -104,8 +111,13 @@ export class MakeCodeEditor { this.initWebviewHtmlAsync(); } - startWatching(folder: vscode.WorkspaceFolder) { - if (this.running && this.folder === folder) {return;} + startWatching(folder: vscode.WorkspaceFolder, previewTutorial?: vscode.Uri) { + if (this.running && this.folder === folder && previewTutorial?.path === this.tutorialUri?.path) { + return; + } + + this.tutorialUri = previewTutorial; + this.stop(); this.folder = folder; @@ -133,8 +145,9 @@ export class MakeCodeEditor { if (/\/?((node|pxt)_modules|built|\.git)/i.test(uri.path)) { return; } - // only watch for source files - if (!/\.(json|jres|ts|asm|cpp|c|h|hpp)$/i.test(uri.path)) { + // only watch for source files or active tutorial + if (!/\.(json|jres|ts|asm|cpp|c|h|hpp)$/i.test(uri.path) + && uri.path !== this.tutorialUri?.path) { return; } @@ -176,6 +189,14 @@ export class MakeCodeEditor { async handleHostMessage(message: any) { if (message.action === "workspacesync") { + if (this.tutorialUri) { + message.projects = []; + message._fromVscode = true; + + this.panel.webview.postMessage(message); + return; + } + const project = await this.readProjectAsync(this.folder || activeWorkspace()); const testProject = await this.readTestProjectAsync(this.folder || activeWorkspace()); message.projects = [project]; @@ -193,11 +214,10 @@ export class MakeCodeEditor { this.extHeaderId = project.header.id; } - message._fromVscode = true; this.panel.webview.postMessage(message); } - else if (message.action === "workspacesave") { + else if (message.action === "workspacesave" && !this.tutorialUri) { const project = message.project as Project; if (project?.header.extensionUnderTest === this.extHeaderId) { this.saveTestProjectAsync(this.folder || activeWorkspace(), project); @@ -206,7 +226,14 @@ export class MakeCodeEditor { } async openTestProjectAsync() { - if (this.testHeaderId) { + if (this.tutorialUri) { + await this.sendMessageAsync({ + type: "pxteditor", + action: "importtutorial", + markdown: await readTextFileAsync(this.tutorialUri), + headerId: this.extHeaderId + }) + } else if (this.testHeaderId) { await this.sendMessageAsync({ type: "pxteditor", action: "openheader", @@ -240,14 +267,14 @@ export class MakeCodeEditor { } this.panel.webview.html = ""; - const hash = this.testHeaderId ? "header:" + this.testHeaderId : "testproject:" + this.extHeaderId; + const hash = this.tutorialUri ? "" : this.testHeaderId ? "header:" + this.testHeaderId : "testproject:" + this.extHeaderId; const simulatorHTML = await getMakeCodeEditorHtmlAsync(this.panel.webview, hash); this.panel.webview.html = simulatorHTML; } protected async onReadyMessageReceivedAsync() { if (!this.running) { - await this.startWatching(activeWorkspace()); + await this.startWatching(activeWorkspace(), this.tutorialUri); } else { this.openTestProjectAsync(); diff --git a/src/web/extension.ts b/src/web/extension.ts index 4bed27f..ae0cc84 100644 --- a/src/web/extension.ts +++ b/src/web/extension.ts @@ -68,6 +68,7 @@ export function activate(context: vscode.ExtensionContext) { addCmd("makecode.createAnimation", () => createAssetCommand("animation")); addCmd("makecode.createSong", () => createAssetCommand("song")); addCmd("makecode.testBlocks", testBlocksCommandAsync); + addCmd("makecode.testTutorial", testTutorialCommandAsync); context.subscriptions.push( vscode.commands.registerCommand("makecode.createAsset", createAssetCommand) @@ -696,6 +697,40 @@ async function testBlocksCommandAsync() { MakeCodeEditor.createOrShow(); } +async function testTutorialCommandAsync() { + const workspace = await chooseWorkspaceAsync("project"); + if (!workspace) { + return; + } + + const pxtJson = await getPxtJson(workspace); + const mdFiles = pxtJson.files.filter(fn => fn.toLowerCase().endsWith(".md")); + let chosenTutorial: string; + + if (!mdFiles.length) { + // no markdown files in project to pick from. + return; + } else if (mdFiles.length === 1) { + chosenTutorial = mdFiles[0]; + } else { + const choice = await vscode.window.showQuickPick( + mdFiles, + { placeHolder: vscode.l10n.t("Choose a tutorial to preview") } + ); + + if (choice && mdFiles.indexOf(choice) >= 0) { + chosenTutorial = choice; + } else { + // did not select a tutorial / gave up during quick pick + return; + } + } + + const tutorialUri = vscode.Uri.joinPath(workspace.uri, chosenTutorial); + + MakeCodeEditor.createOrShow(tutorialUri); +} + // This method is called when your extension is deactivated export function deactivate() {}