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
Prev Previous commit
Next Next commit
heavy refactoring
  • Loading branch information
ShMcK committed Jan 26, 2020
commit af502edf55e334c7a69e0789888ce97679644a3a
28 changes: 15 additions & 13 deletions web-app/src/Routes.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react'
import * as CR from 'typings'
import { MachineContext as RootMachineContext } from './services/state/machine'
import { MachineContext as SelectTutorialContext } from './services/state/selectTutorial'
import { MachineContext as PlayTutorialContext } from './services/state/playTutorial'
import Router from './components/Router'
import Workspace from './components/Workspace'
import ContinuePage from './containers/Continue'
Expand All @@ -18,29 +20,29 @@ const Routes = () => {
return (
<Workspace>
<Router>
<Route path={['SelectTutorial.Startup', 'SelectTutorial.Authenticate', 'SelectTutorial.NewOrContinue']}>
<LoadingPage text="Launching..." context={{} as CR.MachineContext} />
<Route path={'Initializing'}>
<LoadingPage text="Launching..." context={{} as RootMachineContext} />
</Route>
<Route path="SelectTutorial.SelectTutorial">
<NewPage send={tempSend} context={{} as CR.MachineContext} />
<Route path="Start.SelectTutorial">
<NewPage send={tempSend} context={{} as SelectTutorialContext} />
</Route>
<Route path="SelectTutorial.ContinueTutorial">
<ContinuePage send={tempSend} context={{} as CR.MachineContext} />
<Route path="Start.ContinueTutorial">
<ContinuePage send={tempSend} context={{} as SelectTutorialContext} />
</Route>
<Route path="PlayTutorial.Initialize">
<LoadingPage text="Initializing..." context={{} as CR.MachineContext} />
<Route path="Start.Initialize">
<LoadingPage text="Initializing..." context={{} as SelectTutorialContext} />
</Route>
<Route path="PlayTutorial.LoadNext">
<LoadingPage text="Loading..." context={{} as CR.MachineContext} />
<LoadingPage text="Loading..." context={{} as PlayTutorialContext} />
</Route>
<Route path="PlayTutorial.Summary">
<OverviewPage send={tempSend} context={{} as CR.MachineContext} />
<OverviewPage send={tempSend} context={{} as PlayTutorialContext} />
</Route>
<Route path="PlayTutorial.Level">
<LevelSummaryPage send={tempSend} context={{} as CR.PlayMachineContext} />
<LevelSummaryPage send={tempSend} context={{} as PlayTutorialContext} />
</Route>
<Route path="PlayTutorial.Completed">
<CompletedPage send={tempSend} context={{} as CR.PlayMachineContext} />
<CompletedPage send={tempSend} context={{} as PlayTutorialContext} />
</Route>
</Router>
</Workspace>
Expand Down
4 changes: 2 additions & 2 deletions web-app/src/components/Debugger/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react'
import * as T from 'typings'
import { css, jsx } from '@emotion/core'
import { MachineContext } from '../../services/state/playTutorial'

interface Props extends T.PlayMachineContext {
interface Props extends MachineContext {
state: string
children: React.ReactElement
}
Expand Down
10 changes: 7 additions & 3 deletions web-app/src/components/Router/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import * as React from 'react'
import * as CR from 'typings'
import channel from '../../services/channel'
import messageBusReceiver from '../../services/channel/receiver'
import machine from '../../services/state/machine'
import { useMachine } from '../../services/xstate-react'
import debuggerWrapper from '../Debugger/debuggerWrapper'
import Route from './Route'
import onError from '../../services/sentry/onError'
import { MachineContext, MachineEvent } from '../../services/state/machine'

interface Props {
children: any
}

interface CloneElementProps {
context: CR.MachineContext
send(action: CR.Action): void
context: MachineContext
send(action: MachineEvent): void
}

// TODO: rewrite router, logic is messy

// router finds first state match of <Route path='' />
const Router = ({ children }: Props): React.ReactElement<CloneElementProps> | null => {
const [state, send] = useMachine(machine, {
Expand All @@ -32,6 +34,7 @@ const Router = ({ children }: Props): React.ReactElement<CloneElementProps> | nu

const childArray = React.Children.toArray(children)
for (const child of childArray) {
// @ts-ignore
const { path } = child.props
let pathMatch
if (typeof path === 'string') {
Expand All @@ -42,6 +45,7 @@ const Router = ({ children }: Props): React.ReactElement<CloneElementProps> | nu
throw new Error(`Invalid route path ${JSON.stringify(path)}`)
}
if (pathMatch) {
// @ts-ignore
const element = React.cloneElement<CloneElementProps>(child.props.children, { send, context: state.context })
return debuggerWrapper(element, state)
}
Expand Down
12 changes: 8 additions & 4 deletions web-app/src/containers/Continue/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { MachineContext, MachineEvent } from '../../services/state/selectTutorial'
import { css, jsx } from '@emotion/core'
import Button from '../../components/Button'
import Card from '../../components/Card'
Expand Down Expand Up @@ -47,8 +47,8 @@ export const ContinuePage = (props: Props) => (
)

interface ContainerProps {
context: CR.MachineContext
send(action: CR.Action | string): void
context: MachineContext
send(action: MachineEvent): void
}

const ContinuePageContainer = ({ context, send }: ContainerProps) => {
Expand All @@ -59,7 +59,11 @@ const ContinuePageContainer = ({ context, send }: ContainerProps) => {
}

return (
<ContinuePage tutorial={tutorial} onContinue={() => send('TUTORIAL_START')} onNew={() => send('TUTORIAL_SELECT')} />
<ContinuePage
tutorial={tutorial}
onContinue={() => send({ type: 'TUTORIAL_START' })}
onNew={() => send({ type: 'SELECT_NEW_TUTORIAL' })}
/>
)
}

Expand Down
3 changes: 1 addition & 2 deletions web-app/src/containers/LoadingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as React from 'react'
import * as T from 'typings'
import { css, jsx } from '@emotion/core'
import Loading from '../components/Loading'
import Message from '../components/Message'

interface Props {
text: string
context: T.MachineContext
context: any
}

const styles = {
Expand Down
6 changes: 3 additions & 3 deletions web-app/src/containers/New/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useQuery } from '@apollo/react-hooks'
import * as React from 'react'
import * as T from 'typings'
import * as G from 'typings/graphql'
import { MachineContext, MachineEvent } from '../../services/state/selectTutorial'
import ErrorView from '../../components/Error'
import queryTutorials from '../../services/apollo/queries/tutorials'
import LoadingPage from '../LoadingPage'
import NewPage from './NewPage'

interface ContainerProps {
send(action: T.Action): void
context: T.MachineContext
send(action: MachineEvent): void
context: MachineContext
}

interface TutorialsData {
Expand Down
5 changes: 2 additions & 3 deletions web-app/src/containers/Overview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useQuery } from '@apollo/react-hooks'
import * as React from 'react'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import ErrorView from '../../components/Error'
import queryTutorial from '../../services/apollo/queries/tutorial'
import OverviewPage from './OverviewPage'
import { MachineContext } from '../../services/state/selectTutorial'
import { MachineContext, MachineEvent } from '../../services/state/selectTutorial'

interface PageProps {
context: MachineContext
send(action: CR.Action): void
send(action: MachineEvent): void
}

interface TutorialData {
Expand Down
8 changes: 4 additions & 4 deletions web-app/src/containers/Tutorial/CompletedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import * as CR from 'typings'
import { MachineContext, MachineEvent } from '../../services/state/playTutorial'
import { css, jsx } from '@emotion/core'
import Button from '../../components/Button'

Expand All @@ -10,13 +10,13 @@ const styles = {
}

interface Props {
context: CR.MachineContext
send(action: CR.Action | string): void
context: MachineContext
send(action: MachineEvent): void
}

const CompletedPage = (props: Props) => {
const selectNewTutorial = () => {
props.send('SELECT_TUTORIAL')
props.send({ type: 'EXIT' })
}
return (
<div>
Expand Down
5 changes: 3 additions & 2 deletions web-app/src/containers/Tutorial/LevelPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import * as T from 'typings'
import * as G from 'typings/graphql'
import * as selectors from '../../../services/selectors'
import Level from './Level'
import { MachineContext } from '../../../services/state/playTutorial'

interface PageProps {
context: T.PlayMachineContext
context: MachineContext
send(action: T.Action): void
}

Expand All @@ -17,7 +18,7 @@ const LevelSummaryPageContainer = (props: PageProps) => {

const onContinue = (): void => {
props.send({
type: 'LEVEL_NEXT',
type: 'NEXT_LEVEL',
payload: {
LevelId: position.levelId,
},
Expand Down
37 changes: 10 additions & 27 deletions web-app/src/services/channel/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Action } from 'typings'
import { MachineEvent } from '../state/machine'
import { MachineEvent as AuthenticateEvent } from '../state/authenticate'
import { MachineEvent as SelectTutorialEvent } from '../state/selectTutorial'
import { MachineEvent as PlayTutorialEvent } from '../state/playTutorial'

declare let acquireVsCodeApi: any

type SendEvent = MachineEvent | AuthenticateEvent | SelectTutorialEvent | PlayTutorialEvent

interface ReceivedEvent {
data: Action
data: SendEvent
}

class Channel {
Expand All @@ -21,10 +26,10 @@ class Channel {
this.editorSend = editor.postMessage
}

public machineSend = (action: Action | string) => {
public machineSend = (event: SendEvent) => {
/* implemented by `setMachineSend` in router on startup */
}
public editorSend = (action: Action) => {
public editorSend = (event: SendEvent) => {
/* */
}

Expand All @@ -40,29 +45,7 @@ class Channel {
return
}

// messages from core
switch (action.type) {
case 'ENV_LOAD':
case 'AUTHENTICATED':
case 'TUTORIAL_LOADED':
case 'NEW_TUTORIAL':
case 'TUTORIAL_CONFIGURED':
case 'CONTINUE_TUTORIAL':
case 'TEST_PASS':
case 'TEST_FAIL':
case 'TEST_RUNNING':
case 'TEST_ERROR':
case 'COMMAND_START':
case 'COMMAND_SUCCESS':
case 'COMMAND_FAIL':
case 'ERROR':
this.machineSend(action)
return
default:
if (action.type) {
console.warn(`Unknown received action ${action.type}`, action)
}
}
this.machineSend(action)
}
}

Expand Down
8 changes: 4 additions & 4 deletions web-app/src/services/selectors/tutorial.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createSelector } from 'reselect'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import onError from '../../services/sentry/onError'
import { MachineContext } from '../../services/state/playTutorial'

export const currentTutorial = ({ tutorial }: CR.MachineContext): G.Tutorial => {
export const currentTutorial = ({ tutorial }: MachineContext): G.Tutorial => {
if (!tutorial) {
const error = new Error('Tutorial not found')
onError(error)
Expand All @@ -21,7 +21,7 @@ export const currentVersion = createSelector(currentTutorial, (tutorial: G.Tutor
return tutorial.version
})

export const currentLevel = (context: CR.PlayMachineContext): G.Level =>
export const currentLevel = (context: MachineContext): G.Level =>
createSelector(
currentVersion,
(version: G.TutorialVersion): G.Level => {
Expand All @@ -41,7 +41,7 @@ export const currentLevel = (context: CR.PlayMachineContext): G.Level =>
},
)(context)

export const currentStep = (context: CR.PlayMachineContext): G.Step =>
export const currentStep = (context: MachineContext): G.Step =>
createSelector(
currentLevel,
(level: G.Level): G.Step => {
Expand Down
13 changes: 6 additions & 7 deletions web-app/src/services/state/authenticate/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ interface AuthenticateVariables {
}

const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
// @ts-ignore
setEnv: assign({
env: (context: CR.MachineContext, event: CR.MachineEvent) => {
return {
...context.env,
...event.payload.env,
}
},
env: (context: MachineContext, event: { type: 'ENV_LOAD'; payload: { env: CR.Environment } }): CR.Environment => ({
...context.env,
...event.payload.env,
}),
}),
authenticate: async (context: CR.MachineContext): Promise<void> => {
authenticate: async (context: MachineContext): Promise<void> => {
const result = await client
.mutate<AuthenticateData, AuthenticateVariables>({
mutation: authenticateMutation,
Expand Down
7 changes: 6 additions & 1 deletion web-app/src/services/state/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type MachineContext = {
env: CR.Environment
error: CR.ErrorMessage | null
tutorial: G.Tutorial | null
position: CR.Position
progress: CR.Progress
}

export type MachineStateSchema = {
Expand All @@ -30,6 +32,8 @@ export const machine = Machine<MachineContext, MachineStateSchema, MachineEvent>
error: null,
env: { machineId: '', sessionId: '', token: '' },
tutorial: null,
progress: { levels: {}, steps: {}, complete: false },
position: { levelId: '', stepId: '' },
},
states: {
// load environment
Expand All @@ -53,9 +57,10 @@ export const machine = Machine<MachineContext, MachineStateSchema, MachineEvent>
src: selectTutorialMachine,
onDone: 'PlayTutorial',
data: {
env: (context: MachineContext) => context.env,
tutorial: (context: MachineContext) => context.tutorial,
error: null,
position: (context: MachineContext) => context.position,
progress: (context: MachineContext) => context.progress,
},
},
},
Expand Down
Loading