From 481807c47cf46fa72107a91e24a9a179615412ad Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 8 Jun 2019 11:10:29 -0700 Subject: [PATCH 01/12] run test action --- src/editor/commands/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 2d000bb9..ad8f39be 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -2,15 +2,15 @@ import * as vscode from 'vscode' import start from './start' import ReactPanel from '../views/createWebview' -// import runTest from './runTest' +import runTest from './runTest' // import loadSolution from './loadSolution' // import quit from './quit' const COMMANDS = { // TUTORIAL_SETUP: 'coderoad.tutorial_setup', START: 'coderoad.start', - OPEN_WEBVIEW: 'coderoad.open_webview' - // RUN_TEST: 'coderoad.test_run', + OPEN_WEBVIEW: 'coderoad.open_webview', + RUN_TEST: 'coderoad.test_run', // LOAD_SOLUTION: 'coderoad.solution_load', // QUIT: 'coderoad.quit', } @@ -18,13 +18,15 @@ const COMMANDS = { export default (context: vscode.ExtensionContext): void => { const commands = { - [COMMANDS.START]: async function startCommand(): Promise { - return start(context) + [COMMANDS.START]: () => { + start(context) }, [COMMANDS.OPEN_WEBVIEW]: () => { ReactPanel.createOrShow(context.extensionPath); }, - // [COMMANDS.RUN_TEST]: runTest, + [COMMANDS.RUN_TEST]: () => { + runTest() + }, // [COMMANDS.LOAD_SOLUTION]: loadSolution, // [COMMANDS.QUIT]: () => quit(context.subscriptions), } From d00a485520f4e121797b8531695aa02e47d3d28a Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 8 Jun 2019 12:06:17 -0700 Subject: [PATCH 02/12] progress refactoring webview, editor, machine --- src/editor/ReactWebView.ts | 146 ++++++++++++++++++++++++++++++ src/editor/commands/start.ts | 4 +- src/editor/index.ts | 75 +++++++++++++++ src/editor/init.ts | 33 ------- src/editor/views/createWebview.ts | 141 ----------------------------- src/editor/views/index.ts | 7 -- src/editor/views/onReceive.ts | 6 -- src/editor/views/utils/nonce.ts | 8 -- src/extension.ts | 11 ++- src/state/index.ts | 57 +++++++----- src/state/message.ts | 13 +++ src/typings/index.d.ts | 10 +- 12 files changed, 288 insertions(+), 223 deletions(-) create mode 100644 src/editor/ReactWebView.ts create mode 100644 src/editor/index.ts delete mode 100644 src/editor/init.ts delete mode 100644 src/editor/views/createWebview.ts delete mode 100644 src/editor/views/index.ts delete mode 100644 src/editor/views/onReceive.ts delete mode 100644 src/editor/views/utils/nonce.ts create mode 100644 src/state/message.ts diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts new file mode 100644 index 00000000..56247edc --- /dev/null +++ b/src/editor/ReactWebView.ts @@ -0,0 +1,146 @@ +import * as vscode from 'vscode' +import * as CR from 'typings' +import * as path from 'path' + +function getNonce(): string { + let text = '' + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)) + } + return text +} + +// TODO: move column into createOrShow + + +/** + * Manages React webview panels + */ +class ReactWebView { + /** + * Track the currently panel. Only allow a single panel to exist at a time. + */ + public static currentPanel: ReactWebView | undefined = undefined + + // @ts-ignore + private panel: vscode.WebviewPanel + private extensionPath: string + private disposables: vscode.Disposable[] = [] + private onReceive: any // TODO: properly type + + public constructor(extensionPath: string, onReceive: any) { + this.extensionPath = extensionPath + this.onReceive = onReceive + } + + public async createOrShow(extensionPath: string, column: number = vscode.ViewColumn.One): Promise { + const hasActiveEditor = vscode.window.activeTextEditor + + if (!hasActiveEditor) { + throw new Error('Should have an open file on launch') + } + + // If we already have a panel, show it. + // Otherwise, create a new panel. + if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) { + ReactWebView.currentPanel.panel.reveal(column) + } else { + const viewType = 'CodeRoad' + const title = 'CodeRoad' + const config = { + // Enable javascript in the webview + enableScripts: true, + + // And restric the webview to only loading content from our extension's `media` directory. + localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))], + + // prevents destroying the window when it is in the background + retainContextWhenHidden: true, + } + // Create and show a new webview panel + this.panel = vscode.window.createWebviewPanel(viewType, title, column, config) + + // Set the webview's initial html content + this.panel.webview.html = this.getHtmlForWebview() + + // Listen for when the panel is disposed + // This happens when the user closes the panel or when the panel is closed programatically + this.panel.onDidDispose(() => this.dispose(), null, this.disposables) + + // Handle messages from the webview + this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables) + } + } + + public async postMessage(action: CR.Action): Promise { + // Send a message to the webview webview. + // You can send any JSON serializable data. + const success = await this.panel.webview.postMessage(action) + if (!success) { + throw new Error(`Message post failure: ${JSON.stringify(action)}`) + } + } + + public dispose(): void { + ReactWebView.currentPanel = undefined + + // Clean up our resources + this.panel.dispose() + + while (this.disposables.length) { + const x = this.disposables.pop() + if (x) { + x.dispose() + } + } + } + + private getHtmlForWebview(): string { + + // eslint-disable-next-line + const manifest = require(path.join(this.extensionPath, 'build', 'asset-manifest.json')) + const mainScript = manifest.files['main.js'] + // grab first chunk + const chunk = Object.keys(manifest.files).filter(f => f.match(/^static\/js\/.+\.js$/))[0] + const chunkScript = manifest.files[chunk] + const mainStyle = manifest.files['main.css'] + + const scriptPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainScript)) + const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' }) + const chunkPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', chunkScript)) + const chunkUri = chunkPathOnDisk.with({ scheme: 'vscode-resource' }) + const stylePathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainStyle)) + const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' }) + + // Use a nonce to whitelist which scripts can be run + const nonce = getNonce() + const nonce2 = getNonce() + const nonce3 = getNonce() + + return ` + + + + + + React App + + + + + + + + + +
Loading...
+ + + + + ` + } +} + +export default ReactWebView diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts index 84c4a77a..48b44ddb 100644 --- a/src/editor/commands/start.ts +++ b/src/editor/commands/start.ts @@ -18,9 +18,9 @@ export default async function start(context: vscode.ExtensionContext): Promise { + this.commandStart() + }, + [COMMANDS.OPEN_WEBVIEW]: () => { + this.webview.createOrShow(this.context.extensionPath); + }, + } + for (const cmd in commands) { + const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) + this.context.subscriptions.push(command) + } + } + public activate(context: vscode.ExtensionContext): void { + console.log('ACTIVATE!') + this.context = context + // commands + this.activateCommands() + + // setup tasks or views here + + } + public deactivate(): void { + console.log('DEACTIVATE!') + // cleanup subscriptions/tasks + for (const disposable of this.context.subscriptions) { + disposable.dispose() + } + // shut down state machine + this.machine.deactivate() + } +} + +export default Editor \ No newline at end of file diff --git a/src/editor/init.ts b/src/editor/init.ts deleted file mode 100644 index dbc9f64a..00000000 --- a/src/editor/init.ts +++ /dev/null @@ -1,33 +0,0 @@ -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode' - -import { deactivate as deactivateMachine } from '../state' -import createCommands from './commands' -import createViews from './views' - -// this method is called when your extension is activated -// your extension is activated the very first time the command is executed -export function activate(context: vscode.ExtensionContext) { - console.log('ACTIVATE!') - - // commands - createCommands(context) - - // views - createViews(context) - - // tasks - // add tasks here -} - -// this method is called when your extension is deactivated -export function deactivate(context: vscode.ExtensionContext): void { - // cleanup subscriptions/tasks - console.log('deactivate context', context) - for (const disposable of context.subscriptions) { - disposable.dispose() - } - // shut down state machine - deactivateMachine() -} diff --git a/src/editor/views/createWebview.ts b/src/editor/views/createWebview.ts deleted file mode 100644 index 321403a0..00000000 --- a/src/editor/views/createWebview.ts +++ /dev/null @@ -1,141 +0,0 @@ -import * as vscode from 'vscode' -import * as CR from 'typings' -import * as path from 'path' - -import getNonce from './utils/nonce' -import onReceive from './onReceive' - -/** - * Manages React webview panels - */ -class ReactPanel { - /** - * Track the currently panel. Only allow a single panel to exist at a time. - */ - public static currentPanel: ReactPanel | undefined = undefined - - private readonly _panel: vscode.WebviewPanel - private readonly _extensionPath: string - private _disposables: vscode.Disposable[] = [] - - public static async createOrShow(extensionPath: string): Promise { - // const hasActiveEditor = vscode.window.activeTextEditor - - // if (!hasActiveEditor) { - // throw new Error('Should have an open file on launch') - // } - const column = vscode.ViewColumn.One - - // If we already have a panel, show it. - // Otherwise, create a new panel. - if (ReactPanel.currentPanel) { - console.log('--- HAS CURRENT PANEL ---') - ReactPanel.currentPanel._panel.reveal(column) - } else { - ReactPanel.currentPanel = new ReactPanel(extensionPath, column) - } - } - - private constructor(extensionPath: string, column: vscode.ViewColumn) { - this._extensionPath = extensionPath - - const viewType = 'CodeRoad' - const title = 'CodeRoad' - const config = { - // Enable javascript in the webview - enableScripts: true, - - // And restric the webview to only loading content from our extension's `media` directory. - localResourceRoots: [vscode.Uri.file(path.join(this._extensionPath, 'build'))], - - // prevents destroying the window when it is in the background - retainContextWhenHidden: true, - } - - // Create and show a new webview panel - this._panel = vscode.window.createWebviewPanel(viewType, title, column, config) - - // Set the webview's initial html content - this._panel.webview.html = this._getHtmlForWebview() - - // Listen for when the panel is disposed - // This happens when the user closes the panel or when the panel is closed programatically - this._panel.onDidDispose(() => this.dispose(), null, this._disposables) - - // Handle messages from the webview - this._panel.webview.onDidReceiveMessage(onReceive, null, this._disposables) - } - - public async postMessage(action: CR.Action): Promise { - // Send a message to the webview webview. - // You can send any JSON serializable data. - const success = await this._panel.webview.postMessage(action) - if (!success) { - throw new Error(`Message post failure: ${JSON.stringify(action)}`) - } - } - - public dispose(): void { - ReactPanel.currentPanel = undefined - - // Clean up our resources - this._panel.dispose() - - while (this._disposables.length) { - const x = this._disposables.pop() - if (x) { - x.dispose() - } - } - } - - private _getHtmlForWebview(): string { - - // eslint-disable-next-line - const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json')) - const mainScript = manifest.files['main.js'] - // grab first chunk - const chunk = Object.keys(manifest.files).filter(f => f.match(/^static\/js\/.+\.js$/))[0] - const chunkScript = manifest.files[chunk] - const mainStyle = manifest.files['main.css'] - - const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainScript)) - const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' }) - const chunkPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', chunkScript)) - const chunkUri = chunkPathOnDisk.with({ scheme: 'vscode-resource' }) - const stylePathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainStyle)) - const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' }) - - // Use a nonce to whitelist which scripts can be run - const nonce = getNonce() - const nonce2 = getNonce() - const nonce3 = getNonce() - - const output = ` - - - - - - React App - - - - - - - - - -
Loading...
- - - - - ` - console.log(output) - return output - } -} - -export default ReactPanel diff --git a/src/editor/views/index.ts b/src/editor/views/index.ts deleted file mode 100644 index 60a7d871..00000000 --- a/src/editor/views/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as vscode from 'vscode' - -const createViews = (context: vscode.ExtensionContext) => { - // TODO: create views -} - -export default createViews diff --git a/src/editor/views/onReceive.ts b/src/editor/views/onReceive.ts deleted file mode 100644 index ccd69eea..00000000 --- a/src/editor/views/onReceive.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as CR from 'typings' - -export default async (action: CR.Action): Promise => { - console.log('action', action) - // TODO: call state machine -} diff --git a/src/editor/views/utils/nonce.ts b/src/editor/views/utils/nonce.ts deleted file mode 100644 index 313819e1..00000000 --- a/src/editor/views/utils/nonce.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function getNonce(): string { - let text = '' - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)) - } - return text -} diff --git a/src/extension.ts b/src/extension.ts index 40646445..80fd9c64 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,2 +1,11 @@ -export { activate, deactivate } from './editor/init' +import StateMachine from './state' +import Editor from './editor' + +// state machine that governs application logic +const Machine = new StateMachine() +// vscode editor +const VSCodeEditor = new Editor(Machine) + +export const activate = VSCodeEditor.activate +export const deactivate = VSCodeEditor.deactivate diff --git a/src/state/index.ts b/src/state/index.ts index 376b66ef..ab5f7686 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,31 +1,40 @@ -import { interpret } from 'xstate' +import { interpret, Interpreter } from 'xstate' +import * as CR from '../typings' import machine from './machine' -const machineOptions = { - logger: console.log, - devTools: true, - deferEvents: true, - execute: true -} // machine interpreter // https://xstate.js.org/docs/guides/interpretation.html -const service = interpret(machine, machineOptions) - // logging - .onTransition(state => { - console.log('state', state) - if (state.changed) { - console.log('transition') - console.log(state.value) - } - }) - -export function activate() { - // initialize - service.start() -} -export function deactivate() { - service.stop() +class StateMachine { + private machineOptions = { + logger: console.log, + devTools: true, + deferEvents: true, + execute: true + } + private service: Interpreter + constructor() { + this.service = interpret(machine, this.machineOptions) + // logging + .onTransition(state => { + console.log('state', state) + if (state.changed) { + console.log('transition') + console.log(state.value) + } + }) + } + activate() { + // initialize + this.service.start() + } + deactivate() { + this.service.stop() + } + onReceive(action: CR.Action) { + console.log('RECEIVED ACTION') + console.log(action) + } } -export default service +export default StateMachine diff --git a/src/state/message.ts b/src/state/message.ts new file mode 100644 index 00000000..2393abde --- /dev/null +++ b/src/state/message.ts @@ -0,0 +1,13 @@ +import panel from '../views/Panel' +import * as CR from '../typings' + +export const onSend = (action: CR.Action) => { + if (!panel || !panel.currentPanel) { + throw new Error('No valid panel available') + } + panel.currentPanel.postMessage(action) +} + +export const onReceive = (action: CR.Action) => { + console.log(`RECEIVED: ${JSON.stringify(action)}`) +} diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 0c16d246..4c8a9bf3 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -1,3 +1,5 @@ +import { onReceive } from "state/message"; + export interface TutorialLevel { stageList: string[] content: { @@ -159,4 +161,10 @@ export interface MachineStateSchema { } } } -} \ No newline at end of file +} + +export interface StateMachine { + activate(): void + deactivate(): void + onReceive(action: Action): void +} From 3261c339f5fa50d74b7152d6933fd32cf7088262 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 8 Jun 2019 19:39:41 -0700 Subject: [PATCH 03/12] refactor order of launches --- src/editor/ReactWebView.ts | 8 +--- src/editor/commands/index.ts | 66 +++++++++++++++--------------- src/editor/commands/start.ts | 78 ++++++++++++++++++------------------ src/editor/index.ts | 24 ++++++----- src/state/message.ts | 10 ++--- 5 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts index 56247edc..e19c8f56 100644 --- a/src/editor/ReactWebView.ts +++ b/src/editor/ReactWebView.ts @@ -34,13 +34,7 @@ class ReactWebView { this.onReceive = onReceive } - public async createOrShow(extensionPath: string, column: number = vscode.ViewColumn.One): Promise { - const hasActiveEditor = vscode.window.activeTextEditor - - if (!hasActiveEditor) { - throw new Error('Should have an open file on launch') - } - + public async createOrShow(column: number = vscode.ViewColumn.One): Promise { // If we already have a panel, show it. // Otherwise, create a new panel. if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) { diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index ad8f39be..5fabd1fe 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -1,38 +1,38 @@ -import * as vscode from 'vscode' -import start from './start' -import ReactPanel from '../views/createWebview' +// import * as vscode from 'vscode' +// import start from './start' +// // import ReactPanel from '../views/createWebview' -import runTest from './runTest' -// import loadSolution from './loadSolution' -// import quit from './quit' +// import runTest from './runTest' +// // import loadSolution from './loadSolution' +// // import quit from './quit' -const COMMANDS = { - // TUTORIAL_SETUP: 'coderoad.tutorial_setup', - START: 'coderoad.start', - OPEN_WEBVIEW: 'coderoad.open_webview', - RUN_TEST: 'coderoad.test_run', - // LOAD_SOLUTION: 'coderoad.solution_load', - // QUIT: 'coderoad.quit', -} +// const COMMANDS = { +// // TUTORIAL_SETUP: 'coderoad.tutorial_setup', +// START: 'coderoad.start', +// OPEN_WEBVIEW: 'coderoad.open_webview', +// RUN_TEST: 'coderoad.test_run', +// // LOAD_SOLUTION: 'coderoad.solution_load', +// // QUIT: 'coderoad.quit', +// } -export default (context: vscode.ExtensionContext): void => { - const commands = { - [COMMANDS.START]: () => { - start(context) - }, - [COMMANDS.OPEN_WEBVIEW]: () => { - ReactPanel.createOrShow(context.extensionPath); - }, - [COMMANDS.RUN_TEST]: () => { - runTest() - }, - // [COMMANDS.LOAD_SOLUTION]: loadSolution, - // [COMMANDS.QUIT]: () => quit(context.subscriptions), - } +// export default (context: vscode.ExtensionContext): void => { +// const commands = { +// [COMMANDS.START]: () => { +// start(context) +// }, +// [COMMANDS.OPEN_WEBVIEW]: () => { +// // ReactPanel.createOrShow(context.extensionPath); +// }, +// [COMMANDS.RUN_TEST]: () => { +// runTest() +// }, +// // [COMMANDS.LOAD_SOLUTION]: loadSolution, +// // [COMMANDS.QUIT]: () => quit(context.subscriptions), +// } - for (const cmd in commands) { - const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) - context.subscriptions.push(command) - } -} +// for (const cmd in commands) { +// const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) +// context.subscriptions.push(command) +// } +// } diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts index 48b44ddb..a7e80ae3 100644 --- a/src/editor/commands/start.ts +++ b/src/editor/commands/start.ts @@ -1,45 +1,45 @@ -import * as vscode from 'vscode' -import { setWorkspaceRoot } from '../../services/node' -import { setStorage } from '../../editor/storage' -import { activate as activateMachine, default as machine } from '../../state' -import * as storage from '../../services/storage' -import * as git from '../../services/git' -import * as CR from 'typings' +// import * as vscode from 'vscode' +// import { setWorkspaceRoot } from '../../services/node' +// import { setStorage } from '../../editor/storage' +// import { activate as activateMachine, default as machine } from '../../state' +// import * as storage from '../../services/storage' +// import * as git from '../../services/git' +// import * as CR from 'typings' -let initialTutorial: CR.Tutorial | undefined -let initialProgress: CR.Progress = { - levels: {}, - stages: {}, - steps: {}, - complete: false, -} +// let initialTutorial: CR.Tutorial | undefined +// let initialProgress: CR.Progress = { +// levels: {}, +// stages: {}, +// steps: {}, +// complete: false, +// } -export default async function start(context: vscode.ExtensionContext): Promise { - console.log('TUTORIAL_START') +// export default async function start(context: vscode.ExtensionContext): Promise { +// console.log('TUTORIAL_START') - // setup connection to workspace - // await setWorkspaceRoot() - // set workspace context path - // await setStorage(context.workspaceState) +// // setup connection to workspace +// // await setWorkspaceRoot() +// // set workspace context path +// // await setStorage(context.workspaceState) - // initialize state machine - activateMachine() +// // initialize state machine +// activateMachine() - console.log('ACTION: start') +// console.log('ACTION: start') - // verify that the user has a tutorial & progress - // verify git is setup with a coderoad remote - const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ - storage.getTutorial(), - storage.getProgress(), - git.gitVersion(), - git.gitCheckRemoteExists(), - ]) - initialTutorial = tutorial - initialProgress = progress - const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) - console.log('canContinue', canContinue) - // if a tutorial exists, "CONTINUE" - // otherwise start from "NEW" - machine.send(canContinue ? 'CONTINUE' : 'NEW') -} \ No newline at end of file +// // verify that the user has a tutorial & progress +// // verify git is setup with a coderoad remote +// const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ +// storage.getTutorial(), +// storage.getProgress(), +// git.gitVersion(), +// git.gitCheckRemoteExists(), +// ]) +// initialTutorial = tutorial +// initialProgress = progress +// const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) +// console.log('canContinue', canContinue) +// // if a tutorial exists, "CONTINUE" +// // otherwise start from "NEW" +// machine.send(canContinue ? 'CONTINUE' : 'NEW') +// } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index 3b9b40d1..870ea187 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -21,7 +21,7 @@ class Editor { this.machine = machine } - private commandStart() { + private commandStart = (): void => { // set workspace root const { rootPath } = vscode.workspace if (!rootPath) { @@ -33,18 +33,24 @@ class Editor { setStorage(this.context.workspaceState) // activate machine - this.machine.activate() this.webview = new ReactWebView(this.context.extensionPath, this.machine.onReceive) + this.machine.activate() + + console.log('command start webview') + console.log(this.webview) } - private activateCommands() { - const { COMMANDS } = this + private activateCommands = (): void => { + console.log('this.COMMANDS', this.COMMANDS) const commands = { - [COMMANDS.START]: () => { + [this.COMMANDS.START]: () => { + console.log('start') this.commandStart() }, - [COMMANDS.OPEN_WEBVIEW]: () => { - this.webview.createOrShow(this.context.extensionPath); + [this.COMMANDS.OPEN_WEBVIEW]: () => { + console.log('open webview') + console.log(this.webview) + this.webview.createOrShow(); }, } for (const cmd in commands) { @@ -52,7 +58,7 @@ class Editor { this.context.subscriptions.push(command) } } - public activate(context: vscode.ExtensionContext): void { + public activate = (context: vscode.ExtensionContext): void => { console.log('ACTIVATE!') this.context = context // commands @@ -61,7 +67,7 @@ class Editor { // setup tasks or views here } - public deactivate(): void { + public deactivate = (): void => { console.log('DEACTIVATE!') // cleanup subscriptions/tasks for (const disposable of this.context.subscriptions) { diff --git a/src/state/message.ts b/src/state/message.ts index 2393abde..70c2909f 100644 --- a/src/state/message.ts +++ b/src/state/message.ts @@ -1,11 +1,11 @@ -import panel from '../views/Panel' +// import panel from '../views/Panel' import * as CR from '../typings' export const onSend = (action: CR.Action) => { - if (!panel || !panel.currentPanel) { - throw new Error('No valid panel available') - } - panel.currentPanel.postMessage(action) + // if (!panel || !panel.currentPanel) { + // throw new Error('No valid panel available') + // } + // panel.currentPanel.postMessage(action) } export const onReceive = (action: CR.Action) => { From 6d8b993d332134b8ab6b10ec364cc9ab86bf026d Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 8 Jun 2019 20:25:21 -0700 Subject: [PATCH 04/12] further editor refactoring --- src/editor/commands/index.ts | 75 ++++++++++++++++++++---------------- src/editor/commands/quit.ts | 5 --- src/editor/index.ts | 53 ++++++++----------------- src/extension.ts | 14 ++++--- src/services/git/index.ts | 5 ++- src/services/node/index.ts | 19 +-------- src/services/testResult.ts | 1 - src/state/index.ts | 3 +- 8 files changed, 71 insertions(+), 104 deletions(-) delete mode 100644 src/editor/commands/quit.ts diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 5fabd1fe..94f0c7d9 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -1,38 +1,47 @@ -// import * as vscode from 'vscode' -// import start from './start' -// // import ReactPanel from '../views/createWebview' +import * as vscode from 'vscode' +import { join } from 'path' +import { setStorage } from '../storage' +import ReactWebView from '../ReactWebView' +import * as CR from '../../typings' -// import runTest from './runTest' -// // import loadSolution from './loadSolution' -// // import quit from './quit' +const COMMANDS = { + START: 'coderoad.start', + OPEN_WEBVIEW: 'coderoad.open_webview', + OPEN_FILE: 'coderoad.open_file', + RUN_TEST: 'coderoad.test_run', +} -// const COMMANDS = { -// // TUTORIAL_SETUP: 'coderoad.tutorial_setup', -// START: 'coderoad.start', -// OPEN_WEBVIEW: 'coderoad.open_webview', -// RUN_TEST: 'coderoad.test_run', -// // LOAD_SOLUTION: 'coderoad.solution_load', -// // QUIT: 'coderoad.quit', -// } +interface CreateCommandProps { + context: vscode.ExtensionContext, + machine: CR.StateMachine +} +// React panel webview +let webview: any; -// export default (context: vscode.ExtensionContext): void => { -// const commands = { -// [COMMANDS.START]: () => { -// start(context) -// }, -// [COMMANDS.OPEN_WEBVIEW]: () => { -// // ReactPanel.createOrShow(context.extensionPath); -// }, -// [COMMANDS.RUN_TEST]: () => { -// runTest() -// }, -// // [COMMANDS.LOAD_SOLUTION]: loadSolution, -// // [COMMANDS.QUIT]: () => quit(context.subscriptions), -// } +export const createCommands = ({ context, machine }: CreateCommandProps) => ({ + [COMMANDS.START]: () => { + // set local storage workspace + setStorage(context.workspaceState) -// for (const cmd in commands) { -// const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) -// context.subscriptions.push(command) -// } -// } + // activate machine + webview = new ReactWebView(context.extensionPath, machine.onReceive) + machine.activate() + }, + [COMMANDS.OPEN_WEBVIEW]: () => { + webview.createOrShow(); + }, + [COMMANDS.OPEN_FILE]: async (relativeFilePath: string) => { + try { + const workspaceRoot = vscode.workspace.rootPath + if (!workspaceRoot) { + throw new Error('No workspace root path') + } + const absoluteFilePath = join(workspaceRoot, relativeFilePath) + const doc = await vscode.workspace.openTextDocument(absoluteFilePath) + await vscode.window.showTextDocument(doc, vscode.ViewColumn.One) + } catch (error) { + console.log(`Failed to open file ${relativeFilePath}`, error) + } + } +}) \ No newline at end of file diff --git a/src/editor/commands/quit.ts b/src/editor/commands/quit.ts deleted file mode 100644 index 06c2254b..00000000 --- a/src/editor/commands/quit.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as vscode from 'vscode' - -export default () => { - // TODO: quit -} diff --git a/src/editor/index.ts b/src/editor/index.ts index 870ea187..5bacfebf 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,58 +1,35 @@ import * as vscode from 'vscode' import * as CR from '../typings' -import { setStorage } from './storage' -import ReactWebView from './ReactWebView' +import { createCommands } from './commands' + +interface Props { + machine: CR.StateMachine, + setWorkspaceRoot(rootPath: string): void +} class Editor { // extension context set on activation // @ts-ignore private context: vscode.ExtensionContext - private workspaceRoot: string | undefined private machine: CR.StateMachine - private webview: any - private COMMANDS = { - START: 'coderoad.start', - OPEN_WEBVIEW: 'coderoad.open_webview', - RUN_TEST: 'coderoad.test_run', - } - - constructor(machine: CR.StateMachine) { + constructor({ machine, setWorkspaceRoot }: Props) { this.machine = machine - } - private commandStart = (): void => { - // set workspace root - const { rootPath } = vscode.workspace + // set workspace root for node executions + const { workspace } = vscode + const { rootPath } = workspace if (!rootPath) { throw new Error('Requires a workspace. Please open a folder') } - this.workspaceRoot = rootPath - - // set local storage workspace - setStorage(this.context.workspaceState) - - // activate machine - this.webview = new ReactWebView(this.context.extensionPath, this.machine.onReceive) - this.machine.activate() - - console.log('command start webview') - console.log(this.webview) + setWorkspaceRoot(rootPath) } private activateCommands = (): void => { - console.log('this.COMMANDS', this.COMMANDS) - const commands = { - [this.COMMANDS.START]: () => { - console.log('start') - this.commandStart() - }, - [this.COMMANDS.OPEN_WEBVIEW]: () => { - console.log('open webview') - console.log(this.webview) - this.webview.createOrShow(); - }, - } + const commands = createCommands({ + context: this.context, + machine: this.machine, + }) for (const cmd in commands) { const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) this.context.subscriptions.push(command) diff --git a/src/extension.ts b/src/extension.ts index 80fd9c64..62a91e02 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,11 +1,15 @@ +import { setWorkspaceRoot } from './services/node' import StateMachine from './state' import Editor from './editor' // state machine that governs application logic -const Machine = new StateMachine() -// vscode editor -const VSCodeEditor = new Editor(Machine) +export const machine = new StateMachine() -export const activate = VSCodeEditor.activate -export const deactivate = VSCodeEditor.deactivate +// vscode editor +export const editor = new Editor({ + machine, + setWorkspaceRoot, +}) +export const activate = editor.activate +export const deactivate = editor.deactivate diff --git a/src/services/git/index.ts b/src/services/git/index.ts index 347ef142..46cc654f 100644 --- a/src/services/git/index.ts +++ b/src/services/git/index.ts @@ -1,5 +1,6 @@ +import * as vscode from 'vscode' import * as CR from 'typings' -import { exec, exists, openFile } from '../node' +import { exec, exists } from '../node' const gitOrigin = 'coderoad' @@ -40,7 +41,7 @@ export async function gitLoadCommits(actions: CR.TutorialAction): Promise if (files) { for (const filePath of files) { - openFile(filePath) + vscode.commands.executeCommand('coderoad.open_webview', filePath) } } } diff --git a/src/services/node/index.ts b/src/services/node/index.ts index 0a5234d9..04aa976e 100644 --- a/src/services/node/index.ts +++ b/src/services/node/index.ts @@ -1,6 +1,4 @@ -import { workspace } from 'vscode' import * as fs from 'fs' -import * as vscode from 'vscode' import { join } from 'path' import { exec as cpExec } from 'child_process' import { promisify } from 'util' @@ -11,11 +9,7 @@ let workspaceRoot: string // set workspace root // other function will use this to target the correct cwd -export async function setWorkspaceRoot(): Promise { - const { rootPath } = workspace - if (!rootPath) { - throw new Error('Requires a workspace. Please open a folder') - } +export function setWorkspaceRoot(rootPath: string): void { workspaceRoot = rootPath } @@ -28,17 +22,6 @@ export const exec = (cmd: string): Promise<{ stdout: string; stderr: string }> = // collect all paths together export const exists = (...paths: string[]): boolean => fs.existsSync(join(workspaceRoot, ...paths)) -export const openFile = async (relativeFilePath: string): Promise => { - try { - const absoluteFilePath = join(workspaceRoot, relativeFilePath) - const doc = await vscode.workspace.openTextDocument(absoluteFilePath) - await vscode.window.showTextDocument(doc, vscode.ViewColumn.One) - } catch (error) { - console.log(`Failed to open file ${relativeFilePath}`, error) - } -} - - // export async function clear(): Promise { // // remove all files including ignored // // NOTE: Linux only diff --git a/src/services/testResult.ts b/src/services/testResult.ts index b2e3fb0f..056646d1 100644 --- a/src/services/testResult.ts +++ b/src/services/testResult.ts @@ -2,7 +2,6 @@ import * as CR from 'typings' import * as vscode from 'vscode' import * as storage from './storage' - export async function onSuccess(position: CR.Position) { console.log('onSuccess', position) vscode.window.showInformationMessage('SUCCESS') diff --git a/src/state/index.ts b/src/state/index.ts index ab5f7686..516f1358 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -17,9 +17,8 @@ class StateMachine { this.service = interpret(machine, this.machineOptions) // logging .onTransition(state => { - console.log('state', state) if (state.changed) { - console.log('transition') + console.log('next state') console.log(state.value) } }) From 0a3e3956adbb02e8ad5093b1165cd3a6e5f88f23 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 8 Jun 2019 20:30:26 -0700 Subject: [PATCH 05/12] cleanup component structure --- web-app/src/App.css | 33 ------------------------------ web-app/src/App.test.tsx | 9 -------- web-app/src/App.tsx | 14 ------------- web-app/src/Routes.tsx | 5 +++++ web-app/src/index.tsx | 7 ++++--- web-app/src/logo.svg | 7 ------- web-app/src/{ => styles}/index.css | 0 7 files changed, 9 insertions(+), 66 deletions(-) delete mode 100644 web-app/src/App.css delete mode 100644 web-app/src/App.test.tsx delete mode 100644 web-app/src/App.tsx create mode 100644 web-app/src/Routes.tsx delete mode 100644 web-app/src/logo.svg rename web-app/src/{ => styles}/index.css (100%) diff --git a/web-app/src/App.css b/web-app/src/App.css deleted file mode 100644 index b41d297c..00000000 --- a/web-app/src/App.css +++ /dev/null @@ -1,33 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; - pointer-events: none; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/web-app/src/App.test.tsx b/web-app/src/App.test.tsx deleted file mode 100644 index a754b201..00000000 --- a/web-app/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx deleted file mode 100644 index 3b8aacc4..00000000 --- a/web-app/src/App.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import './App.css'; - -const App: React.FC = () => { - return ( -
-
- Hello World -
-
- ); -} - -export default App; diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx new file mode 100644 index 00000000..a9953b31 --- /dev/null +++ b/web-app/src/Routes.tsx @@ -0,0 +1,5 @@ +import * as React from 'react' + +export default () => { + return
Hello World
+} \ No newline at end of file diff --git a/web-app/src/index.tsx b/web-app/src/index.tsx index b7e93595..ba003317 100644 --- a/web-app/src/index.tsx +++ b/web-app/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; +import Routes from './Routes'; -ReactDOM.render(, document.getElementById('root') as HTMLElement) +import './styles/index.css'; + +ReactDOM.render(, document.getElementById('root') as HTMLElement) diff --git a/web-app/src/logo.svg b/web-app/src/logo.svg deleted file mode 100644 index 6b60c104..00000000 --- a/web-app/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web-app/src/index.css b/web-app/src/styles/index.css similarity index 100% rename from web-app/src/index.css rename to web-app/src/styles/index.css From ec2e0e0d6a428d1ca6c101315127dff3b480b31b Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 10:56:51 -0700 Subject: [PATCH 06/12] absolute path typings --- src/editor/commands/index.ts | 2 +- src/editor/index.ts | 2 +- src/state/message.ts | 2 +- tsconfig.json | 2 +- {src/typings => typings}/context.d.ts | 0 {src/typings => typings}/index.d.ts | 2 -- web-app/src/components/Continue/index.tsx | 2 +- web-app/src/components/Level/index.tsx | 2 +- web-app/src/components/Stage/index.tsx | 2 +- web-app/src/components/Step/index.tsx | 2 +- web-app/src/components/Summary/index.tsx | 2 +- web-app/tsconfig.json | 18 ++++-------------- web-app/tsconfig.paths.json | 21 +++++++++++++++++++++ 13 files changed, 34 insertions(+), 25 deletions(-) rename {src/typings => typings}/context.d.ts (100%) rename {src/typings => typings}/index.d.ts (98%) create mode 100644 web-app/tsconfig.paths.json diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 94f0c7d9..2b96940e 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode' import { join } from 'path' import { setStorage } from '../storage' import ReactWebView from '../ReactWebView' -import * as CR from '../../typings' +import * as CR from 'typings' const COMMANDS = { START: 'coderoad.start', diff --git a/src/editor/index.ts b/src/editor/index.ts index 5bacfebf..db128230 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode' -import * as CR from '../typings' +import * as CR from 'typings' import { createCommands } from './commands' interface Props { diff --git a/src/state/message.ts b/src/state/message.ts index 70c2909f..571b2100 100644 --- a/src/state/message.ts +++ b/src/state/message.ts @@ -1,5 +1,5 @@ // import panel from '../views/Panel' -import * as CR from '../typings' +import * as CR from 'typings' export const onSend = (action: CR.Action) => { // if (!panel || !panel.currentPanel) { diff --git a/tsconfig.json b/tsconfig.json index 8cd5646e..cef79727 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "paths": { - "typings": ["./typings/index.d.ts"], + "typings": ["../typings/index.d.ts"], }, }, "exclude": [ diff --git a/src/typings/context.d.ts b/typings/context.d.ts similarity index 100% rename from src/typings/context.d.ts rename to typings/context.d.ts diff --git a/src/typings/index.d.ts b/typings/index.d.ts similarity index 98% rename from src/typings/index.d.ts rename to typings/index.d.ts index 4c8a9bf3..73b2b99f 100644 --- a/src/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,5 +1,3 @@ -import { onReceive } from "state/message"; - export interface TutorialLevel { stageList: string[] content: { diff --git a/web-app/src/components/Continue/index.tsx b/web-app/src/components/Continue/index.tsx index c99195df..ea65eaac 100644 --- a/web-app/src/components/Continue/index.tsx +++ b/web-app/src/components/Continue/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import CR from '../../../../src/typings' +import CR from 'typings' import ContinueItem from './ContinueItem' diff --git a/web-app/src/components/Level/index.tsx b/web-app/src/components/Level/index.tsx index 403c9f3d..a48d8ee5 100644 --- a/web-app/src/components/Level/index.tsx +++ b/web-app/src/components/Level/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Button, Card } from '@alifd/next' -import CR from '../../../../src/typings' +import CR from 'typings' const styles = { card: { diff --git a/web-app/src/components/Stage/index.tsx b/web-app/src/components/Stage/index.tsx index ac45304c..16b7a104 100644 --- a/web-app/src/components/Stage/index.tsx +++ b/web-app/src/components/Stage/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Button, Card } from '@alifd/next' -import CR from '../../../../src/typings' +import CR from 'typings' import Step from '../Step' diff --git a/web-app/src/components/Step/index.tsx b/web-app/src/components/Step/index.tsx index 1e764e17..372c77d4 100644 --- a/web-app/src/components/Step/index.tsx +++ b/web-app/src/components/Step/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { Checkbox } from '@alifd/next' // import CC from '../../typings/client' -import CR from '../../../../src/typings' +import CR from 'typings' const styles = { card: { diff --git a/web-app/src/components/Summary/index.tsx b/web-app/src/components/Summary/index.tsx index 00509b27..e00ac952 100644 --- a/web-app/src/components/Summary/index.tsx +++ b/web-app/src/components/Summary/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Button, Card } from '@alifd/next' -import CR from '../../../../src/typings' +import CR from 'typings' const styles = { card: { diff --git a/web-app/tsconfig.json b/web-app/tsconfig.json index e68470ec..04664a04 100644 --- a/web-app/tsconfig.json +++ b/web-app/tsconfig.json @@ -1,8 +1,10 @@ { + "extends": "./tsconfig.paths.json", "compilerOptions": { "target": "es5", "lib": [ "dom", + "dom.iterable", "esnext" ], "allowJs": true, @@ -16,21 +18,9 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve", - "sourceMap": true, - "rootDirs": ["src", "stories"], - "baseUrl": "src", - "outDir": "build" + "jsx": "preserve" }, "include": [ - "src", - "../src/typings" - ], - "exclude": [ - "node_modules", - "build", - "scripts", - "jest", - "public" + "src" ] } diff --git a/web-app/tsconfig.paths.json b/web-app/tsconfig.paths.json new file mode 100644 index 00000000..89a64aa3 --- /dev/null +++ b/web-app/tsconfig.paths.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "rootDirs": [ + "src", + "stories" + ], + "paths": { + "typings": [ + "../../typings/index.d.ts" + ], + } + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "jest", + "public" + ] +} \ No newline at end of file From b50e2c095bb37b21d05d0bdd3058e71fbe401c3a Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 14:25:14 -0700 Subject: [PATCH 07/12] support passing of messages to view --- src/editor/ReactWebView.ts | 93 ++++++++++++++++++------------------ src/editor/commands/index.ts | 53 ++++++++++++++++++-- src/editor/commands/start.ts | 45 ----------------- src/editor/index.ts | 9 ++++ src/extension.ts | 1 + src/services/git/index.ts | 2 +- src/state/actions/index.ts | 3 ++ src/state/index.ts | 10 +++- src/state/machine.ts | 8 +++- typings/index.d.ts | 4 ++ web-app/package.json | 1 - web-app/src/Routes.tsx | 34 +++++++++++-- 12 files changed, 161 insertions(+), 102 deletions(-) delete mode 100644 src/editor/commands/start.ts diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts index e19c8f56..ca941fcd 100644 --- a/src/editor/ReactWebView.ts +++ b/src/editor/ReactWebView.ts @@ -2,27 +2,10 @@ import * as vscode from 'vscode' import * as CR from 'typings' import * as path from 'path' -function getNonce(): string { - let text = '' - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)) - } - return text -} - -// TODO: move column into createOrShow - - /** * Manages React webview panels */ class ReactWebView { - /** - * Track the currently panel. Only allow a single panel to exist at a time. - */ - public static currentPanel: ReactWebView | undefined = undefined - // @ts-ignore private panel: vscode.WebviewPanel private extensionPath: string @@ -32,53 +15,71 @@ class ReactWebView { public constructor(extensionPath: string, onReceive: any) { this.extensionPath = extensionPath this.onReceive = onReceive + + // Create and show a new webview panel + this.panel = this.createWebviewPanel(vscode.ViewColumn.One) + + // Set the webview's initial html content + this.panel.webview.html = this.getHtmlForWebview() + + // Listen for when the panel is disposed + // This happens when the user closes the panel or when the panel is closed programatically + this.panel.onDidDispose(() => this.dispose(), null, this.disposables) + + // Handle messages from the webview + this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables) + console.log('webview loaded') } - public async createOrShow(column: number = vscode.ViewColumn.One): Promise { + public async createOrShow(column: number): Promise { // If we already have a panel, show it. // Otherwise, create a new panel. - if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) { - ReactWebView.currentPanel.panel.reveal(column) + if (this.panel && this.panel.webview) { + console.log('reveal') + this.panel.reveal(column) } else { - const viewType = 'CodeRoad' - const title = 'CodeRoad' - const config = { - // Enable javascript in the webview - enableScripts: true, - - // And restric the webview to only loading content from our extension's `media` directory. - localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))], + console.log('make new panel') + this.panel = this.createWebviewPanel(column) - // prevents destroying the window when it is in the background - retainContextWhenHidden: true, - } - // Create and show a new webview panel - this.panel = vscode.window.createWebviewPanel(viewType, title, column, config) - - // Set the webview's initial html content - this.panel.webview.html = this.getHtmlForWebview() + } + } - // Listen for when the panel is disposed - // This happens when the user closes the panel or when the panel is closed programatically - this.panel.onDidDispose(() => this.dispose(), null, this.disposables) + private createWebviewPanel(column: number): vscode.WebviewPanel { + const viewType = 'CodeRoad' + const title = 'CodeRoad' + const config = { + // Enable javascript in the webview + enableScripts: true, + // And restric the webview to only loading content from our extension's `media` directory. + localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))], + // prevents destroying the window when it is in the background + retainContextWhenHidden: true, + } + return vscode.window.createWebviewPanel(viewType, title, column, config) + } - // Handle messages from the webview - this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables) + private getNonce(): string { + let text = '' + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)) } + return text } public async postMessage(action: CR.Action): Promise { + console.log('webview postMessage') + console.log(action) // Send a message to the webview webview. // You can send any JSON serializable data. const success = await this.panel.webview.postMessage(action) if (!success) { throw new Error(`Message post failure: ${JSON.stringify(action)}`) } + console.log('postMessage sent') } public dispose(): void { - ReactWebView.currentPanel = undefined - // Clean up our resources this.panel.dispose() @@ -108,9 +109,9 @@ class ReactWebView { const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' }) // Use a nonce to whitelist which scripts can be run - const nonce = getNonce() - const nonce2 = getNonce() - const nonce3 = getNonce() + const nonce = this.getNonce() + const nonce2 = this.getNonce() + const nonce3 = this.getNonce() return ` diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 2b96940e..ec1c41e7 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -6,32 +6,65 @@ import * as CR from 'typings' const COMMANDS = { START: 'coderoad.start', + NEW_OR_CONTINUE: 'coderoad.new_or_continue', OPEN_WEBVIEW: 'coderoad.open_webview', + SEND_STATE: 'coderoad.send_state', OPEN_FILE: 'coderoad.open_file', RUN_TEST: 'coderoad.test_run', } interface CreateCommandProps { context: vscode.ExtensionContext, - machine: CR.StateMachine + machine: CR.StateMachine, + storage: any, + git: any } // React panel webview let webview: any; +let initialTutorial: CR.Tutorial | undefined +let initialProgress: CR.Progress = { + levels: {}, + stages: {}, + steps: {}, + complete: false, +} -export const createCommands = ({ context, machine }: CreateCommandProps) => ({ +export const createCommands = ({ context, machine, storage, git }: CreateCommandProps) => ({ + // initialize [COMMANDS.START]: () => { // set local storage workspace setStorage(context.workspaceState) // activate machine webview = new ReactWebView(context.extensionPath, machine.onReceive) + console.log('webview', webview.panel.webview.postMessage) machine.activate() }, - [COMMANDS.OPEN_WEBVIEW]: () => { - webview.createOrShow(); + [COMMANDS.NEW_OR_CONTINUE]: async () => { + // verify that the user has a tutorial & progress + // verify git is setup with a coderoad remote + const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ + storage.getTutorial(), + storage.getProgress(), + git.gitVersion(), + git.gitCheckRemoteExists(), + ]) + initialTutorial = tutorial + initialProgress = progress + const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) + console.log('canContinue', canContinue) + // if a tutorial exists, "CONTINUE" + // otherwise start from "NEW" + machine.send(canContinue ? 'CONTINUE' : 'NEW') + }, + // open React webview + [COMMANDS.OPEN_WEBVIEW]: (column: number = vscode.ViewColumn.One) => { + webview.createOrShow(column); }, + // open a file [COMMANDS.OPEN_FILE]: async (relativeFilePath: string) => { + console.log(`OPEN_FILE ${JSON.stringify(relativeFilePath)}`) try { const workspaceRoot = vscode.workspace.rootPath if (!workspaceRoot) { @@ -43,5 +76,17 @@ export const createCommands = ({ context, machine }: CreateCommandProps) => ({ } catch (error) { console.log(`Failed to open file ${relativeFilePath}`, error) } + }, + // send messages to webview + [COMMANDS.SEND_STATE]: (action: CR.Action) => { + console.log(`SEND ${JSON.stringify(action)}`) + console.log('webview') + console.log(webview) + // console.log(webview.currentPanel) + // if (!webview || !webview.currentPanel) { + // throw new Error('No valid panel available') + // } + webview.postMessage(action) + } }) \ No newline at end of file diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts deleted file mode 100644 index a7e80ae3..00000000 --- a/src/editor/commands/start.ts +++ /dev/null @@ -1,45 +0,0 @@ -// import * as vscode from 'vscode' -// import { setWorkspaceRoot } from '../../services/node' -// import { setStorage } from '../../editor/storage' -// import { activate as activateMachine, default as machine } from '../../state' -// import * as storage from '../../services/storage' -// import * as git from '../../services/git' -// import * as CR from 'typings' - -// let initialTutorial: CR.Tutorial | undefined -// let initialProgress: CR.Progress = { -// levels: {}, -// stages: {}, -// steps: {}, -// complete: false, -// } - -// export default async function start(context: vscode.ExtensionContext): Promise { -// console.log('TUTORIAL_START') - -// // setup connection to workspace -// // await setWorkspaceRoot() -// // set workspace context path -// // await setStorage(context.workspaceState) - -// // initialize state machine -// activateMachine() - -// console.log('ACTION: start') - -// // verify that the user has a tutorial & progress -// // verify git is setup with a coderoad remote -// const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ -// storage.getTutorial(), -// storage.getProgress(), -// git.gitVersion(), -// git.gitCheckRemoteExists(), -// ]) -// initialTutorial = tutorial -// initialProgress = progress -// const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) -// console.log('canContinue', canContinue) -// // if a tutorial exists, "CONTINUE" -// // otherwise start from "NEW" -// machine.send(canContinue ? 'CONTINUE' : 'NEW') -// } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index db128230..26504478 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,6 +1,8 @@ import * as vscode from 'vscode' import * as CR from 'typings' import { createCommands } from './commands' +import * as storage from '../services/storage' +import * as git from '../services/git' interface Props { machine: CR.StateMachine, @@ -29,6 +31,8 @@ class Editor { const commands = createCommands({ context: this.context, machine: this.machine, + storage, + git, }) for (const cmd in commands) { const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd]) @@ -53,6 +57,11 @@ class Editor { // shut down state machine this.machine.deactivate() } + + // execute vscode command + public dispatch = (type: string, payload: any) => { + vscode.commands.executeCommand(type, payload) + } } export default Editor \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 62a91e02..ad32b1d8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,7 @@ import { setWorkspaceRoot } from './services/node' import StateMachine from './state' import Editor from './editor' + // state machine that governs application logic export const machine = new StateMachine() diff --git a/src/services/git/index.ts b/src/services/git/index.ts index 46cc654f..4cd7e34a 100644 --- a/src/services/git/index.ts +++ b/src/services/git/index.ts @@ -41,7 +41,7 @@ export async function gitLoadCommits(actions: CR.TutorialAction): Promise if (files) { for (const filePath of files) { - vscode.commands.executeCommand('coderoad.open_webview', filePath) + vscode.commands.executeCommand('coderoad.open_file', filePath) } } } diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index 3c435d38..c2abb20b 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -48,5 +48,8 @@ export default { createWebview() { console.log('execute coderoad.open_webview') vscode.commands.executeCommand('coderoad.open_webview') + }, + newOrContinue() { + vscode.commands.executeCommand('coderoad.new_or_continue') } } \ No newline at end of file diff --git a/src/state/index.ts b/src/state/index.ts index 516f1358..4d2905b4 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,6 +1,7 @@ import { interpret, Interpreter } from 'xstate' -import * as CR from '../typings' +import * as CR from 'typings' import machine from './machine' +import * as vscode from 'vscode' // machine interpreter // https://xstate.js.org/docs/guides/interpretation.html @@ -17,9 +18,11 @@ class StateMachine { this.service = interpret(machine, this.machineOptions) // logging .onTransition(state => { + console.log('onTransition', state.changed) if (state.changed) { console.log('next state') console.log(state.value) + vscode.commands.executeCommand('coderoad.send_state', state.value) } }) } @@ -30,6 +33,11 @@ class StateMachine { deactivate() { this.service.stop() } + send(action: string | CR.Action) { + console.log('machine.send') + console.log(action) + this.service.send(action) + } onReceive(action: CR.Action) { console.log('RECEIVED ACTION') console.log(action) diff --git a/src/state/machine.ts b/src/state/machine.ts index 49f6008b..273a915e 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -16,10 +16,16 @@ export const machine = Machine< initial: 'SelectTutorial', states: { SelectTutorial: { + onEntry: ['createWebview'], initial: 'Initial', states: { Initial: { - onEntry: ['createWebview'], + after: { + 1000: 'Startup' + } + }, + Startup: { + onEntry: ['newOrContinue'], on: { CONTINUE: 'ContinueTutorial', NEW: 'NewTutorial', diff --git a/typings/index.d.ts b/typings/index.d.ts index 73b2b99f..84b30f12 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,3 +1,5 @@ +import { send } from "xstate"; + export interface TutorialLevel { stageList: string[] content: { @@ -132,6 +134,7 @@ export interface MachineStateSchema { SelectTutorial: { states: { Initial: {} + Startup: {} NewTutorial: { states: { SelectTutorial: {} @@ -164,5 +167,6 @@ export interface MachineStateSchema { export interface StateMachine { activate(): void deactivate(): void + send(action: string | Action): void onReceive(action: Action): void } diff --git a/web-app/package.json b/web-app/package.json index f2e82aec..4ac6c449 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -19,7 +19,6 @@ "build": "react-scripts build", "postbuild": "cp -R ./build/ ../build/ && cp public/webpackBuild.js ../build/webpackBuild.js", "test": "react-scripts test", - "eject": "react-scripts eject", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" }, diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index a9953b31..5d7c9a34 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -1,5 +1,33 @@ import * as React from 'react' +import * as CR from 'typings' -export default () => { - return
Hello World
-} \ No newline at end of file +interface ReceivedEvent { + data: CR.Action +} + +const Routes = () => { + const [state, setState] = React.useState({ SelectTutorial: 'Initial' }) + const handleEvent = (event: ReceivedEvent): void => { + console.log('--- HANDLE EVENT ---') + const message = event.data + console.log(`RECEIVED: ${JSON.stringify(message)}`) + // messages from core + if (message.type === 'SET_STATE') { + setState(message.payload) + } + } + + // event bus listener + React.useEffect(() => { + const listener = 'message' + window.addEventListener(listener, handleEvent) + return () => { + window.removeEventListener(listener, handleEvent) + } + }) + return ( +
State: {JSON.stringify(state)}
+ ) +} + +export default Routes \ No newline at end of file From 9c530c95b403e18b0ad0d7e1aaecea6abfee9ead Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 14:29:43 -0700 Subject: [PATCH 08/12] update state in client --- src/editor/commands/index.ts | 12 +++++------- web-app/src/Routes.tsx | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index ec1c41e7..05dd3939 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -54,8 +54,8 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand initialProgress = progress const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) console.log('canContinue', canContinue) - // if a tutorial exists, "CONTINUE" - // otherwise start from "NEW" + // if a tutorial exists, 'CONTINUE' + // otherwise start from 'NEW' machine.send(canContinue ? 'CONTINUE' : 'NEW') }, // open React webview @@ -78,15 +78,13 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand } }, // send messages to webview - [COMMANDS.SEND_STATE]: (action: CR.Action) => { - console.log(`SEND ${JSON.stringify(action)}`) - console.log('webview') - console.log(webview) + [COMMANDS.SEND_STATE]: (payload: any) => { + console.log(`SEND ${JSON.stringify(payload)}`) // console.log(webview.currentPanel) // if (!webview || !webview.currentPanel) { // throw new Error('No valid panel available') // } - webview.postMessage(action) + webview.postMessage({ type: 'SET_STATE', payload }) } }) \ No newline at end of file diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 5d7c9a34..1d65c837 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -8,7 +8,6 @@ interface ReceivedEvent { const Routes = () => { const [state, setState] = React.useState({ SelectTutorial: 'Initial' }) const handleEvent = (event: ReceivedEvent): void => { - console.log('--- HANDLE EVENT ---') const message = event.data console.log(`RECEIVED: ${JSON.stringify(message)}`) // messages from core From 864a26ed7732f8ad05c2c7a692d2db820d09f5f5 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 15:00:19 -0700 Subject: [PATCH 09/12] setup cond rendering route --- web-app/src/Routes.tsx | 13 ++++++++++++- web-app/src/components/Cond/index.tsx | 17 +++++++++++++++++ web-app/src/components/Cond/utils/state.ts | 12 ++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 web-app/src/components/Cond/index.tsx create mode 100644 web-app/src/components/Cond/utils/state.ts diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 1d65c837..f83e0363 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -1,5 +1,8 @@ import * as React from 'react' import * as CR from 'typings' +import NewPage from './components/New' +import ContinuePage from './components/Continue' +import Cond from './components/Cond' interface ReceivedEvent { data: CR.Action @@ -24,8 +27,16 @@ const Routes = () => { window.removeEventListener(listener, handleEvent) } }) + return ( -
State: {JSON.stringify(state)}
+
+ + console.log('new!')} /> + + + console.log('continue!')} tutorials={[]} /> + +
) } diff --git a/web-app/src/components/Cond/index.tsx b/web-app/src/components/Cond/index.tsx new file mode 100644 index 00000000..9eed24ba --- /dev/null +++ b/web-app/src/components/Cond/index.tsx @@ -0,0 +1,17 @@ +import * as React from 'react' +import { stateMatch } from './utils/state' + +interface Props { + state: any + path: string + children: React.ReactElement +} + +const Cond = (props: Props) => { + if (!stateMatch(props.state, props.path)) { + return null + } + return props.children +} + +export default Cond diff --git a/web-app/src/components/Cond/utils/state.ts b/web-app/src/components/Cond/utils/state.ts new file mode 100644 index 00000000..5706cc57 --- /dev/null +++ b/web-app/src/components/Cond/utils/state.ts @@ -0,0 +1,12 @@ +export function stateMatch(state: any, statePath: string) { + let current = state + let paths = statePath.split('.') + try { + for (const p of paths) { + current = current[p] + } + } catch (error) { + return false + } + return current !== undefined +} \ No newline at end of file From 08b28afd3d66695d663a5dcc0afbef93735f1ef6 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 15:43:15 -0700 Subject: [PATCH 10/12] run received actions through state machine --- src/editor/ReactWebView.ts | 6 +++--- src/editor/commands/index.ts | 16 +++++----------- src/state/index.ts | 4 ++-- typings/index.d.ts | 2 +- web-app/src/Routes.tsx | 11 ++++++++++- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts index ca941fcd..de113d6b 100644 --- a/src/editor/ReactWebView.ts +++ b/src/editor/ReactWebView.ts @@ -12,9 +12,8 @@ class ReactWebView { private disposables: vscode.Disposable[] = [] private onReceive: any // TODO: properly type - public constructor(extensionPath: string, onReceive: any) { + public constructor(extensionPath: string) { this.extensionPath = extensionPath - this.onReceive = onReceive // Create and show a new webview panel this.panel = this.createWebviewPanel(vscode.ViewColumn.One) @@ -27,7 +26,8 @@ class ReactWebView { this.panel.onDidDispose(() => this.dispose(), null, this.disposables) // Handle messages from the webview - this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables) + const onReceive = (action: string | CR.Action) => vscode.commands.executeCommand('coderoad.receive_action', action) + this.panel.webview.onDidReceiveMessage(onReceive, null, this.disposables) console.log('webview loaded') } diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 05dd3939..45d45a47 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -9,6 +9,7 @@ const COMMANDS = { NEW_OR_CONTINUE: 'coderoad.new_or_continue', OPEN_WEBVIEW: 'coderoad.open_webview', SEND_STATE: 'coderoad.send_state', + RECEIVE_ACTION: 'coderoad.receive_action', OPEN_FILE: 'coderoad.open_file', RUN_TEST: 'coderoad.test_run', } @@ -22,13 +23,6 @@ interface CreateCommandProps { // React panel webview let webview: any; -let initialTutorial: CR.Tutorial | undefined -let initialProgress: CR.Progress = { - levels: {}, - stages: {}, - steps: {}, - complete: false, -} export const createCommands = ({ context, machine, storage, git }: CreateCommandProps) => ({ // initialize @@ -37,7 +31,7 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand setStorage(context.workspaceState) // activate machine - webview = new ReactWebView(context.extensionPath, machine.onReceive) + webview = new ReactWebView(context.extensionPath) console.log('webview', webview.panel.webview.postMessage) machine.activate() }, @@ -50,8 +44,6 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand git.gitVersion(), git.gitCheckRemoteExists(), ]) - initialTutorial = tutorial - initialProgress = progress const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) console.log('canContinue', canContinue) // if a tutorial exists, 'CONTINUE' @@ -85,6 +77,8 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand // throw new Error('No valid panel available') // } webview.postMessage({ type: 'SET_STATE', payload }) - + }, + [COMMANDS.RECEIVE_ACTION]: (action: string | CR.Action) => { + machine.onReceive(action) } }) \ No newline at end of file diff --git a/src/state/index.ts b/src/state/index.ts index 4d2905b4..d7f0e827 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -38,9 +38,9 @@ class StateMachine { console.log(action) this.service.send(action) } - onReceive(action: CR.Action) { - console.log('RECEIVED ACTION') + onReceive(action: string | CR.Action) { console.log(action) + this.service.send(action) } } diff --git a/typings/index.d.ts b/typings/index.d.ts index 84b30f12..044be525 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -168,5 +168,5 @@ export interface StateMachine { activate(): void deactivate(): void send(action: string | Action): void - onReceive(action: Action): void + onReceive(action: string | Action): void } diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index f83e0363..5822eea5 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -8,6 +8,15 @@ interface ReceivedEvent { data: CR.Action } +declare var acquireVsCodeApi: any + +const vscode = acquireVsCodeApi() + +function send(event: string|CR.Action) { + return vscode.postMessage(event) +} + + const Routes = () => { const [state, setState] = React.useState({ SelectTutorial: 'Initial' }) const handleEvent = (event: ReceivedEvent): void => { @@ -31,7 +40,7 @@ const Routes = () => { return (
- console.log('new!')} /> + send('TUTORIAL_START')} /> console.log('continue!')} tutorials={[]} /> From 3cdce4d9aa088558b0956273c0de0229e1bf8292 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 16:02:09 -0700 Subject: [PATCH 11/12] setup state/data --- src/editor/commands/index.ts | 12 ++++++------ src/state/index.ts | 6 ++++-- web-app/src/Routes.tsx | 25 ++++++++++++++----------- web-app/src/utils/vscode.ts | 9 +++++++++ 4 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 web-app/src/utils/vscode.ts diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 45d45a47..9ea0b92e 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -9,6 +9,7 @@ const COMMANDS = { NEW_OR_CONTINUE: 'coderoad.new_or_continue', OPEN_WEBVIEW: 'coderoad.open_webview', SEND_STATE: 'coderoad.send_state', + SEND_DATA: 'coderoad.send_data', RECEIVE_ACTION: 'coderoad.receive_action', OPEN_FILE: 'coderoad.open_file', RUN_TEST: 'coderoad.test_run', @@ -70,15 +71,14 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand } }, // send messages to webview - [COMMANDS.SEND_STATE]: (payload: any) => { - console.log(`SEND ${JSON.stringify(payload)}`) - // console.log(webview.currentPanel) - // if (!webview || !webview.currentPanel) { - // throw new Error('No valid panel available') - // } + [COMMANDS.SEND_STATE]: (payload: { data: any, state: any }) => { webview.postMessage({ type: 'SET_STATE', payload }) }, + [COMMANDS.SEND_DATA]: (payload: { data: any }) => { + webview.postMessage({ type: 'SET_DATA', payload }) + }, [COMMANDS.RECEIVE_ACTION]: (action: string | CR.Action) => { + console.log('onReceiveAction', action) machine.onReceive(action) } }) \ No newline at end of file diff --git a/src/state/index.ts b/src/state/index.ts index d7f0e827..6a7cb3bb 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -18,11 +18,13 @@ class StateMachine { this.service = interpret(machine, this.machineOptions) // logging .onTransition(state => { - console.log('onTransition', state.changed) + console.log('onTransition', state) if (state.changed) { console.log('next state') console.log(state.value) - vscode.commands.executeCommand('coderoad.send_state', state.value) + vscode.commands.executeCommand('coderoad.send_state', { state: state.value, data: state.context }) + } else { + vscode.commands.executeCommand('coderoad.send_data', { data: state.context }) } }) } diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 5822eea5..6e0c9e88 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -1,5 +1,7 @@ import * as React from 'react' import * as CR from 'typings' +import { send } from './utils/vscode' + import NewPage from './components/New' import ContinuePage from './components/Continue' import Cond from './components/Cond' @@ -8,23 +10,21 @@ interface ReceivedEvent { data: CR.Action } -declare var acquireVsCodeApi: any - -const vscode = acquireVsCodeApi() - -function send(event: string|CR.Action) { - return vscode.postMessage(event) -} - - const Routes = () => { const [state, setState] = React.useState({ SelectTutorial: 'Initial' }) + const [data, setData] = React.useState({}) + + const handleEvent = (event: ReceivedEvent): void => { const message = event.data - console.log(`RECEIVED: ${JSON.stringify(message)}`) + console.log('RECEIVED') + console.log(message) // messages from core if (message.type === 'SET_STATE') { - setState(message.payload) + setState(message.payload.state) + setData(message.payload.data) + } else if (message.type === 'SET_DATA') { + setData(message.payload.data) } } @@ -37,8 +37,11 @@ const Routes = () => { } }) + // TODO: refactor cond to user and accept first route as if/else if return (
+
state: {JSON.stringify(state)}
+

