Skip to content

Fix/continue #301

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

Merged
merged 5 commits into from
Apr 27, 2020
Merged
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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ Resulting in a folder structure like the following:

## [0.4.0]

- Navigate through text content from previous levels.
- Want to look back at a previous lesson's content? Navigate through text content from previous levels by clicking the "Learn" dropdown.

![traverse content](./docs/images/traverse-content.png)

- Fixes progress navigation bug when no steps in a level
- Continue an incomplete tutorial started in the same workspace. Choose the "continue" path from the start screen. Progress is stored in local storage in the workspace.

![continue tutorial](./docs/images/continue-tutorial.png)
Binary file added docs/images/continue-tutorial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 14 additions & 16 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,22 @@ class Channel implements Channel {
// continue from tutorial from local storage
const tutorial: TT.Tutorial | null = this.context.tutorial.get()

// new tutorial
this.send({ type: 'START_NEW_TUTORIAL', payload: { env } })
return

// disable continue until fixed

// // set tutorial
// const { position, progress } = await this.context.setTutorial(this.workspaceState, tutorial)
// no stored tutorial, must start new tutorial
if (!tutorial || !tutorial.id) {
this.send({ type: 'START_NEW_TUTORIAL', payload: { env } })
return
}

// if (progress.complete) {
// // tutorial is already complete
// this.send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
// return
// }
// // communicate to client the tutorial & stepProgress state
// this.send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
// load continued tutorial position & progress
const { position, progress } = await this.context.setTutorial(this.workspaceState, tutorial)

// return
if (progress.complete) {
// tutorial is already complete
this.send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
return
}
// communicate to client the tutorial & stepProgress state
this.send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
} catch (e) {
const error = {
type: 'UnknownError',
Expand Down
12 changes: 8 additions & 4 deletions web-app/src/components/Router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ declare let acquireVsCodeApi: any

const editor = acquireVsCodeApi()
const editorSend = (action: T.Action) => {
logger(`CLIENT TO EXT: "${action.type}"`)
logger(`TO EXT: "${action.type}"`)
return editor.postMessage(action)
}

// router finds first state match of <Route path='' />
const useRouter = (): Output => {
const [state, send] = useMachine<T.MachineContext, any>(createMachine({ editorSend }))

const sendWithLog = (action: T.Action): void => {
logger(`SEND: ${action.type}`, action)
send(action)
}

logger(`STATE: ${JSON.stringify(state.value)}`)

// event bus listener
Expand All @@ -38,8 +43,7 @@ const useRouter = (): Output => {
if (action.source) {
return
}
logger(`CLIENT RECEIVED: "${action.type}"`)
send(action)
sendWithLog(action)
}
window.addEventListener(listener, handler)
return () => {
Expand Down Expand Up @@ -74,7 +78,7 @@ const useRouter = (): Output => {

return {
context: state.context,
send,
send: sendWithLog,
Router,
Route,
}
Expand Down
6 changes: 5 additions & 1 deletion web-app/src/containers/Start/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ interface ContainerProps {
const StartPageContainer = ({ context, send }: ContainerProps) => {
const tutorial = context.tutorial || undefined
return (
<StartPage onContinue={() => send('CONTINUE_TUTORIAL')} onNew={() => send('NEW_TUTORIAL')} tutorial={tutorial} />
<StartPage
onContinue={() => send({ type: 'CONTINUE_TUTORIAL' })}
onNew={() => send({ type: 'NEW_TUTORIAL' })}
tutorial={tutorial}
/>
)
}

Expand Down
52 changes: 52 additions & 0 deletions web-app/src/containers/Tutorial/ContentMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react'
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import { Menu } from '@alifd/next'
import Icon from '../../components/Icon'

interface Props {
tutorial: TT.Tutorial
position: T.Position
progress: T.Progress
setTitle: (title: string) => void
setContent: (content: string) => void
}

const ContentMenu = ({ tutorial, position, progress, setTitle, setContent }: Props) => {
const setMenuContent = (levelId: string) => {
const selectedLevel: TT.Level | undefined = tutorial.levels.find((l: TT.Level) => l.id === levelId)
if (selectedLevel) {
setTitle(selectedLevel.title)
setContent(selectedLevel.content)
}
}
return (
<Menu>
{tutorial.levels.map((level: TT.Level) => {
const isCurrent = level.id === position.levelId
const isComplete = progress.levels[level.id]
let icon
let disabled = false

if (isComplete) {
// completed icon
icon = <Icon type="minus" size="xs" />
} else if (isCurrent) {
// current icon`
icon = <Icon type="minus" size="xs" />
} else {
// upcoming
disabled = true
icon = <Icon type="lock" size="xs" />
}
return (
<Menu.Item key={level.id} disabled={disabled} onSelect={() => setMenuContent(level.id)}>
{icon}&nbsp;&nbsp;&nbsp;{level.title}
</Menu.Item>
)
})}
</Menu>
)
}

export default ContentMenu
55 changes: 12 additions & 43 deletions web-app/src/containers/Tutorial/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from 'react'
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import { Menu } from '@alifd/next'
import * as selectors from '../../services/selectors'
import Icon from '../../components/Icon'
import ContentMenu from './ContentMenu'
import Level from './components/Level'
import logger from '../../services/logger'

interface PageProps {
context: T.MachineContext
Expand All @@ -23,9 +21,9 @@ const TutorialPage = (props: PageProps) => {

const onContinue = (): void => {
props.send({
type: 'LEVEL_NEXT',
type: 'NEXT_LEVEL',
payload: {
LevelId: position.levelId,
levelId: position.levelId,
},
})
}
Expand All @@ -45,48 +43,19 @@ const TutorialPage = (props: PageProps) => {
return { ...step, status }
})

const setMenuContent = (levelId: string) => {
const selectedLevel: TT.Level | undefined = tutorial.levels.find((l: TT.Level) => l.id === levelId)
if (selectedLevel) {
setTitle(selectedLevel.title)
setContent(selectedLevel.content)
}
}

const menu = (
<Menu>
{tutorial.levels.map((level: TT.Level) => {
const isCurrent = level.id === position.levelId
logger('progress', progress)
const isComplete = progress.levels[level.id]
let icon
let disabled = false

if (isComplete) {
// completed icon
icon = <Icon type="minus" size="xs" />
} else if (isCurrent) {
// current icon`
icon = <Icon type="minus" size="xs" />
} else {
// upcoming
disabled = true
icon = <Icon type="lock" size="xs" />
}
return (
<Menu.Item key={level.id} disabled={disabled} onSelect={() => setMenuContent(level.id)}>
{icon}&nbsp;&nbsp;&nbsp;{level.title}
</Menu.Item>
)
})}
</Menu>
)

return (
<Level
title={title}
content={content}
menu={menu}
menu={
<ContentMenu
tutorial={tutorial}
position={position}
progress={progress}
setTitle={setTitle}
setContent={setContent}
/>
}
index={tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId)}
steps={steps}
status={progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE'}
Expand Down
36 changes: 16 additions & 20 deletions web-app/src/services/state/actions/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as TT from 'typings/tutorial'
import { assign, send, ActionFunctionMap } from 'xstate'
import * as selectors from '../../selectors'
import onError from '../../../services/sentry/onError'
import logger from '../../../services/logger'

const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
// @ts-ignore
Expand All @@ -15,25 +16,20 @@ const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
},
}),
// @ts-ignore
storeContinuedTutorial: assign({
env: (context: T.MachineContext, event: T.MachineEvent) => {
return {
loadContinuedTutorial: assign((context: T.MachineContext, event: T.MachineEvent): any => {
return {
env: {
...context.env,
...event.payload.env,
}
},
tutorial: (context: T.MachineContext, event: T.MachineEvent) => {
return event.payload.tutorial
},
progress: (context: T.MachineContext, event: T.MachineEvent) => {
return event.payload.progress
},
position: (context: T.MachineContext, event: T.MachineEvent) => {
return event.payload.position
},
},
tutorial: event.payload.tutorial,
progress: event.payload.progress,
position: event.payload.position,
}
}),

// @ts-ignore
startNewTutorial: assign({
initProgressPosition: assign({
position: (context: T.MachineContext, event: T.MachineEvent): any => {
const position: T.Position = selectors.initialPosition(context)
return position
Expand Down Expand Up @@ -119,8 +115,7 @@ const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
// @ts-ignore
updatePosition: assign({
position: (context: T.MachineContext, event: T.MachineEvent): any => {
const { position } = event.payload
return position
return event.payload
},
}),
loadNext: send(
Expand All @@ -140,7 +135,7 @@ const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
// NEXT STEP
if (hasNextStep) {
const nextPosition = { ...position, stepId: steps[stepIndex + 1].id }
return { type: 'NEXT_STEP', payload: { position: nextPosition } }
return { type: 'NEXT_STEP', payload: nextPosition }
}

// has next level?
Expand All @@ -164,7 +159,7 @@ const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
levelId: nextLevel.id,
stepId: nextLevel.steps[0].id,
}
return { type: 'NEXT_LEVEL', payload: { position: nextPosition } }
return { type: 'NEXT_LEVEL', payload: nextPosition }
}

// COMPLETED
Expand Down Expand Up @@ -230,8 +225,9 @@ const contextActions: ActionFunctionMap<T.MachineContext, T.MachineEvent> = {
error: (): any => null,
}),
// @ts-ignore
checkEmptySteps: send((context: T.MachineContext) => {
checkLevelCompleted: send((context: T.MachineContext) => {
// no step id indicates no steps to complete
logger(context.position)
return {
type: context.position.stepId === null ? 'START_COMPLETED_LEVEL' : 'START_LEVEL',
}
Expand Down
12 changes: 6 additions & 6 deletions web-app/src/services/state/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const createMachine = (options: any) => {
},
LOAD_STORED_TUTORIAL: {
target: 'Start',
actions: ['storeContinuedTutorial'],
actions: ['loadContinuedTutorial'],
},
START_NEW_TUTORIAL: {
target: 'Start',
Expand Down Expand Up @@ -97,7 +97,7 @@ export const createMachine = (options: any) => {
on: {
NEW_TUTORIAL: 'ValidateSetup',
CONTINUE_TUTORIAL: {
target: '#tutorial-level',
target: '#tutorial',
actions: ['continueConfig'],
},
CONTINUE_FAILED: {
Expand Down Expand Up @@ -127,7 +127,7 @@ export const createMachine = (options: any) => {
},
},
StartTutorial: {
onEntry: ['startNewTutorial'],
onEntry: ['initProgressPosition'],
after: {
0: '#tutorial',
},
Expand Down Expand Up @@ -157,7 +157,7 @@ export const createMachine = (options: any) => {
initial: 'Load',
states: {
Load: {
onEntry: ['loadLevel', 'loadStep', 'checkEmptySteps'],
onEntry: ['loadLevel', 'loadStep', 'checkLevelCompleted'],
on: {
START_LEVEL: 'Normal',
START_COMPLETED_LEVEL: 'LevelComplete',
Expand Down Expand Up @@ -214,9 +214,9 @@ export const createMachine = (options: any) => {
onEntry: ['updateLevelProgress'],
onExit: ['syncLevelProgress'],
on: {
LEVEL_NEXT: {
NEXT_LEVEL: {
target: '#tutorial-load-next',
actions: ['testClear'],
actions: ['testClear', 'updatePosition'],
},
},
},
Expand Down