From 820097a9ddc67ca15a7f9f9a4d2002db8ff58070 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 26 May 2019 19:19:37 -0700 Subject: [PATCH 01/11] setup level/stage tree --- package.json | 14 +++- src/views/index.ts | 3 + src/views/progress/treeDataProvider.ts | 90 ++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/views/progress/treeDataProvider.ts diff --git a/package.json b/package.json index db918883..d0778c66 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,20 @@ "views": { "coderoad-tutorial": [ { - "id": "tutorial-summary", - "name": "Summary" + "id": "progress", + "name": "Progress" }, { - "id": "tutorial-steps", + "id": "explanation", + "name": "Explanation" + }, + { + "id": "instructions", "name": "Instructions" + }, + { + "id": "hints", + "name": "Hints" } ] } diff --git a/src/views/index.ts b/src/views/index.ts index c550d756..c16d8ce1 100644 --- a/src/views/index.ts +++ b/src/views/index.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode' +import { TestView } from './progress/treeDataProvider' const createViews = (context: vscode.ExtensionContext) => { // TODO: level/stage select @@ -6,6 +7,8 @@ const createViews = (context: vscode.ExtensionContext) => { // TODO: instruction view // docs: https://code.visualstudio.com/api/extension-guides/tree-view // vscode.window.registerTreeDataProvider('nodeDependencies', new TreeDataProvider(context.workspace)) + + new TestView(context); } export default createViews diff --git a/src/views/progress/treeDataProvider.ts b/src/views/progress/treeDataProvider.ts new file mode 100644 index 00000000..c8b08bec --- /dev/null +++ b/src/views/progress/treeDataProvider.ts @@ -0,0 +1,90 @@ +import * as vscode from 'vscode' + +const tree = { + 'level1Id': { + 'stage1Id': {}, + 'stage2Id': {}, + }, + 'level2Id': { + 'l2s1': {}, + 'l2s2': {} + } +} + +function getChildren(key: string): string[] { + if (!key) { + return Object.keys(tree); + } + let treeElement = getTreeElement(key); + if (treeElement) { + return Object.values(treeElement); + } + return []; +} + +function getTreeItem(key: string): vscode.TreeItem { + const treeElement = getTreeElement(key); + return { + label: key, + tooltip: `Tooltip for ${key}`, + collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None + }; +} + +function getTreeElement(element: any): any { + let parent = tree; + const levels = Object.keys(parent) + + if (levels.includes(element)) { + return Object.keys(parent[element]) + } else { + return null + } +} + +function getNode(key: string): { key: string } { + if (!nodes[key]) { + nodes[key] = new Key(key); + } + return nodes[key]; +} + +export class TestView { + + constructor(context: vscode.ExtensionContext) { + const view = vscode.window.createTreeView('progress', { + treeDataProvider: aNodeWithIdTreeDataProvider(), + showCollapseAll: true + }); + // vscode.commands.registerCommand('progress.reveal', async () => { + // const key = await vscode.window.showInputBox({ placeHolder: 'Type the label of the item to reveal' }); + // if (key) { + // await view.reveal({ key }, { focus: true, select: false, expand: true }); + // } + // }); + } +} + +let nodes = {}; + +function aNodeWithIdTreeDataProvider(): vscode.TreeDataProvider<{ key: string }> { + return { + getChildren: (element: { key: string }): { key: string }[] => { + return getChildren(element ? element.key : '').map((key: string) => getNode(key)); + }, + getTreeItem: (element: { key: string }): vscode.TreeItem => { + const treeItem = getTreeItem(element.key); + treeItem.id = element.key; + return treeItem; + }, + getParent: ({ key }: { key: string }): { key: string } | undefined => { + return new Key(key) + } + }; +} + + + +class Key { + constructor(readonly key: string) { } +} \ No newline at end of file From 32edbcf7e6f6bc62c14b76fc465f6b24451154d6 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 1 Jun 2019 21:42:41 -0700 Subject: [PATCH 02/11] setup xstate machine as core --- TODO.md | 6 + package-lock.json | 5 + package.json | 49 +------ src/commands/index.ts | 14 +- src/commands/tutorialLoad.ts | 125 +++++++++--------- src/state/actions/index.ts | 1 + src/state/context/index.ts | 20 +++ src/{ => state/context}/tutorials/basic.ts | 0 src/state/guards/index.ts | 1 + src/state/machine.ts | 144 +++++++++++++++++++++ src/typings/context.d.ts | 21 +++ src/typings/index.d.ts | 43 ++++++ src/utils/fetch.ts | 2 +- src/views/index.ts | 9 +- src/views/progress/treeDataProvider.ts | 90 ------------- 15 files changed, 318 insertions(+), 212 deletions(-) create mode 100644 TODO.md create mode 100644 src/state/actions/index.ts create mode 100644 src/state/context/index.ts rename src/{ => state/context}/tutorials/basic.ts (100%) create mode 100644 src/state/guards/index.ts create mode 100644 src/state/machine.ts create mode 100644 src/typings/context.d.ts delete mode 100644 src/views/progress/treeDataProvider.ts diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..14c5a713 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# Todos +- add to scripts when url fixed + +``` +"postinstall": "node ./node_modules/vscode/bin/install", +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index eb72b493..aa84afab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -923,6 +923,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true + }, + "xstate": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.6.0.tgz", + "integrity": "sha512-1bPy5an0QLUX3GiqtJB68VgBrLIotxZwpItDBO3vbakgzEY0D8UG1hsbM4MJM3BN1Y43pnac3ChmPL5s+Bca0A==" } } } diff --git a/package.json b/package.json index d0778c66..cdae712b 100644 --- a/package.json +++ b/package.json @@ -16,56 +16,12 @@ "main": "./out/extension.js", "contributes": { "commands": [ - { - "command": "coderoad.tutorial_setup", - "title": "Tutorial Setup", - "category": "CodeRoad" - }, { "command": "coderoad.tutorial_load", "title": "Load Tutorial", "category": "CodeRoad" - }, - { - "command": "coderoad.test_run", - "title": "Run Test", - "category": "CodeRoad" - }, - { - "command": "coderoad.solution_load", - "title": "Load Solution", - "category": "CodeRoad" } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "coderoad-tutorial", - "title": "CodeRoad Tutorial", - "icon": "resources/icons/icon.svg" - } - ] - }, - "views": { - "coderoad-tutorial": [ - { - "id": "progress", - "name": "Progress" - }, - { - "id": "explanation", - "name": "Explanation" - }, - { - "id": "instructions", - "name": "Instructions" - }, - { - "id": "hints", - "name": "Hints" - } - ] - } + ] }, "scripts": { "vscode:prepublish": "npm run compile", @@ -82,5 +38,8 @@ "tslint-config-prettier": "^1.18.0", "typescript": "^3.3.1", "vscode": "^1.1.28" + }, + "dependencies": { + "xstate": "^4.6.0" } } diff --git a/src/commands/index.ts b/src/commands/index.ts index cd8623c6..e90a93d1 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,15 +1,15 @@ import * as vscode from 'vscode' -import runTest from './runTest' +// import runTest from './runTest' import tutorialLoad from './tutorialLoad' -import loadSolution from './loadSolution' +// import loadSolution from './loadSolution' // import quit from './quit' const COMMANDS = { - TUTORIAL_SETUP: 'coderoad.tutorial_setup', + // TUTORIAL_SETUP: 'coderoad.tutorial_setup', TUTORIAL_LOAD: 'coderoad.tutorial_load', - RUN_TEST: 'coderoad.test_run', - LOAD_SOLUTION: 'coderoad.solution_load', + // RUN_TEST: 'coderoad.test_run', + // LOAD_SOLUTION: 'coderoad.solution_load', // QUIT: 'coderoad.quit', } @@ -18,8 +18,8 @@ export default (context: vscode.ExtensionContext): void => { [COMMANDS.TUTORIAL_LOAD](): void { tutorialLoad(context) }, - [COMMANDS.RUN_TEST]: runTest, - [COMMANDS.LOAD_SOLUTION]: loadSolution, + // [COMMANDS.RUN_TEST]: runTest, + // [COMMANDS.LOAD_SOLUTION]: loadSolution, // [COMMANDS.QUIT]: () => quit(context.subscriptions), } diff --git a/src/commands/tutorialLoad.ts b/src/commands/tutorialLoad.ts index 08c2bd32..a7baddac 100644 --- a/src/commands/tutorialLoad.ts +++ b/src/commands/tutorialLoad.ts @@ -62,69 +62,72 @@ async function validateCanContinue(): Promise { } export default async function tutorialLoad(context: vscode.ExtensionContext): Promise { + console.log(`tutorialLoad ${JSON.stringify(context)}`) + // setup connection to workspace await rootSetup(context) - - const modes = ['New'] - - const canContinue = await validateCanContinue() - if (canContinue) { - modes.push('Continue') - } - - const selectedMode: string | undefined = await vscode.window.showQuickPick(modes) - - if (!selectedMode) { - throw new Error('No mode selected') - return - } - - interface TutorialQuickPickItem extends vscode.QuickPickItem { - id: string - } - - // load tutorial summaries - const tutorialsData: { [id: string]: CR.TutorialSummary } = await fetch({ - resource: 'getTutorialsSummary', - }) - const selectableTutorials: TutorialQuickPickItem[] = Object.keys(tutorialsData).map(id => { - const tutorial = tutorialsData[id] - return { - label: tutorial.title, - description: tutorial.description, - // detail: '', // optional additional info - id, - } - }) - const selectedTutorial: TutorialQuickPickItem | undefined = await vscode.window.showQuickPick(selectableTutorials) - - if (!selectedTutorial) { - throw new Error('No tutorial selected') - } - - // load specific tutorial - const tutorial: CR.Tutorial | undefined = await fetch({ - resource: 'getTutorial', - params: { id: selectedTutorial.id }, - }) - - if (!tutorial) { - throw new Error('No tutorial found') - } - - switch (selectedMode) { - // new tutorial - case modes[0]: - await newTutorial(tutorial) - break - // continue - case modes[1]: - await continueTutorial() - break - } - - // setup hook to run tests on save - onSaveHook(tutorial.meta.languages) + return; + + // const modes = ['New'] + + // const canContinue = await validateCanContinue() + // if (canContinue) { + // modes.push('Continue') + // } + + // const selectedMode: string | undefined = await vscode.window.showQuickPick(modes) + + // if (!selectedMode) { + // throw new Error('No mode selected') + // return + // } + + // interface TutorialQuickPickItem extends vscode.QuickPickItem { + // id: string + // } + + // // load tutorial summaries + // const tutorialsData: { [id: string]: CR.TutorialSummary } = await fetch({ + // resource: 'getTutorialsSummary', + // }) + // const selectableTutorials: TutorialQuickPickItem[] = Object.keys(tutorialsData).map(id => { + // const tutorial = tutorialsData[id] + // return { + // label: tutorial.title, + // description: tutorial.description, + // // detail: '', // optional additional info + // id, + // } + // }) + // const selectedTutorial: TutorialQuickPickItem | undefined = await vscode.window.showQuickPick(selectableTutorials) + + // if (!selectedTutorial) { + // throw new Error('No tutorial selected') + // } + + // // load specific tutorial + // const tutorial: CR.Tutorial | undefined = await fetch({ + // resource: 'getTutorial', + // params: { id: selectedTutorial.id }, + // }) + + // if (!tutorial) { + // throw new Error('No tutorial found') + // } + + // switch (selectedMode) { + // // new tutorial + // case modes[0]: + // await newTutorial(tutorial) + // break + // // continue + // case modes[1]: + // await continueTutorial() + // break + // } + + // // setup hook to run tests on save + // onSaveHook(tutorial.meta.languages) // TODO: start } diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts new file mode 100644 index 00000000..56004c9f --- /dev/null +++ b/src/state/actions/index.ts @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/src/state/context/index.ts b/src/state/context/index.ts new file mode 100644 index 00000000..3b0443a4 --- /dev/null +++ b/src/state/context/index.ts @@ -0,0 +1,20 @@ +import basicTutorialData from './tutorials/basic' +import * as CR from 'typings' + +const tutorialContext: CR.MachineContext = { + position: { + levelId: '', + stageId: '', + stepId: '', + }, + progress: { + levels: {}, + stages: {}, + steps: {}, + complete: false, + }, + // TODO: load tutorial instead of preloading demo + data: basicTutorialData.data, +} + +export default tutorialContext \ No newline at end of file diff --git a/src/tutorials/basic.ts b/src/state/context/tutorials/basic.ts similarity index 100% rename from src/tutorials/basic.ts rename to src/state/context/tutorials/basic.ts diff --git a/src/state/guards/index.ts b/src/state/guards/index.ts new file mode 100644 index 00000000..56004c9f --- /dev/null +++ b/src/state/guards/index.ts @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/src/state/machine.ts b/src/state/machine.ts new file mode 100644 index 00000000..15bd94f5 --- /dev/null +++ b/src/state/machine.ts @@ -0,0 +1,144 @@ +import { Machine } from 'xstate' +import * as CR from 'typings' + +import actions from './actions' +import guards from './guards' +import initialContext from './context' + +// TODO: replace with API + +export const tutorialMachine = Machine< + CR.MachineContext, + CR.MachineStateSchema, + CR.MachineEvent +>( + { + id: 'tutorial', + context: initialContext, + initial: 'initial', + states: { + initial: { + on: { + START: [ + { + target: 'continue', + cond: 'hasExistingTutorial', + }, + { + target: 'new', + }, + ], + }, + }, + new: { + on: { + TUTORIAL_START: 'loading', + }, + }, + continue: { + on: { + TUTORIAL_START: 'loading', + }, + }, + loading: { + on: { + TUTORIAL_LOADED: [ + { + target: 'summary', + cond: 'hasNoProgress', + }, + { + target: 'level', + cond: 'hasNoLevelProgress', + }, + { + target: 'stage', + }, + ], + }, + }, + summary: { + on: { + NEXT: 'level', + }, + }, + level: { + onEntry: ['loadLevel'], + on: { + NEXT: 'stage', + BACK: 'summary', + }, + }, + stage: { + onEntry: ['loadStage'], + initial: 'stageNormal', + states: { + stageNormal: { + on: { + TEST_RUN: 'testRunning', + STEP_SOLUTION_LOAD: { + actions: ['callSolution'], + }, + }, + }, + testRunning: { + on: { + TEST_SUCCESS: [ + { + target: 'complete', + cond: 'tasksComplete', + }, + { + target: 'testPass', + }, + ], + TEST_FAILURE: 'testFail', + }, + }, + testPass: { + onEntry: ['stepComplete'], + on: { + NEXT: [ + { + target: 'stageNormal', + cond: 'hasNextStep', + }, + { + target: 'stageComplete', + }, + ], + }, + }, + testFail: { + on: { + RETURN: 'stageNormal', + }, + }, + stageComplete: { + on: { + NEXT: [ + { + target: 'stage', + cond: 'hasNextStage', + }, + { + target: 'level', + cond: 'hasNextLevel', + }, + { + target: 'complete', + }, + ], + }, + }, + }, + }, + complete: {}, + }, + }, + { + actions, + guards, + activities: {}, + }, +) diff --git a/src/typings/context.d.ts b/src/typings/context.d.ts new file mode 100644 index 00000000..66155638 --- /dev/null +++ b/src/typings/context.d.ts @@ -0,0 +1,21 @@ +import * as CR from './index' + +export interface Step extends Exclude { + status: { + complete: boolean + active: boolean + } +} + +export interface ReceivedEvent { + data: CR.Action +} + +export interface StageStepStatus { + active: boolean + complete: boolean +} + +export interface StageWithStatus extends CR.TutorialStage { + status: StageStepStatus +} diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index c4e5e07e..4e5f2b56 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -104,3 +104,46 @@ export interface Action { payload?: any meta?: any } + +export interface MachineContext { + position: Position + data: { + summary: TutorialSummary + levels: { + [levelId: string]: TutorialLevel + } + stages: { + [stageId: string]: TutorialStage + } + steps: { + [stepId: string]: TutorialStep + } + } + progress: Progress +} + +export interface MachineEvent { + type: string + payload?: any +} + +export interface MachineStateSchema { + states: { + initial: {} + new: {} + continue: {} + loading: {} + summary: {} + level: {} + stage: { + states: { + stageNormal: {} + testRunning: {} + testPass: {} + testFail: {} + stageComplete: {} + } + } + complete: {} + } +} \ No newline at end of file diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index d1a53b89..ade5e52e 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -1,7 +1,7 @@ import * as CR from 'typings' // temporary tutorials -import basicTutorial from '../tutorials/basic' +import basicTutorial from '../state/context/tutorials/basic' interface Options { resource: string diff --git a/src/views/index.ts b/src/views/index.ts index c16d8ce1..60a7d871 100644 --- a/src/views/index.ts +++ b/src/views/index.ts @@ -1,14 +1,7 @@ import * as vscode from 'vscode' -import { TestView } from './progress/treeDataProvider' const createViews = (context: vscode.ExtensionContext) => { - // TODO: level/stage select - // TODO: summary view - // TODO: instruction view - // docs: https://code.visualstudio.com/api/extension-guides/tree-view - // vscode.window.registerTreeDataProvider('nodeDependencies', new TreeDataProvider(context.workspace)) - - new TestView(context); + // TODO: create views } export default createViews diff --git a/src/views/progress/treeDataProvider.ts b/src/views/progress/treeDataProvider.ts deleted file mode 100644 index c8b08bec..00000000 --- a/src/views/progress/treeDataProvider.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as vscode from 'vscode' - -const tree = { - 'level1Id': { - 'stage1Id': {}, - 'stage2Id': {}, - }, - 'level2Id': { - 'l2s1': {}, - 'l2s2': {} - } -} - -function getChildren(key: string): string[] { - if (!key) { - return Object.keys(tree); - } - let treeElement = getTreeElement(key); - if (treeElement) { - return Object.values(treeElement); - } - return []; -} - -function getTreeItem(key: string): vscode.TreeItem { - const treeElement = getTreeElement(key); - return { - label: key, - tooltip: `Tooltip for ${key}`, - collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None - }; -} - -function getTreeElement(element: any): any { - let parent = tree; - const levels = Object.keys(parent) - - if (levels.includes(element)) { - return Object.keys(parent[element]) - } else { - return null - } -} - -function getNode(key: string): { key: string } { - if (!nodes[key]) { - nodes[key] = new Key(key); - } - return nodes[key]; -} - -export class TestView { - - constructor(context: vscode.ExtensionContext) { - const view = vscode.window.createTreeView('progress', { - treeDataProvider: aNodeWithIdTreeDataProvider(), - showCollapseAll: true - }); - // vscode.commands.registerCommand('progress.reveal', async () => { - // const key = await vscode.window.showInputBox({ placeHolder: 'Type the label of the item to reveal' }); - // if (key) { - // await view.reveal({ key }, { focus: true, select: false, expand: true }); - // } - // }); - } -} - -let nodes = {}; - -function aNodeWithIdTreeDataProvider(): vscode.TreeDataProvider<{ key: string }> { - return { - getChildren: (element: { key: string }): { key: string }[] => { - return getChildren(element ? element.key : '').map((key: string) => getNode(key)); - }, - getTreeItem: (element: { key: string }): vscode.TreeItem => { - const treeItem = getTreeItem(element.key); - treeItem.id = element.key; - return treeItem; - }, - getParent: ({ key }: { key: string }): { key: string } | undefined => { - return new Key(key) - } - }; -} - - - -class Key { - constructor(readonly key: string) { } -} \ No newline at end of file From 4be011f457ad47d599e71a0a2e94741540122c7a Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 1 Jun 2019 22:00:10 -0700 Subject: [PATCH 03/11] setup start w/ continue or new --- src/state/actions/index.ts | 17 ++++++++++++++++- src/state/guards/index.ts | 5 ++++- src/state/machine.ts | 12 +++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index 56004c9f..b2b1f86a 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -1 +1,16 @@ -export default {} \ No newline at end of file +import { send } from "xstate"; +import * as storage from '../../services/storage' +import * as git from '../../services/git' + +export default { + start: async () => { + const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ + storage.getTutorial(), + storage.getProgress(), + git.gitVersion(), + git.gitCheckRemoteExists(), + ]) + const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) + send(canContinue ? 'CONTINUE' : 'NEW') + } +} \ No newline at end of file diff --git a/src/state/guards/index.ts b/src/state/guards/index.ts index 56004c9f..6b59b251 100644 --- a/src/state/guards/index.ts +++ b/src/state/guards/index.ts @@ -1 +1,4 @@ -export default {} \ No newline at end of file + +export default { + +} diff --git a/src/state/machine.ts b/src/state/machine.ts index 15bd94f5..324a279d 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -18,16 +18,10 @@ export const tutorialMachine = Machine< initial: 'initial', states: { initial: { + onEntry: 'start', on: { - START: [ - { - target: 'continue', - cond: 'hasExistingTutorial', - }, - { - target: 'new', - }, - ], + CONTINUE: 'continue', + NEW: 'new', }, }, new: { From 6993147a0c124a894979c835635f060ef17b1181 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 1 Jun 2019 23:05:32 -0700 Subject: [PATCH 04/11] add machine loadTutorial --- src/state/actions/index.ts | 44 +++++++- src/state/machine.ts | 204 +++++++++++++++++++++---------------- src/typings/index.d.ts | 39 ++++--- 3 files changed, 182 insertions(+), 105 deletions(-) diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index b2b1f86a..ae2f3bdd 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -1,7 +1,16 @@ -import { send } from "xstate"; +import { assign, send } from 'xstate' +import * as CR from 'typings' import * as storage from '../../services/storage' import * as git from '../../services/git' +let initialTutorial: CR.Tutorial | undefined +let initialProgress: CR.Progress = { + levels: {}, + stages: {}, + steps: {}, + complete: false, +} + export default { start: async () => { const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ @@ -10,7 +19,38 @@ export default { git.gitVersion(), git.gitCheckRemoteExists(), ]) + initialTutorial = tutorial + initialProgress = progress const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) send(canContinue ? 'CONTINUE' : 'NEW') - } + }, + loadTutorial: assign({ + // load initial data, progress & position + data(): CR.TutorialData { + if (!initialTutorial) { + throw new Error('No Tutorial loaded') + } + return initialTutorial.data + + }, + progress(): CR.Progress { return initialProgress }, + position() { + if (!initialTutorial) { + throw new Error('No Tutorial loaded') + } + const { data } = initialTutorial + + const levelId = data.summary.levelList[0] + const stageId = data.levels[levelId].stageList[0] + const stepId = data.stages[stageId].stepList[0] + + const position = { + levelId, + stageId, + stepId, + } + + return position + } + }), } \ No newline at end of file diff --git a/src/state/machine.ts b/src/state/machine.ts index 324a279d..77fbd621 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -15,120 +15,144 @@ export const tutorialMachine = Machine< { id: 'tutorial', context: initialContext, - initial: 'initial', + initial: 'Start', states: { - initial: { - onEntry: 'start', - on: { - CONTINUE: 'continue', - NEW: 'new', - }, - }, - new: { - on: { - TUTORIAL_START: 'loading', - }, - }, - continue: { - on: { - TUTORIAL_START: 'loading', - }, - }, - loading: { - on: { - TUTORIAL_LOADED: [ - { - target: 'summary', - cond: 'hasNoProgress', - }, - { - target: 'level', - cond: 'hasNoLevelProgress', - }, - { - target: 'stage', - }, - ], - }, - }, - summary: { - on: { - NEXT: 'level', - }, - }, - level: { - onEntry: ['loadLevel'], - on: { - NEXT: 'stage', - BACK: 'summary', - }, - }, - stage: { - onEntry: ['loadStage'], - initial: 'stageNormal', + Start: { states: { - stageNormal: { + Initial: { + onEntry: 'start', on: { - TEST_RUN: 'testRunning', - STEP_SOLUTION_LOAD: { - actions: ['callSolution'], - }, + CONTINUE: 'ContinueTutorial', + NEW: 'NewTutorial', }, }, - testRunning: { - on: { - TEST_SUCCESS: [ - { - target: 'complete', - cond: 'tasksComplete', + NewTutorial: { + initial: 'SelectTutorial', + states: { + SelectTutorial: { + on: { + TUTORIAL_START: 'InitializeTutorial', }, - { - target: 'testPass', - }, - ], - TEST_FAILURE: 'testFail', - }, + }, + InitializeTutorial: { + on: { + TUTORIAL_LOADED: 'Tutorial' + } + }, + } + + }, + ContinueTutorial: { + onEntry: 'loadTutorial', + on: { + TUTORIAL_START: { + target: 'Tutorial', + } + } }, - testPass: { - onEntry: ['stepComplete'], + } + }, + Tutorial: { + initial: 'Initialize', + states: { + Initialize: { on: { - NEXT: [ + TUTORIAL_LOADED: [ + { + target: 'Summary', + cond: 'hasNoProgress', + }, { - target: 'stageNormal', - cond: 'hasNextStep', + target: 'Level', + cond: 'hasNoLevelProgress', }, { - target: 'stageComplete', + target: 'Stage', }, ], }, }, - testFail: { + + Summary: { on: { - RETURN: 'stageNormal', + NEXT: 'Level', }, }, - stageComplete: { + Level: { + onEntry: ['loadLevel'], on: { - NEXT: [ - { - target: 'stage', - cond: 'hasNextStage', + NEXT: 'Stage', + BACK: 'Summary', + }, + }, + Stage: { + onEntry: ['loadStage'], + initial: 'StageNormal', + states: { + StageNormal: { + on: { + TEST_RUN: 'TestRunning', + STEP_SOLUTION_LOAD: { + actions: ['callSolution'], + }, }, - { - target: 'level', - cond: 'hasNextLevel', + }, + TestRunning: { + on: { + TEST_SUCCESS: [ + { + target: 'StageComplete', + cond: 'tasksComplete', + }, + { + target: 'TestPass', + }, + ], + TEST_FAILURE: 'TestFail', }, - { - target: 'complete', + }, + TestPass: { + onEntry: ['stepComplete'], + on: { + NEXT: [ + { + target: 'StageNormal', + cond: 'hasNextStep', + }, + { + target: 'StageComplete', + }, + ], }, - ], + }, + TestFail: { + on: { + RETURN: 'StageNormal', + }, + }, + StageComplete: { + on: { + NEXT: [ + { + target: 'Stage', + cond: 'hasNextStage', + }, + { + target: 'Level', + cond: 'hasNextLevel', + }, + { + target: 'EndTutorial', + }, + ], + }, + }, }, }, - }, - }, - complete: {}, - }, + EndTutorial: {}, + } + } + } }, { actions, diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 4e5f2b56..d2cc4dcb 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -129,21 +129,34 @@ export interface MachineEvent { export interface MachineStateSchema { states: { - initial: {} - new: {} - continue: {} - loading: {} - summary: {} - level: {} - stage: { + Start: { states: { - stageNormal: {} - testRunning: {} - testPass: {} - testFail: {} - stageComplete: {} + Initial: {} + NewTutorial: { + states: { + SelectTutorial: {} + InitializeTutorial: {} + } + } + ContinueTutorial: {} + } + } + Tutorial: { + states: { + Initialize: {} + Summary: {} + Level: {} + Stage: { + states: { + StageNormal: {} + TestRunning: {} + TestPass: {} + TestFail: {} + StageComplete: {} + } + } + EndTutorial: {} } } - complete: {} } } \ No newline at end of file From 04653e2f736790df082abc0abddf0f8fc69ccd13 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 1 Jun 2019 23:17:54 -0700 Subject: [PATCH 05/11] setup tutorial initialization --- src/state/actions/index.ts | 11 +++++++++-- src/state/guards/index.ts | 6 +++++- src/state/machine.ts | 19 ++++++++----------- src/typings/index.d.ts | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index ae2f3bdd..1f7524f4 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -13,6 +13,8 @@ let initialProgress: CR.Progress = { export default { start: 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(), @@ -22,9 +24,11 @@ export default { initialTutorial = tutorial initialProgress = progress const canContinue = !!(tutorial && progress && hasGit && hasGitRemote) + // if a tutorial exists, "CONTINUE" + // otherwise start from "NEW" send(canContinue ? 'CONTINUE' : 'NEW') }, - loadTutorial: assign({ + tutorialLoad: assign({ // load initial data, progress & position data(): CR.TutorialData { if (!initialTutorial) { @@ -33,7 +37,9 @@ export default { return initialTutorial.data }, - progress(): CR.Progress { return initialProgress }, + progress(): CR.Progress { + return initialProgress + }, position() { if (!initialTutorial) { throw new Error('No Tutorial loaded') @@ -53,4 +59,5 @@ export default { return position } }), + } \ No newline at end of file diff --git a/src/state/guards/index.ts b/src/state/guards/index.ts index 6b59b251..2d84e38d 100644 --- a/src/state/guards/index.ts +++ b/src/state/guards/index.ts @@ -1,4 +1,8 @@ +import * as CR from 'typings' export default { - + // skip to the stage if the level has already been started + hasNoNextLevelProgress: (context: CR.MachineContext): boolean => { + return false + }, } diff --git a/src/state/machine.ts b/src/state/machine.ts index 77fbd621..e900433e 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -1,4 +1,4 @@ -import { Machine } from 'xstate' +import { Machine, send } from 'xstate' import * as CR from 'typings' import actions from './actions' @@ -43,28 +43,25 @@ export const tutorialMachine = Machine< }, ContinueTutorial: { - onEntry: 'loadTutorial', + onEntry: 'tutorialLoad', on: { TUTORIAL_START: { - target: 'Tutorial', + target: 'Tutorial.LoadNext', } } }, } }, Tutorial: { - initial: 'Initialize', + initial: 'Summary', states: { - Initialize: { + LoadNext: { + onEntry: () => send('LOAD_NEXT'), on: { - TUTORIAL_LOADED: [ - { - target: 'Summary', - cond: 'hasNoProgress', - }, + LOAD_NEXT: [ { target: 'Level', - cond: 'hasNoLevelProgress', + cond: 'hasNoNextLevelProgress', }, { target: 'Stage', diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index d2cc4dcb..5d7b751b 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -143,7 +143,7 @@ export interface MachineStateSchema { } Tutorial: { states: { - Initialize: {} + LoadNext: {} Summary: {} Level: {} Stage: { From b51dd2f6e6c93eb3de6df89fc4249c0bfde4ffa4 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 1 Jun 2019 23:31:52 -0700 Subject: [PATCH 06/11] refactor files --- src/commands/tutorialLoad.ts | 11 +------ src/{utils => services/api}/fetch.ts | 2 +- src/services/api/index.ts | 32 +++++++++++++++++++ src/services/fs.ts | 12 ------- src/services/{git.ts => git/index.ts} | 2 +- src/{utils/node.ts => services/node/index.ts} | 12 +++++++ src/{utils => services}/nonce.ts | 0 src/services/position.ts | 2 +- src/services/rootSetup.ts | 4 +-- src/services/testResult.ts | 2 +- src/services/tutorialSetup.ts | 4 +-- src/{utils => services/vscode}/channel.ts | 0 src/services/vscode/index.ts | 10 ++++++ src/services/vscode/save.ts | 13 ++++++++ src/services/{ => vscode}/storage.ts | 0 src/{utils => services/vscode}/workspace.ts | 2 +- 16 files changed, 77 insertions(+), 31 deletions(-) rename src/{utils => services/api}/fetch.ts (92%) create mode 100644 src/services/api/index.ts delete mode 100644 src/services/fs.ts rename src/services/{git.ts => git/index.ts} (98%) rename src/{utils/node.ts => services/node/index.ts} (79%) rename src/{utils => services}/nonce.ts (100%) rename src/{utils => services/vscode}/channel.ts (100%) create mode 100644 src/services/vscode/index.ts create mode 100644 src/services/vscode/save.ts rename src/services/{ => vscode}/storage.ts (100%) rename src/{utils => services/vscode}/workspace.ts (96%) diff --git a/src/commands/tutorialLoad.ts b/src/commands/tutorialLoad.ts index a7baddac..ddaea8c6 100644 --- a/src/commands/tutorialLoad.ts +++ b/src/commands/tutorialLoad.ts @@ -39,15 +39,6 @@ async function newTutorial(tutorial: CR.Tutorial) { await openReadme() } -function onSaveHook(languageIds: string[]) { - // trigger command on save - vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { - if (languageIds.includes(document.languageId) && document.uri.scheme === 'file') { - // do work - vscode.commands.executeCommand('coderoad.test_run') - } - }) -} async function validateCanContinue(): Promise { // validate tutorial & progress found in local storage @@ -127,7 +118,7 @@ export default async function tutorialLoad(context: vscode.ExtensionContext): Pr // } // // setup hook to run tests on save - // onSaveHook(tutorial.meta.languages) + // TODO: start } diff --git a/src/utils/fetch.ts b/src/services/api/fetch.ts similarity index 92% rename from src/utils/fetch.ts rename to src/services/api/fetch.ts index ade5e52e..206ae0eb 100644 --- a/src/utils/fetch.ts +++ b/src/services/api/fetch.ts @@ -1,7 +1,7 @@ import * as CR from 'typings' // temporary tutorials -import basicTutorial from '../state/context/tutorials/basic' +import basicTutorial from '../../state/context/tutorials/basic' interface Options { resource: string diff --git a/src/services/api/index.ts b/src/services/api/index.ts new file mode 100644 index 00000000..206ae0eb --- /dev/null +++ b/src/services/api/index.ts @@ -0,0 +1,32 @@ +import * as CR from 'typings' + +// temporary tutorials +import basicTutorial from '../../state/context/tutorials/basic' + +interface Options { + resource: string + params?: any +} + +const tutorialsData: { [key: string]: CR.Tutorial } = { + tutorialId: basicTutorial, +} + +// TODO: replace with fetch resource +export default async function fetch(options: Options): Promise { + console.log('options', options) + switch (options.resource) { + case 'getTutorialsSummary': + // list of ids with summaries + let data: { [id: string]: CR.TutorialSummary } = {} + for (const tutorial of Object.values(tutorialsData)) { + data[tutorial.id] = tutorial.data.summary + } + return data + case 'getTutorial': + // specific tutorial by id + return tutorialsData[options.params.id] + default: + throw new Error('Resource not found') + } +} diff --git a/src/services/fs.ts b/src/services/fs.ts deleted file mode 100644 index 5bd52296..00000000 --- a/src/services/fs.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { exec } from '../utils/node' - -export async function clear(): Promise { - // remove all files including ignored - // NOTE: Linux only - const command = 'ls -A1 | xargs rm -rf' - const { stderr } = await exec(command) - if (stderr) { - console.error(stderr) - throw new Error('Error removing all files & folders') - } -} diff --git a/src/services/git.ts b/src/services/git/index.ts similarity index 98% rename from src/services/git.ts rename to src/services/git/index.ts index f6cb33fc..347ef142 100644 --- a/src/services/git.ts +++ b/src/services/git/index.ts @@ -1,5 +1,5 @@ import * as CR from 'typings' -import { exec, exists, openFile } from '../utils/node' +import { exec, exists, openFile } from '../node' const gitOrigin = 'coderoad' diff --git a/src/utils/node.ts b/src/services/node/index.ts similarity index 79% rename from src/utils/node.ts rename to src/services/node/index.ts index 8d5c4c46..0a5234d9 100644 --- a/src/utils/node.ts +++ b/src/services/node/index.ts @@ -37,3 +37,15 @@ export const openFile = async (relativeFilePath: string): Promise => { console.log(`Failed to open file ${relativeFilePath}`, error) } } + + +// export async function clear(): Promise { +// // remove all files including ignored +// // NOTE: Linux only +// const command = 'ls -A1 | xargs rm -rf' +// const { stderr } = await exec(command) +// if (stderr) { +// console.error(stderr) +// throw new Error('Error removing all files & folders') +// } +// } \ No newline at end of file diff --git a/src/utils/nonce.ts b/src/services/nonce.ts similarity index 100% rename from src/utils/nonce.ts rename to src/services/nonce.ts diff --git a/src/services/position.ts b/src/services/position.ts index 6f0e4312..2bc84534 100644 --- a/src/services/position.ts +++ b/src/services/position.ts @@ -1,5 +1,5 @@ import * as CR from 'typings' -import * as storage from './storage' +import * as storage from './vscode/storage' export async function getInitial(tutorial: CR.Tutorial): Promise { const { data } = tutorial diff --git a/src/services/rootSetup.ts b/src/services/rootSetup.ts index 04c372a3..cf1a3346 100644 --- a/src/services/rootSetup.ts +++ b/src/services/rootSetup.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode' -import { setWorkspaceRoot } from '../utils/node' -import { setStorage } from './storage' +import { setWorkspaceRoot } from '../services/node' +import { setStorage } from './vscode/storage' export default async function setupRoot(context: vscode.ExtensionContext) { await setWorkspaceRoot() diff --git a/src/services/testResult.ts b/src/services/testResult.ts index b2e3fb0f..3d3cf7c5 100644 --- a/src/services/testResult.ts +++ b/src/services/testResult.ts @@ -1,6 +1,6 @@ import * as CR from 'typings' import * as vscode from 'vscode' -import * as storage from './storage' +import * as storage from './vscode/storage' export async function onSuccess(position: CR.Position) { diff --git a/src/services/tutorialSetup.ts b/src/services/tutorialSetup.ts index ea9b10ee..b07ce434 100644 --- a/src/services/tutorialSetup.ts +++ b/src/services/tutorialSetup.ts @@ -1,7 +1,7 @@ import * as CR from 'typings' import * as position from '../services/position' -import * as storage from '../services/storage' -import { isEmptyWorkspace } from '../utils/workspace' +import * as storage from '../services/vscode/storage' +import { isEmptyWorkspace } from '../services/vscode/workspace' import { gitLoadCommits, gitInitIfNotExists, gitSetupRemote } from '../services/git' const testRepo = 'https://github.com/ShMcK/coderoad-tutorial-basic.git' diff --git a/src/utils/channel.ts b/src/services/vscode/channel.ts similarity index 100% rename from src/utils/channel.ts rename to src/services/vscode/channel.ts diff --git a/src/services/vscode/index.ts b/src/services/vscode/index.ts new file mode 100644 index 00000000..49d6cfa3 --- /dev/null +++ b/src/services/vscode/index.ts @@ -0,0 +1,10 @@ +import * as CR from 'typings' +import onSaveHook from './save' + +const vscodeAction = (action: CR.Action) => { + switch (action.type) { + case 'ON_FILE_SAVE_RUN_TEST': { + onSaveHook(action.payload.languages) + } + } +} \ No newline at end of file diff --git a/src/services/vscode/save.ts b/src/services/vscode/save.ts new file mode 100644 index 00000000..5aee7a5f --- /dev/null +++ b/src/services/vscode/save.ts @@ -0,0 +1,13 @@ +import * as vscode from 'vscode' + +function onSaveHook(languageIds: string[]) { + // trigger command on save + vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { + if (languageIds.includes(document.languageId) && document.uri.scheme === 'file') { + // do work + vscode.commands.executeCommand('coderoad.test_run') + } + }) +} + +export default onSaveHook \ No newline at end of file diff --git a/src/services/storage.ts b/src/services/vscode/storage.ts similarity index 100% rename from src/services/storage.ts rename to src/services/vscode/storage.ts diff --git a/src/utils/workspace.ts b/src/services/vscode/workspace.ts similarity index 96% rename from src/utils/workspace.ts rename to src/services/vscode/workspace.ts index 599d19a9..8d94fc98 100644 --- a/src/utils/workspace.ts +++ b/src/services/vscode/workspace.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import * as vscode from 'vscode' -import { exec, exists } from './node' +import { exec, exists } from '../node' export async function isEmptyWorkspace(): Promise { const { stdout, stderr } = await exec('ls') From 89045a36936d008763ff5b939c784741935848fc Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 2 Jun 2019 00:01:00 -0700 Subject: [PATCH 07/11] refactor files --- src/{services/vscode => editor}/channel.ts | 0 src/{ => editor}/commands/index.ts | 0 src/{ => editor}/commands/loadSolution.ts | 4 ++-- src/{ => editor}/commands/quit.ts | 0 src/{ => editor}/commands/runTest.ts | 8 ++++---- src/{ => editor}/commands/tutorialLoad.ts | 16 ++++++++-------- src/{services/vscode => editor}/index.ts | 0 src/{services/vscode => editor}/save.ts | 0 src/{services/vscode => editor}/storage.ts | 0 src/{services/vscode => editor}/workspace.ts | 2 +- src/extension.ts | 2 +- src/services/position.ts | 2 +- src/services/rootSetup.ts | 2 +- src/services/testResult.ts | 2 +- src/services/tutorialSetup.ts | 4 ++-- src/state/actions/index.ts | 2 +- 16 files changed, 22 insertions(+), 22 deletions(-) rename src/{services/vscode => editor}/channel.ts (100%) rename src/{ => editor}/commands/index.ts (100%) rename src/{ => editor}/commands/loadSolution.ts (83%) rename src/{ => editor}/commands/quit.ts (100%) rename src/{ => editor}/commands/runTest.ts (94%) rename src/{ => editor}/commands/tutorialLoad.ts (89%) rename src/{services/vscode => editor}/index.ts (100%) rename src/{services/vscode => editor}/save.ts (100%) rename src/{services/vscode => editor}/storage.ts (100%) rename src/{services/vscode => editor}/workspace.ts (96%) diff --git a/src/services/vscode/channel.ts b/src/editor/channel.ts similarity index 100% rename from src/services/vscode/channel.ts rename to src/editor/channel.ts diff --git a/src/commands/index.ts b/src/editor/commands/index.ts similarity index 100% rename from src/commands/index.ts rename to src/editor/commands/index.ts diff --git a/src/commands/loadSolution.ts b/src/editor/commands/loadSolution.ts similarity index 83% rename from src/commands/loadSolution.ts rename to src/editor/commands/loadSolution.ts index 442f83b7..f7df360e 100644 --- a/src/commands/loadSolution.ts +++ b/src/editor/commands/loadSolution.ts @@ -1,6 +1,6 @@ import * as CR from 'typings' -import * as storage from '../services/storage' -import { gitLoadCommits, gitClear } from '../services/git' +import * as storage from '../storage' +import { gitLoadCommits, gitClear } from '../../services/git' export default async function loadSolution(): Promise { const [position, tutorial]: [CR.Position, CR.Tutorial | undefined] = await Promise.all([ diff --git a/src/commands/quit.ts b/src/editor/commands/quit.ts similarity index 100% rename from src/commands/quit.ts rename to src/editor/commands/quit.ts diff --git a/src/commands/runTest.ts b/src/editor/commands/runTest.ts similarity index 94% rename from src/commands/runTest.ts rename to src/editor/commands/runTest.ts index a3bd40f8..c7d2f7e6 100644 --- a/src/commands/runTest.ts +++ b/src/editor/commands/runTest.ts @@ -1,7 +1,7 @@ -import { getOutputChannel } from '../utils/channel' -import { exec } from '../utils/node' -import * as storage from '../services/storage' -import * as testResult from '../services/testResult' +import { getOutputChannel } from '../channel' +import { exec } from '../../services/node' +import * as storage from '../storage' +import * as testResult from '../../services/testResult' // ensure only latest run_test action is taken let currentId = 0 diff --git a/src/commands/tutorialLoad.ts b/src/editor/commands/tutorialLoad.ts similarity index 89% rename from src/commands/tutorialLoad.ts rename to src/editor/commands/tutorialLoad.ts index ddaea8c6..8a20e79a 100644 --- a/src/commands/tutorialLoad.ts +++ b/src/editor/commands/tutorialLoad.ts @@ -1,13 +1,13 @@ import * as vscode from 'vscode' import * as CR from 'typings' -import fetch from '../utils/fetch' -import tutorialSetup from '../services/tutorialSetup' -import { loadProgressPosition } from '../services/position' -import * as storage from '../services/storage' -import rootSetup from '../services/rootSetup' -import { isEmptyWorkspace, openReadme } from '../utils/workspace' -import * as git from '../services/git' +import api from '../../services/api' +import tutorialSetup from '../../services/tutorialSetup' +import { loadProgressPosition } from '../../services/position' +import * as storage from '../storage' +import rootSetup from '../../services/rootSetup' +import { isEmptyWorkspace, openReadme } from '../workspace' +import * as git from '../../services/git' /* new @@ -78,7 +78,7 @@ export default async function tutorialLoad(context: vscode.ExtensionContext): Pr // } // // load tutorial summaries - // const tutorialsData: { [id: string]: CR.TutorialSummary } = await fetch({ + // const tutorialsData: { [id: string]: CR.TutorialSummary } = await api({ // resource: 'getTutorialsSummary', // }) // const selectableTutorials: TutorialQuickPickItem[] = Object.keys(tutorialsData).map(id => { diff --git a/src/services/vscode/index.ts b/src/editor/index.ts similarity index 100% rename from src/services/vscode/index.ts rename to src/editor/index.ts diff --git a/src/services/vscode/save.ts b/src/editor/save.ts similarity index 100% rename from src/services/vscode/save.ts rename to src/editor/save.ts diff --git a/src/services/vscode/storage.ts b/src/editor/storage.ts similarity index 100% rename from src/services/vscode/storage.ts rename to src/editor/storage.ts diff --git a/src/services/vscode/workspace.ts b/src/editor/workspace.ts similarity index 96% rename from src/services/vscode/workspace.ts rename to src/editor/workspace.ts index 8d94fc98..ed1d456c 100644 --- a/src/services/vscode/workspace.ts +++ b/src/editor/workspace.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import * as vscode from 'vscode' -import { exec, exists } from '../node' +import { exec, exists } from '../services/node' export async function isEmptyWorkspace(): Promise { const { stdout, stderr } = await exec('ls') diff --git a/src/extension.ts b/src/extension.ts index 2ef8c00a..d70584bc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,7 +2,7 @@ // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode' -import createCommands from './commands' +import createCommands from './editor/commands' import createViews from './views' // this method is called when your extension is activated diff --git a/src/services/position.ts b/src/services/position.ts index 2bc84534..4d057484 100644 --- a/src/services/position.ts +++ b/src/services/position.ts @@ -1,5 +1,5 @@ import * as CR from 'typings' -import * as storage from './vscode/storage' +import * as storage from '../editor/storage' export async function getInitial(tutorial: CR.Tutorial): Promise { const { data } = tutorial diff --git a/src/services/rootSetup.ts b/src/services/rootSetup.ts index cf1a3346..daeac9b7 100644 --- a/src/services/rootSetup.ts +++ b/src/services/rootSetup.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode' import { setWorkspaceRoot } from '../services/node' -import { setStorage } from './vscode/storage' +import { setStorage } from '../editor/storage' export default async function setupRoot(context: vscode.ExtensionContext) { await setWorkspaceRoot() diff --git a/src/services/testResult.ts b/src/services/testResult.ts index 3d3cf7c5..a88fe899 100644 --- a/src/services/testResult.ts +++ b/src/services/testResult.ts @@ -1,6 +1,6 @@ import * as CR from 'typings' import * as vscode from 'vscode' -import * as storage from './vscode/storage' +import * as storage from '../editor/storage' export async function onSuccess(position: CR.Position) { diff --git a/src/services/tutorialSetup.ts b/src/services/tutorialSetup.ts index b07ce434..90672c55 100644 --- a/src/services/tutorialSetup.ts +++ b/src/services/tutorialSetup.ts @@ -1,7 +1,7 @@ import * as CR from 'typings' import * as position from '../services/position' -import * as storage from '../services/vscode/storage' -import { isEmptyWorkspace } from '../services/vscode/workspace' +import * as storage from '../editor/storage' +import { isEmptyWorkspace } from '../editor/workspace' import { gitLoadCommits, gitInitIfNotExists, gitSetupRemote } from '../services/git' const testRepo = 'https://github.com/ShMcK/coderoad-tutorial-basic.git' diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index 1f7524f4..c694b37e 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -1,6 +1,6 @@ import { assign, send } from 'xstate' import * as CR from 'typings' -import * as storage from '../../services/storage' +import * as storage from '../../editor/storage' import * as git from '../../services/git' let initialTutorial: CR.Tutorial | undefined From 9bcf64cc75b38a7c46df17f18d1d44fd5fd6afb9 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 2 Jun 2019 00:12:20 -0700 Subject: [PATCH 08/11] refactor storage into editor --- src/editor/commands/loadSolution.ts | 2 +- src/editor/commands/runTest.ts | 2 +- src/editor/commands/tutorialLoad.ts | 2 +- src/editor/storage.ts | 74 ++------------------------- src/services/position.ts | 2 +- src/services/storage.ts | 77 +++++++++++++++++++++++++++++ src/services/testResult.ts | 2 +- src/services/tutorialSetup.ts | 2 +- src/state/actions/index.ts | 2 +- 9 files changed, 88 insertions(+), 77 deletions(-) create mode 100644 src/services/storage.ts diff --git a/src/editor/commands/loadSolution.ts b/src/editor/commands/loadSolution.ts index f7df360e..22d41257 100644 --- a/src/editor/commands/loadSolution.ts +++ b/src/editor/commands/loadSolution.ts @@ -1,5 +1,5 @@ import * as CR from 'typings' -import * as storage from '../storage' +import * as storage from '../../services/storage' import { gitLoadCommits, gitClear } from '../../services/git' export default async function loadSolution(): Promise { diff --git a/src/editor/commands/runTest.ts b/src/editor/commands/runTest.ts index c7d2f7e6..6e959433 100644 --- a/src/editor/commands/runTest.ts +++ b/src/editor/commands/runTest.ts @@ -1,6 +1,6 @@ import { getOutputChannel } from '../channel' import { exec } from '../../services/node' -import * as storage from '../storage' +import * as storage from '../../services/storage' import * as testResult from '../../services/testResult' // ensure only latest run_test action is taken diff --git a/src/editor/commands/tutorialLoad.ts b/src/editor/commands/tutorialLoad.ts index 8a20e79a..f79ab488 100644 --- a/src/editor/commands/tutorialLoad.ts +++ b/src/editor/commands/tutorialLoad.ts @@ -4,7 +4,7 @@ import * as CR from 'typings' import api from '../../services/api' import tutorialSetup from '../../services/tutorialSetup' import { loadProgressPosition } from '../../services/position' -import * as storage from '../storage' +import * as storage from '../../services/storage' import rootSetup from '../../services/rootSetup' import { isEmptyWorkspace, openReadme } from '../workspace' import * as git from '../../services/git' diff --git a/src/editor/storage.ts b/src/editor/storage.ts index b108edbd..a7d9f848 100644 --- a/src/editor/storage.ts +++ b/src/editor/storage.ts @@ -8,77 +8,11 @@ export function setStorage(workspaceState: vscode.Memento): void { storage = workspaceState } -// TUTORIAL -const STORE_TUTORIAL = 'coderoad:tutorial' - -export async function getTutorial(): Promise { - return storage.get(STORE_TUTORIAL) -} - -export async function setTutorial(tutorial: CR.Tutorial): Promise { - await storage.update(STORE_TUTORIAL, tutorial) +export function get(key: string): T | undefined { + return storage.get(key) } -// POSITION -const STORE_POSITION = 'coderoad:position' - -const defaultPosition = { levelId: '', stageId: '', stepId: '' } - -export async function getPosition(): Promise { - const position: CR.Position | undefined = storage.get(STORE_POSITION) - return position || defaultPosition +export function update(key: string, value: string | Object): Thenable { + return storage.update(key, value) } -export async function setPosition(position: CR.Position): Promise { - await storage.update(STORE_POSITION, position) -} - -// PROGRESS -const STORE_PROGRESS = 'coderoad:progress' - -const defaultProgress = { levels: {}, stages: {}, steps: {}, hints: {}, complete: false } - -export async function getProgress(): Promise { - const progress: CR.Progress | undefined = await storage.get(STORE_PROGRESS) - return progress || defaultProgress -} - -export async function resetProgress(): Promise { - await storage.update(STORE_PROGRESS, defaultProgress) -} - -interface ProgressUpdate { - levels?: { - [levelId: string]: boolean - } - stages?: { - [stageid: string]: boolean - } - steps?: { - [stepId: string]: boolean - } -} - -export async function updateProgress(record: ProgressUpdate): Promise { - const progress = await getProgress() - if (record.levels) { - progress.levels = { - ...progress.levels, - ...record.levels, - } - } - if (record.stages) { - progress.stages = { - ...progress.stages, - ...record.stages, - } - } - if (record.steps) { - progress.steps = { - ...progress.steps, - ...record.steps, - } - } - - await storage.update(STORE_PROGRESS, progress) -} diff --git a/src/services/position.ts b/src/services/position.ts index 4d057484..6f0e4312 100644 --- a/src/services/position.ts +++ b/src/services/position.ts @@ -1,5 +1,5 @@ import * as CR from 'typings' -import * as storage from '../editor/storage' +import * as storage from './storage' export async function getInitial(tutorial: CR.Tutorial): Promise { const { data } = tutorial diff --git a/src/services/storage.ts b/src/services/storage.ts new file mode 100644 index 00000000..397833bf --- /dev/null +++ b/src/services/storage.ts @@ -0,0 +1,77 @@ +import * as CR from 'typings' +import * as storage from '../editor/storage' + +// TUTORIAL +const STORE_TUTORIAL = 'coderoad:tutorial' + +export async function getTutorial(): Promise { + return storage.get(STORE_TUTORIAL) +} + +export async function setTutorial(tutorial: CR.Tutorial): Promise { + await storage.update(STORE_TUTORIAL, tutorial) +} + +// POSITION +const STORE_POSITION = 'coderoad:position' + +const defaultPosition = { levelId: '', stageId: '', stepId: '' } + +export async function getPosition(): Promise { + const position: CR.Position | undefined = storage.get(STORE_POSITION) + return position || defaultPosition +} + +export async function setPosition(position: CR.Position): Promise { + await storage.update(STORE_POSITION, position) +} + +// PROGRESS +const STORE_PROGRESS = 'coderoad:progress' + +const defaultProgress = { levels: {}, stages: {}, steps: {}, hints: {}, complete: false } + +export async function getProgress(): Promise { + const progress: CR.Progress | undefined = await storage.get(STORE_PROGRESS) + return progress || defaultProgress +} + +export async function resetProgress(): Promise { + await storage.update(STORE_PROGRESS, defaultProgress) +} + +interface ProgressUpdate { + levels?: { + [levelId: string]: boolean + } + stages?: { + [stageid: string]: boolean + } + steps?: { + [stepId: string]: boolean + } +} + +export async function updateProgress(record: ProgressUpdate): Promise { + const progress = await getProgress() + if (record.levels) { + progress.levels = { + ...progress.levels, + ...record.levels, + } + } + if (record.stages) { + progress.stages = { + ...progress.stages, + ...record.stages, + } + } + if (record.steps) { + progress.steps = { + ...progress.steps, + ...record.steps, + } + } + + await storage.update(STORE_PROGRESS, progress) +} diff --git a/src/services/testResult.ts b/src/services/testResult.ts index a88fe899..b2e3fb0f 100644 --- a/src/services/testResult.ts +++ b/src/services/testResult.ts @@ -1,6 +1,6 @@ import * as CR from 'typings' import * as vscode from 'vscode' -import * as storage from '../editor/storage' +import * as storage from './storage' export async function onSuccess(position: CR.Position) { diff --git a/src/services/tutorialSetup.ts b/src/services/tutorialSetup.ts index 90672c55..140604d7 100644 --- a/src/services/tutorialSetup.ts +++ b/src/services/tutorialSetup.ts @@ -1,6 +1,6 @@ import * as CR from 'typings' import * as position from '../services/position' -import * as storage from '../editor/storage' +import * as storage from '../services/storage' import { isEmptyWorkspace } from '../editor/workspace' import { gitLoadCommits, gitInitIfNotExists, gitSetupRemote } from '../services/git' diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index c694b37e..1f7524f4 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -1,6 +1,6 @@ import { assign, send } from 'xstate' import * as CR from 'typings' -import * as storage from '../../editor/storage' +import * as storage from '../../services/storage' import * as git from '../../services/git' let initialTutorial: CR.Tutorial | undefined From cc176e616b8efcf149c494fe3449f3e6fdfe69b8 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 2 Jun 2019 09:31:21 -0700 Subject: [PATCH 09/11] update deps --- package-lock.json | 46 ++++++++++++++++++++++++---------------------- package.json | 8 ++++---- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa84afab..581dde5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,15 +25,15 @@ } }, "@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", "dev": true }, "@types/node": { - "version": "10.14.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.7.tgz", - "integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==", + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", + "integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==", "dev": true }, "agent-base": { @@ -166,6 +166,17 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "color-convert": { @@ -767,15 +778,6 @@ "tweetnacl": "~0.14.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -801,9 +803,9 @@ "dev": true }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -812,7 +814,7 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -852,9 +854,9 @@ "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index cdae712b..e2597cd6 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "test": "npm run compile && node ./node_modules/vscode/bin/test" }, "devDependencies": { - "@types/mocha": "^2.2.42", - "@types/node": "^10.12.21", + "@types/mocha": "^5.2.7", + "@types/node": "^12.0.4", "prettier": "^1.17.1", - "tslint": "^5.12.1", + "tslint": "^5.17.0", "tslint-config-prettier": "^1.18.0", - "typescript": "^3.3.1", + "typescript": "^3.5.1", "vscode": "^1.1.28" }, "dependencies": { From e88651e2f5ae5cd742dd05e7f84d8a632da8abf6 Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 2 Jun 2019 13:26:58 -0700 Subject: [PATCH 10/11] setup xstate with interpreter --- .vscode/settings.json | 1 - package.json | 7 +-- src/editor/commands/index.ts | 9 ++-- .../commands/{tutorialLoad.ts => start.ts} | 44 +++++++------------ src/editor/index.ts | 10 ----- src/editor/init.ts | 33 ++++++++++++++ src/{services => editor/utils}/nonce.ts | 0 src/{ => editor}/views/index.ts | 0 src/extension.ts | 30 +------------ src/services/rootSetup.ts | 8 ---- src/state/actions/index.ts | 4 ++ src/state/guards/index.ts | 1 + src/state/index.ts | 28 ++++++++++++ src/state/machine.ts | 7 +-- 14 files changed, 97 insertions(+), 85 deletions(-) rename src/editor/commands/{tutorialLoad.ts => start.ts} (71%) delete mode 100644 src/editor/index.ts create mode 100644 src/editor/init.ts rename src/{services => editor/utils}/nonce.ts (100%) rename src/{ => editor}/views/index.ts (100%) delete mode 100644 src/services/rootSetup.ts create mode 100644 src/state/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 53cf2003..a0dca7e4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,6 @@ "source.fixAll": true, }, "tslint.enable": true, - "tslint.jsEnable": true, "[javascript]": { "editor.formatOnSave": true }, diff --git a/package.json b/package.json index e2597cd6..4cd38b8b 100644 --- a/package.json +++ b/package.json @@ -11,20 +11,21 @@ ], "publisher": "Shawn McKay ", "activationEvents": [ - "onCommand:coderoad.tutorial_load" + "onCommand:coderoad.start" ], "main": "./out/extension.js", "contributes": { "commands": [ { - "command": "coderoad.tutorial_load", - "title": "Load Tutorial", + "command": "coderoad.start", + "title": "Start", "category": "CodeRoad" } ] }, "scripts": { "vscode:prepublish": "npm run compile", + "machine": "node ./out/state/index.js", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index e90a93d1..638cee32 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -1,13 +1,13 @@ import * as vscode from 'vscode' // import runTest from './runTest' -import tutorialLoad from './tutorialLoad' +import start from './start' // import loadSolution from './loadSolution' // import quit from './quit' const COMMANDS = { // TUTORIAL_SETUP: 'coderoad.tutorial_setup', - TUTORIAL_LOAD: 'coderoad.tutorial_load', + START: 'coderoad.start', // RUN_TEST: 'coderoad.test_run', // LOAD_SOLUTION: 'coderoad.solution_load', // QUIT: 'coderoad.quit', @@ -15,8 +15,9 @@ const COMMANDS = { export default (context: vscode.ExtensionContext): void => { const commands = { - [COMMANDS.TUTORIAL_LOAD](): void { - tutorialLoad(context) + [COMMANDS.START](): void { + console.log('TUTORIAL_START') + start(context) }, // [COMMANDS.RUN_TEST]: runTest, // [COMMANDS.LOAD_SOLUTION]: loadSolution, diff --git a/src/editor/commands/tutorialLoad.ts b/src/editor/commands/start.ts similarity index 71% rename from src/editor/commands/tutorialLoad.ts rename to src/editor/commands/start.ts index f79ab488..4393f340 100644 --- a/src/editor/commands/tutorialLoad.ts +++ b/src/editor/commands/start.ts @@ -1,13 +1,11 @@ import * as vscode from 'vscode' import * as CR from 'typings' -import api from '../../services/api' import tutorialSetup from '../../services/tutorialSetup' -import { loadProgressPosition } from '../../services/position' -import * as storage from '../../services/storage' -import rootSetup from '../../services/rootSetup' import { isEmptyWorkspace, openReadme } from '../workspace' -import * as git from '../../services/git' +import { setWorkspaceRoot } from '../../services/node' +import { setStorage } from '../../editor/storage' +import createStateMachine from '../../state' /* new @@ -15,13 +13,13 @@ if current workspace is empty, use it if not, open a new folder then start */ -async function continueTutorial() { - // TODO: verify that tutorial is loaded in workspace - // TODO: verify progress - // TODO: verify setup - await loadProgressPosition() - await openReadme() -} +// async function continueTutorial() { +// // TODO: verify that tutorial is loaded in workspace +// // TODO: verify progress +// // TODO: verify setup +// await loadProgressPosition() +// await openReadme() +// } async function newTutorial(tutorial: CR.Tutorial) { // if workspace isn't empty, clear it out if given permission @@ -40,23 +38,15 @@ async function newTutorial(tutorial: CR.Tutorial) { } -async function validateCanContinue(): Promise { - // validate tutorial & progress found in local storage - // validate git is setup with a remote - const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([ - storage.getTutorial(), - storage.getProgress(), - git.gitVersion(), - git.gitCheckRemoteExists(), - ]) - return !!(tutorial && progress && hasGit && hasGitRemote) -} - -export default async function tutorialLoad(context: vscode.ExtensionContext): Promise { - console.log(`tutorialLoad ${JSON.stringify(context)}`) +export default async function start(context: vscode.ExtensionContext): Promise { + console.log('start', context) // setup connection to workspace - await rootSetup(context) + await setWorkspaceRoot() + // set workspace context path + await setStorage(context.workspaceState) + // initiate the state machine + createStateMachine() return; // const modes = ['New'] diff --git a/src/editor/index.ts b/src/editor/index.ts deleted file mode 100644 index 49d6cfa3..00000000 --- a/src/editor/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as CR from 'typings' -import onSaveHook from './save' - -const vscodeAction = (action: CR.Action) => { - switch (action.type) { - case 'ON_FILE_SAVE_RUN_TEST': { - onSaveHook(action.payload.languages) - } - } -} \ No newline at end of file diff --git a/src/editor/init.ts b/src/editor/init.ts new file mode 100644 index 00000000..98f57d58 --- /dev/null +++ b/src/editor/init.ts @@ -0,0 +1,33 @@ +// 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 createCommands from './commands' +import createViews from './views' +import createStateMachine from '../state' + +// 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) + + // tasks + // add tasks here + + // views + createViews(context) + + +} + +// 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() + } +} diff --git a/src/services/nonce.ts b/src/editor/utils/nonce.ts similarity index 100% rename from src/services/nonce.ts rename to src/editor/utils/nonce.ts diff --git a/src/views/index.ts b/src/editor/views/index.ts similarity index 100% rename from src/views/index.ts rename to src/editor/views/index.ts diff --git a/src/extension.ts b/src/extension.ts index d70584bc..40646445 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,30 +1,2 @@ -// 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' +export { activate, deactivate } from './editor/init' -import createCommands from './editor/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) - - // tasks - // add tasks here - - // views - createViews(context) -} - -// 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() - } -} diff --git a/src/services/rootSetup.ts b/src/services/rootSetup.ts deleted file mode 100644 index daeac9b7..00000000 --- a/src/services/rootSetup.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as vscode from 'vscode' -import { setWorkspaceRoot } from '../services/node' -import { setStorage } from '../editor/storage' - -export default async function setupRoot(context: vscode.ExtensionContext) { - await setWorkspaceRoot() - await setStorage(context.workspaceState) -} diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index 1f7524f4..e9d74caa 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -13,6 +13,7 @@ let initialProgress: CR.Progress = { export default { start: async () => { + 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([ @@ -31,6 +32,7 @@ export default { tutorialLoad: assign({ // load initial data, progress & position data(): CR.TutorialData { + console.log('ACTION: tutorialLoad.data') if (!initialTutorial) { throw new Error('No Tutorial loaded') } @@ -38,9 +40,11 @@ export default { }, progress(): CR.Progress { + console.log('ACTION: tutorialLoad.progress') return initialProgress }, position() { + console.log('ACTION: tutorialLoad.position') if (!initialTutorial) { throw new Error('No Tutorial loaded') } diff --git a/src/state/guards/index.ts b/src/state/guards/index.ts index 2d84e38d..563fac43 100644 --- a/src/state/guards/index.ts +++ b/src/state/guards/index.ts @@ -3,6 +3,7 @@ import * as CR from 'typings' export default { // skip to the stage if the level has already been started hasNoNextLevelProgress: (context: CR.MachineContext): boolean => { + console.log('GUARD: hasNoNextLevelProgress') return false }, } diff --git a/src/state/index.ts b/src/state/index.ts new file mode 100644 index 00000000..e1e1be6e --- /dev/null +++ b/src/state/index.ts @@ -0,0 +1,28 @@ +import { interpret } from 'xstate' +import machine from './machine' + + +const createStateMachine = () => { + 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) + } + }) + // initialize + service.start() + return service +} + +export default createStateMachine diff --git a/src/state/machine.ts b/src/state/machine.ts index e900433e..b82e1f20 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -5,9 +5,7 @@ import actions from './actions' import guards from './guards' import initialContext from './context' -// TODO: replace with API - -export const tutorialMachine = Machine< +export const machine = Machine< CR.MachineContext, CR.MachineStateSchema, CR.MachineEvent @@ -18,6 +16,7 @@ export const tutorialMachine = Machine< initial: 'Start', states: { Start: { + initial: 'Initial', states: { Initial: { onEntry: 'start', @@ -157,3 +156,5 @@ export const tutorialMachine = Machine< activities: {}, }, ) + +export default machine \ No newline at end of file From f8c527e198f84dca34b64de5c6fdd2302bd82e3d Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 2 Jun 2019 14:38:07 -0700 Subject: [PATCH 11/11] cleanup start --- src/editor/commands/index.ts | 8 +- src/editor/commands/start-old.ts | 108 +++++++++++++++++++++++ src/editor/commands/start.ts | 145 ++++++++----------------------- src/editor/init.ts | 10 +-- src/state/actions/index.ts | 19 +--- src/state/index.ts | 43 ++++----- src/state/machine.ts | 5 +- src/typings/index.d.ts | 2 +- 8 files changed, 182 insertions(+), 158 deletions(-) create mode 100644 src/editor/commands/start-old.ts diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts index 638cee32..d923f11a 100644 --- a/src/editor/commands/index.ts +++ b/src/editor/commands/index.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode' +import start from './start' // import runTest from './runTest' -import start from './start' // import loadSolution from './loadSolution' // import quit from './quit' @@ -13,11 +13,11 @@ const COMMANDS = { // QUIT: 'coderoad.quit', } + export default (context: vscode.ExtensionContext): void => { const commands = { - [COMMANDS.START](): void { - console.log('TUTORIAL_START') - start(context) + [COMMANDS.START]: async function startCommand(): Promise { + return start(context) }, // [COMMANDS.RUN_TEST]: runTest, // [COMMANDS.LOAD_SOLUTION]: loadSolution, diff --git a/src/editor/commands/start-old.ts b/src/editor/commands/start-old.ts new file mode 100644 index 00000000..5d172fbc --- /dev/null +++ b/src/editor/commands/start-old.ts @@ -0,0 +1,108 @@ +import * as vscode from 'vscode' +import * as CR from 'typings' + +import tutorialSetup from '../../services/tutorialSetup' +import { isEmptyWorkspace, openReadme } from '../workspace' +import { setWorkspaceRoot } from '../../services/node' +import { setStorage } from '../../editor/storage' + +/* +new +if current workspace is empty, use it +if not, open a new folder then start +*/ + +// async function continueTutorial() { +// // TODO: verify that tutorial is loaded in workspace +// // TODO: verify progress +// // TODO: verify setup +// await loadProgressPosition() +// await openReadme() +// } + +async function newTutorial(tutorial: CR.Tutorial) { + // if workspace isn't empty, clear it out if given permission + const isEmpty: boolean = await isEmptyWorkspace() + if (!isEmpty) { + // eslint-disable-next-line + const options = ['Open a new folder', 'I\'ll clear the files and folders myself'] + const shouldOpenFolder = await vscode.window.showQuickPick(options) + if (shouldOpenFolder === options[0]) { + await vscode.commands.executeCommand('vscode.openFolder') + } + } + + await tutorialSetup(tutorial) + await openReadme() +} + + +export default async function start(context: vscode.ExtensionContext): Promise { + console.log('start', context) + + + return; + + // const modes = ['New'] + + // const canContinue = await validateCanContinue() + // if (canContinue) { + // modes.push('Continue') + // } + + // const selectedMode: string | undefined = await vscode.window.showQuickPick(modes) + + // if (!selectedMode) { + // throw new Error('No mode selected') + // return + // } + + // interface TutorialQuickPickItem extends vscode.QuickPickItem { + // id: string + // } + + // // load tutorial summaries + // const tutorialsData: { [id: string]: CR.TutorialSummary } = await api({ + // resource: 'getTutorialsSummary', + // }) + // const selectableTutorials: TutorialQuickPickItem[] = Object.keys(tutorialsData).map(id => { + // const tutorial = tutorialsData[id] + // return { + // label: tutorial.title, + // description: tutorial.description, + // // detail: '', // optional additional info + // id, + // } + // }) + // const selectedTutorial: TutorialQuickPickItem | undefined = await vscode.window.showQuickPick(selectableTutorials) + + // if (!selectedTutorial) { + // throw new Error('No tutorial selected') + // } + + // // load specific tutorial + // const tutorial: CR.Tutorial | undefined = await fetch({ + // resource: 'getTutorial', + // params: { id: selectedTutorial.id }, + // }) + + // if (!tutorial) { + // throw new Error('No tutorial found') + // } + + // switch (selectedMode) { + // // new tutorial + // case modes[0]: + // await newTutorial(tutorial) + // break + // // continue + // case modes[1]: + // await continueTutorial() + // break + // } + + // // setup hook to run tests on save + + + // TODO: start +} diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts index 4393f340..84c4a77a 100644 --- a/src/editor/commands/start.ts +++ b/src/editor/commands/start.ts @@ -1,114 +1,45 @@ import * as vscode from 'vscode' -import * as CR from 'typings' - -import tutorialSetup from '../../services/tutorialSetup' -import { isEmptyWorkspace, openReadme } from '../workspace' import { setWorkspaceRoot } from '../../services/node' import { setStorage } from '../../editor/storage' -import createStateMachine from '../../state' - -/* -new -if current workspace is empty, use it -if not, open a new folder then start -*/ - -// async function continueTutorial() { -// // TODO: verify that tutorial is loaded in workspace -// // TODO: verify progress -// // TODO: verify setup -// await loadProgressPosition() -// await openReadme() -// } - -async function newTutorial(tutorial: CR.Tutorial) { - // if workspace isn't empty, clear it out if given permission - const isEmpty: boolean = await isEmptyWorkspace() - if (!isEmpty) { - // eslint-disable-next-line - const options = ['Open a new folder', 'I\'ll clear the files and folders myself'] - const shouldOpenFolder = await vscode.window.showQuickPick(options) - if (shouldOpenFolder === options[0]) { - await vscode.commands.executeCommand('vscode.openFolder') - } - } +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' - await tutorialSetup(tutorial) - await openReadme() +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('start', context) - - // setup connection to workspace - await setWorkspaceRoot() - // set workspace context path - await setStorage(context.workspaceState) - // initiate the state machine - createStateMachine() - return; - - // const modes = ['New'] - - // const canContinue = await validateCanContinue() - // if (canContinue) { - // modes.push('Continue') - // } - - // const selectedMode: string | undefined = await vscode.window.showQuickPick(modes) - - // if (!selectedMode) { - // throw new Error('No mode selected') - // return - // } - - // interface TutorialQuickPickItem extends vscode.QuickPickItem { - // id: string - // } - - // // load tutorial summaries - // const tutorialsData: { [id: string]: CR.TutorialSummary } = await api({ - // resource: 'getTutorialsSummary', - // }) - // const selectableTutorials: TutorialQuickPickItem[] = Object.keys(tutorialsData).map(id => { - // const tutorial = tutorialsData[id] - // return { - // label: tutorial.title, - // description: tutorial.description, - // // detail: '', // optional additional info - // id, - // } - // }) - // const selectedTutorial: TutorialQuickPickItem | undefined = await vscode.window.showQuickPick(selectableTutorials) - - // if (!selectedTutorial) { - // throw new Error('No tutorial selected') - // } - - // // load specific tutorial - // const tutorial: CR.Tutorial | undefined = await fetch({ - // resource: 'getTutorial', - // params: { id: selectedTutorial.id }, - // }) - - // if (!tutorial) { - // throw new Error('No tutorial found') - // } - - // switch (selectedMode) { - // // new tutorial - // case modes[0]: - // await newTutorial(tutorial) - // break - // // continue - // case modes[1]: - // await continueTutorial() - // break - // } - - // // setup hook to run tests on save - - - // TODO: start -} + 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/init.ts b/src/editor/init.ts index 98f57d58..dbc9f64a 100644 --- a/src/editor/init.ts +++ b/src/editor/init.ts @@ -2,9 +2,9 @@ // 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' -import createStateMachine from '../state' // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -14,13 +14,11 @@ export function activate(context: vscode.ExtensionContext) { // commands createCommands(context) - // tasks - // add tasks here - // views createViews(context) - + // tasks + // add tasks here } // this method is called when your extension is deactivated @@ -30,4 +28,6 @@ export function deactivate(context: vscode.ExtensionContext): void { for (const disposable of context.subscriptions) { disposable.dispose() } + // shut down state machine + deactivateMachine() } diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index e9d74caa..c3385df5 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -1,4 +1,4 @@ -import { assign, send } from 'xstate' +import { assign } from 'xstate' import * as CR from 'typings' import * as storage from '../../services/storage' import * as git from '../../services/git' @@ -12,23 +12,6 @@ let initialProgress: CR.Progress = { } export default { - start: async () => { - 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) - // if a tutorial exists, "CONTINUE" - // otherwise start from "NEW" - send(canContinue ? 'CONTINUE' : 'NEW') - }, tutorialLoad: assign({ // load initial data, progress & position data(): CR.TutorialData { diff --git a/src/state/index.ts b/src/state/index.ts index e1e1be6e..376b66ef 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,28 +1,31 @@ import { interpret } from 'xstate' 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) + } + }) -const createStateMachine = () => { - 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() - return service } -export default createStateMachine +export function deactivate() { + service.stop() +} + +export default service diff --git a/src/state/machine.ts b/src/state/machine.ts index b82e1f20..18cad205 100644 --- a/src/state/machine.ts +++ b/src/state/machine.ts @@ -13,13 +13,12 @@ export const machine = Machine< { id: 'tutorial', context: initialContext, - initial: 'Start', + initial: 'SelectTutorial', states: { - Start: { + SelectTutorial: { initial: 'Initial', states: { Initial: { - onEntry: 'start', on: { CONTINUE: 'ContinueTutorial', NEW: 'NewTutorial', diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 5d7b751b..d98e8049 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -129,7 +129,7 @@ export interface MachineEvent { export interface MachineStateSchema { states: { - Start: { + SelectTutorial: { states: { Initial: {} NewTutorial: {