data:{JSON.stringify(data)}

send('TUTORIAL_START')} /> diff --git a/web-app/src/utils/vscode.ts b/web-app/src/utils/vscode.ts new file mode 100644 index 00000000..c53a5ebf --- /dev/null +++ b/web-app/src/utils/vscode.ts @@ -0,0 +1,9 @@ +import { Action } from 'typings' + +declare var acquireVsCodeApi: any + +const vscode = acquireVsCodeApi() + +export function send(event: string | Action) { + return vscode.postMessage(event) +} From 99372b4e9ac156dc3836b1727eba2d8a1e5fbd36 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 9 Jun 2019 16:03:47 -0700 Subject: [PATCH 12/12] move components into containers --- web-app/src/Routes.tsx | 5 +++-- .../src/{components => containers}/Continue/ContinueItem.tsx | 0 web-app/src/{components => containers}/Continue/index.tsx | 0 web-app/src/{components => containers}/New/index.tsx | 0 web-app/stories/Continue.stories.tsx | 2 +- web-app/stories/New.stories.tsx | 2 +- 6 files changed, 5 insertions(+), 4 deletions(-) rename web-app/src/{components => containers}/Continue/ContinueItem.tsx (100%) rename web-app/src/{components => containers}/Continue/index.tsx (100%) rename web-app/src/{components => containers}/New/index.tsx (100%) diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 6e0c9e88..ca1d1512 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -2,9 +2,10 @@ import * as React from 'react' import * as CR from 'typings' import { send } from './utils/vscode' -import NewPage from './components/New' -import ContinuePage from './components/Continue' import Cond from './components/Cond' +import NewPage from './containers/New' +import ContinuePage from './containers/Continue' + interface ReceivedEvent { data: CR.Action diff --git a/web-app/src/components/Continue/ContinueItem.tsx b/web-app/src/containers/Continue/ContinueItem.tsx similarity index 100% rename from web-app/src/components/Continue/ContinueItem.tsx rename to web-app/src/containers/Continue/ContinueItem.tsx diff --git a/web-app/src/components/Continue/index.tsx b/web-app/src/containers/Continue/index.tsx similarity index 100% rename from web-app/src/components/Continue/index.tsx rename to web-app/src/containers/Continue/index.tsx diff --git a/web-app/src/components/New/index.tsx b/web-app/src/containers/New/index.tsx similarity index 100% rename from web-app/src/components/New/index.tsx rename to web-app/src/containers/New/index.tsx diff --git a/web-app/stories/Continue.stories.tsx b/web-app/stories/Continue.stories.tsx index 6845b72c..1f4df9d0 100644 --- a/web-app/stories/Continue.stories.tsx +++ b/web-app/stories/Continue.stories.tsx @@ -3,7 +3,7 @@ import React from 'react' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' -import Continue from '../src/components/Continue' +import Continue from '../src/containers/Continue' import demo from './data/basic' storiesOf('Continue', module).add('Page', () => ) diff --git a/web-app/stories/New.stories.tsx b/web-app/stories/New.stories.tsx index 643311d0..5ade6701 100644 --- a/web-app/stories/New.stories.tsx +++ b/web-app/stories/New.stories.tsx @@ -3,6 +3,6 @@ import React from 'react' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' -import New from '../src/components/New' +import New from '../src/containers/New' storiesOf('New', module).add('Page', () => )