Skip to content

WIP - Split state machines #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as T from 'typings'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { EditorEvents, ClientEvents } from 'typings/events'
import * as vscode from 'vscode'
import saveCommit from '../actions/saveCommit'
import setupActions from '../actions/setupActions'
Expand All @@ -10,18 +11,18 @@ import logger from '../services/logger'
import Context from './context'

interface Channel {
receive(action: T.Action): Promise<void>
send(action: T.Action): Promise<void>
receive(action: EditorEvents): Promise<void>
send(action: ClientEvents): Promise<void>
}

interface ChannelProps {
postMessage: (action: T.Action) => Thenable<boolean>
postMessage: (action: ClientEvents) => Thenable<boolean>
workspaceState: vscode.Memento
workspaceRoot: vscode.WorkspaceFolder
}

class Channel implements Channel {
private postMessage: (action: T.Action) => Thenable<boolean>
private postMessage: (action: ClientEvents) => Thenable<boolean>
private workspaceState: vscode.Memento
private workspaceRoot: vscode.WorkspaceFolder
private context: Context
Expand All @@ -34,10 +35,11 @@ class Channel implements Channel {
}

// receive from webview
public receive = async (action: T.Action) => {
public receive = async (action: EditorEvents) => {
// action may be an object.type or plain string
const actionType: string = typeof action === 'string' ? action : action.type
const onError = (error: T.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })
// @ts-ignore TODO: actual error, fix !
const onError = (error: CR.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })

switch (actionType) {
case 'ENV_GET':
Expand All @@ -47,17 +49,18 @@ class Channel implements Channel {
env: {
machineId: vscode.env.machineId,
sessionId: vscode.env.sessionId,
token: '',
},
},
})
return
// continue from tutorial from local storage
case 'EDITOR_TUTORIAL_LOAD':
case 'EDITOR_LOAD_STORED_TUTORIAL':
const tutorial: G.Tutorial | null = this.context.tutorial.get()

// new tutorial
if (!tutorial || !tutorial.id || !tutorial.version) {
this.send({ type: 'NEW_TUTORIAL' })
this.send({ type: 'NO_CONTINUE' })
return
}

Expand All @@ -66,21 +69,22 @@ class Channel implements Channel {

if (progress.complete) {
// tutorial is already complete
this.send({ type: 'NEW_TUTORIAL' })
this.send({ type: 'NO_CONTINUE' })
return
}

// communicate to client the tutorial & stepProgress state
this.send({ type: 'CONTINUE_TUTORIAL', payload: { tutorial, progress, position } })
this.send({ type: 'CAN_CONTINUE', payload: { tutorial, progress, position } })

return
// clear tutorial local storage
case 'TUTORIAL_CLEAR':
case 'EDITOR_CLEAR_TUTORIAL_STORAGE':
// clear current progress/position/tutorial
this.context.reset()
return
// configure test runner, language, git
case 'EDITOR_TUTORIAL_CONFIG':
// @ts-ignore TODO: fix typings
const tutorialData: G.Tutorial = action.payload.tutorial
// setup tutorial config (save watcher, test runner, etc)
this.context.setTutorial(this.workspaceState, tutorialData)
Expand All @@ -97,7 +101,7 @@ class Channel implements Channel {
if (!tutorialContinue) {
throw new Error('Invalid tutorial to continue')
}
const continueConfig: T.TutorialConfig = tutorialContinue.version.data.config
const continueConfig: CR.TutorialConfig = tutorialContinue.version.data.config
tutorialConfig(
{
config: continueConfig,
Expand All @@ -106,17 +110,22 @@ class Channel implements Channel {
onError,
)
// update the current stepId on startup
// @ts-ignore TODO: fix typings
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_STEP, action.payload)
return
// load step actions (git commits, commands, open files)
case 'SETUP_ACTIONS':
// @ts-ignore TODO: fix typings
await vscode.commands.executeCommand(COMMANDS.SET_CURRENT_STEP, action.payload)
// @ts-ignore TODO: fix typings
setupActions(this.workspaceRoot, action.payload, this.send)
return
// load solution step actions (git commits, commands, open files)
case 'SOLUTION_ACTIONS':
// @ts-ignore TODO: fix typings
await solutionActions(this.workspaceRoot, action.payload, this.send)
// run test following solution to update position
// @ts-ignore TODO: fix typings
vscode.commands.executeCommand(COMMANDS.RUN_TEST, action.payload)
return

Expand All @@ -126,12 +135,13 @@ class Channel implements Channel {
}
}
// send to webview
public send = async (action: T.Action) => {
public send = async (action: ClientEvents) => {
// action may be an object.type or plain string
const actionType: string = typeof action === 'string' ? action : action.type
switch (actionType) {
case 'TEST_PASS':
// update local storage stepProgress
// @ts-ignore TODO: fix typings
const progress = this.context.progress.setStepComplete(action.payload.stepId)
const tutorial = this.context.tutorial.get()
if (!tutorial) {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"emitDecoratorMetadata": true,
"paths": {
"typings": ["../typings/index.d.ts"],
"typings/graphql": ["../typings/graphql.d.ts"]
"typings/graphql": ["../typings/graphql.d.ts"],
"typings/events": ["../typings/events.d.ts"]
},
"allowJs": true,
"removeComments": true
Expand Down
78 changes: 78 additions & 0 deletions typings/events.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as CR from './index'
import * as G from './graphql'

/*
--- Editor Events ---
sent from client to editor
*/

export type EnvGetEvent = { type: 'ENV_GET' }
export type EditorTutorialConfigEvent = { type: 'EDITOR_TUTORIAL_CONFIG'; payload: { tutorial: G.Tutorial } }
export type StepActionsEvent = { type: 'SETUP_ACTIONS'; payload: CR.StepActions }
export type SolutionActionsEvent = { type: 'SOLUTION_ACTIONS'; payload: CR.StepActions }

export type EditorEvents =
| EnvGetEvent
| { type: 'EDITOR_LOAD_STORED_TUTORIAL' }
| { type: 'EDITOR_CLEAR_TUTORIAL_STORAGE' }
| EditorTutorialConfigEvent
| { type: 'EDITOR_TUTORIAL_CONTINUE_CONFIG' }
| StepActionsEvent
| SolutionActionsEvent

/*
--- Client Events ---
sent within client
or sent from editor to client
*/

export type EventLoadEvent = { type: 'ENV_LOAD'; payload: { env: CR.Environment } }
export type ErrorMessageEvent = { type: 'ERROR'; payload: CR.ErrorMessage }
export type CommandStartEvent = { type: 'COMMAND_START'; payload: { process: CR.ProcessEvent } }
export type CommandSuccessEvent = { type: 'COMMAND_SUCCESS'; payload: { process: CR.ProcessEvent } }
export type CommandFailEvent = { type: 'COMMAND_FAIL'; payload: { process: CR.ProcessEvent } }
export type TestPassEvent = { type: 'TEST_PASS'; payload: { stepId: string } }
export type TestFailEvent = { type: 'TEST_FAIL'; payload: { stepId: string } }
export type NextStepEvent = { type: 'NEXT_STEP'; payload: { position: CR.Position } }
export type NextLevelEvent = { type: 'NEXT_LEVEL'; payload: { position: CR.Position } }
export type TestRunningEvent = { type: 'TEST_RUNNING'; payload: { stepId: string } }
export type TestErrorEvent = { type: 'TEST_ERROR'; payload: { stepId: string } }
export type LoadNextStepEvent = { type: 'LOAD_NEXT_STEP'; payload: { step: string } }
export type CanContinueEvent = {
type: 'CAN_CONTINUE'
payload: { tutorial: G.Tutorial; progress: CR.Progress; position: CR.Position }
}
export type LoadTutorialEvent = { type: 'LOAD_TUTORIAL'; payload: { tutorial: G.Tutorial } }
export type TutorialSelectedEvent = { type: 'TUTORIAL_SELECTED'; payload: { tutorial: G.Tutorial } }

export type AuthenticateEvents = EventLoadEvent | { type: 'AUTHENTICATED' } | ErrorEvent

export type PlayTutorialEvents =
| CommandStartEvent
| CommandSuccessEvent
| CommandFailEvent
| ErrorEvent
| NextStepEvent
| NextLevelEvent
| { type: 'COMPLETED' }
| TestRunningEvent
| { type: 'STEP_SOLUTION_LOAD' }
| TestPassEvent
| TestFailEvent
| TestErrorEvent
| LoadNextStepEvent
| { type: 'LEVEL_COMPLETE' }
| { type: 'EXIT' }

export type SelectTutorialEvents =
| { type: 'NO_CONTINUE' }
| CanContinueEvent
| LoadTutorialEvent
| { type: 'BACK' }
| TutorialSelectedEvent
| { type: 'TUTORIAL_CONFIGURED' }
| { type: 'CHOOSE_NEW' }
| { type: 'CHOOSE_CONTINUE' }
| ErrorEvent

type ClientEvents = AuthenticateEvents | SelectTutorialEvents | PlayTutorialEvents
49 changes: 0 additions & 49 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,6 @@ export interface ErrorMessage {
description?: string
}

export interface MachineContext {
env: Environment
error: ErrorMessage | null
tutorial: G.Tutorial | null
position: Position
progress: Progress
processes: ProcessEvent[]
}

export interface MachineEvent {
type: string
payload?: any
data?: any
}

export interface MachineStateSchema {
states: {
Start: {
states: {
Startup: {}
Authenticate: {}
NewOrContinue: {}
SelectTutorial: {}
ContinueTutorial: {}
}
}
Tutorial: {
states: {
Initialize: {}
Summary: {}
LoadNext: {}
Level: {
states: {
Load: {}
Normal: {}
TestRunning: {}
TestPass: {}
TestFail: {}
TestError: {}
StepNext: {}
LevelComplete: {}
}
}
Completed: {}
}
}
}
}

export interface StateMachine {
activate(): void
deactivate(): void
Expand Down
Loading