Skip to content

Commit 54b5428

Browse files
authoredApr 26, 2020
Merge pull request coderoad#296 from coderoad/traverse-content
Traverse content (blocked)
2 parents 38a474f + 965064e commit 54b5428

File tree

8 files changed

+176
-45
lines changed

8 files changed

+176
-45
lines changed
 

‎src/channel/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ class Channel implements Channel {
297297
vscode.commands.executeCommand(COMMANDS.RUN_TEST)
298298
return
299299

300+
case 'EDITOR_SYNC_PROGRESS':
301+
// update progress when a level is deemed complete in the client
302+
await this.context.progress.syncProgress(action.payload.progress)
303+
return
304+
300305
default:
301306
logger(`No match for action type: ${actionType}`)
302307
return

‎src/channel/state/Progress.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class Progress {
3939
public reset = () => {
4040
this.set(defaultValue)
4141
}
42+
public syncProgress = (progress: T.Progress): T.Progress => {
43+
const next = { ...this.value, ...progress }
44+
return this.set(next)
45+
}
4246
public setStepComplete = (tutorial: TT.Tutorial, stepId: string): T.Progress => {
4347
const next = this.value
4448
// mark step complete

‎web-app/src/containers/Tutorial/components/Level.tsx

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import * as React from 'react'
22
import * as T from 'typings'
33
import * as TT from 'typings/tutorial'
44
import { css, jsx } from '@emotion/core'
5+
import { Dropdown } from '@alifd/next'
6+
import Icon from '../../../components/Icon'
57
import Button from '../../../components/Button'
68
import Markdown from '../../../components/Markdown'
79
import ProcessMessages from '../../../components/ProcessMessages'
@@ -22,12 +24,19 @@ const styles = {
2224
paddingBottom: '5rem',
2325
},
2426
header: {
27+
display: 'flex' as 'flex',
28+
alignItems: 'center',
29+
justifyContent: 'space-between',
2530
height: '2rem',
2631
backgroundColor: '#EBEBEB',
2732
fontSize: '1rem',
2833
lineHeight: '1rem',
2934
padding: '10px 1rem',
3035
},
36+
learn: {
37+
textDecoration: 'none',
38+
color: 'inherit',
39+
},
3140
text: {
3241
padding: '0rem 1rem',
3342
paddingBottom: '1rem',
@@ -77,18 +86,34 @@ const styles = {
7786
}
7887

7988
interface Props {
80-
level: TT.Level & { status: T.ProgressStatus; index: number; steps: Array<TT.Step & { status: T.ProgressStatus }> }
89+
menu: any
90+
steps: Array<TT.Step & { status: T.ProgressStatus }>
91+
title: string
92+
index: number
93+
content: string
94+
status: 'COMPLETE' | 'ACTIVE' | 'INCOMPLETE'
8195
processes: T.ProcessEvent[]
8296
testStatus: T.TestStatus | null
8397
onContinue(): void
8498
onLoadSolution(): void
8599
}
86100

87-
const Level = ({ level, onContinue, onLoadSolution, processes, testStatus }: Props) => {
101+
const Level = ({
102+
menu,
103+
steps,
104+
title,
105+
content,
106+
index,
107+
status,
108+
onContinue,
109+
onLoadSolution,
110+
processes,
111+
testStatus,
112+
}: Props) => {
88113
// @ts-ignore
89-
let currentStep = level.steps.findIndex((s) => s.status === 'ACTIVE')
114+
let currentStep = steps.findIndex((s) => s.status === 'ACTIVE')
90115
if (currentStep === -1) {
91-
currentStep = level.steps.length
116+
currentStep = steps.length
92117
}
93118

94119
const pageBottomRef = React.useRef(null)
@@ -103,18 +128,27 @@ const Level = ({ level, onContinue, onLoadSolution, processes, testStatus }: Pro
103128
<div css={styles.page}>
104129
<div css={styles.content}>
105130
<div css={styles.header}>
106-
<span>Learn</span>
131+
<Dropdown
132+
trigger={
133+
<a css={styles.learn}>
134+
Learn <Icon type="arrow-down" size="xxs" />
135+
</a>
136+
}
137+
triggerType="click"
138+
>
139+
{menu}
140+
</Dropdown>
107141
</div>
108142
<div css={styles.text}>
109-
<h2 css={styles.title}>{level.title}</h2>
110-
<Markdown>{level.content || ''}</Markdown>
143+
<h2 css={styles.title}>{title}</h2>
144+
<Markdown>{content || ''}</Markdown>
111145
</div>
112146

113-
{level.steps.length ? (
147+
{steps.length ? (
114148
<div css={styles.tasks}>
115149
<div css={styles.header}>Tasks</div>
116150
<div css={styles.steps}>
117-
{level.steps.map((step: (TT.Step & { status: T.ProgressStatus }) | null, index: number) => {
151+
{steps.map((step: (TT.Step & { status: T.ProgressStatus }) | null, index: number) => {
118152
if (!step) {
119153
return null
120154
}
@@ -146,17 +180,17 @@ const Level = ({ level, onContinue, onLoadSolution, processes, testStatus }: Pro
146180

147181
<div css={styles.footer}>
148182
<span>
149-
{typeof level.index === 'number' ? `${level.index + 1}. ` : ''}
150-
{level.title}
183+
{typeof index === 'number' ? `${index + 1}. ` : ''}
184+
{title}
151185
</span>
152186
<span>
153-
{level.status === 'COMPLETE' || !level.steps.length ? (
187+
{status === 'COMPLETE' || !steps.length ? (
154188
<Button type="primary" onClick={onContinue}>
155189
Continue
156190
</Button>
157191
) : (
158192
<span css={styles.taskCount}>
159-
{currentStep} of {level.steps.length} tasks
193+
{currentStep} of {steps.length} tasks
160194
</span>
161195
)}
162196
</span>

‎web-app/src/containers/Tutorial/index.tsx

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as React from 'react'
22
import * as T from 'typings'
33
import * as TT from 'typings/tutorial'
4+
import { Menu } from '@alifd/next'
45
import * as selectors from '../../services/selectors'
6+
import Icon from '../../components/Icon'
57
import Level from './components/Level'
8+
import logger from '../../services/logger'
69

710
interface PageProps {
811
context: T.MachineContext
@@ -15,6 +18,9 @@ const TutorialPage = (props: PageProps) => {
1518
const tutorial = selectors.currentTutorial(props.context)
1619
const levelData: TT.Level = selectors.currentLevel(props.context)
1720

21+
const [title, setTitle] = React.useState<string>(levelData.title)
22+
const [content, setContent] = React.useState<string>(levelData.content)
23+
1824
const onContinue = (): void => {
1925
props.send({
2026
type: 'LEVEL_NEXT',
@@ -28,29 +34,62 @@ const TutorialPage = (props: PageProps) => {
2834
props.send({ type: 'STEP_SOLUTION_LOAD' })
2935
}
3036

31-
const level: TT.Level & {
32-
status: T.ProgressStatus
33-
index: number
34-
steps: Array<TT.Step & { status: T.ProgressStatus }>
35-
} = {
36-
...levelData,
37-
index: tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId),
38-
status: progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE',
39-
steps: levelData.steps.map((step: TT.Step) => {
40-
// label step status for step component
41-
let status: T.ProgressStatus = 'INCOMPLETE'
42-
if (progress.steps[step.id]) {
43-
status = 'COMPLETE'
44-
} else if (step.id === position.stepId) {
45-
status = 'ACTIVE'
46-
}
47-
return { ...step, status }
48-
}),
37+
const steps = levelData.steps.map((step: TT.Step) => {
38+
// label step status for step component
39+
let status: T.ProgressStatus = 'INCOMPLETE'
40+
if (progress.steps[step.id]) {
41+
status = 'COMPLETE'
42+
} else if (step.id === position.stepId) {
43+
status = 'ACTIVE'
44+
}
45+
return { ...step, status }
46+
})
47+
48+
const setMenuContent = (levelId: string) => {
49+
const selectedLevel: TT.Level | undefined = tutorial.levels.find((l: TT.Level) => l.id === levelId)
50+
if (selectedLevel) {
51+
setTitle(selectedLevel.title)
52+
setContent(selectedLevel.content)
53+
}
4954
}
5055

56+
const menu = (
57+
<Menu>
58+
{tutorial.levels.map((level: TT.Level) => {
59+
const isCurrent = level.id === position.levelId
60+
logger('progress', progress)
61+
const isComplete = progress.levels[level.id]
62+
let icon
63+
let disabled = false
64+
65+
if (isComplete) {
66+
// completed icon
67+
icon = <Icon type="minus" size="xs" />
68+
} else if (isCurrent) {
69+
// current icon`
70+
icon = <Icon type="minus" size="xs" />
71+
} else {
72+
// upcoming
73+
disabled = true
74+
icon = <Icon type="lock" size="xs" />
75+
}
76+
return (
77+
<Menu.Item key={level.id} disabled={disabled} onSelect={() => setMenuContent(level.id)}>
78+
{icon}&nbsp;&nbsp;&nbsp;{level.title}
79+
</Menu.Item>
80+
)
81+
})}
82+
</Menu>
83+
)
84+
5185
return (
5286
<Level
53-
level={level}
87+
title={title}
88+
content={content}
89+
menu={menu}
90+
index={tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId)}
91+
steps={steps}
92+
status={progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE'}
5493
onContinue={onContinue}
5594
onLoadSolution={onLoadSolution}
5695
processes={processes}

‎web-app/src/environment.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ for (const required of requiredKeys) {
99
export const DEBUG: boolean = (process.env.REACT_APP_DEBUG || '').toLowerCase() === 'true'
1010
export const VERSION: string = process.env.VERSION || 'unknown'
1111
export const NODE_ENV: string = process.env.NODE_ENV || 'development'
12-
export const LOG: boolean =
13-
(process.env.REACT_APP_LOG || '').toLowerCase() === 'true' && process.env.NODE_ENV !== 'production'
12+
export const LOG: boolean = (process.env.REACT_APP_LOG || '').toLowerCase() === 'true'
1413
export const TUTORIAL_LIST_URL: string = process.env.REACT_APP_TUTORIAL_LIST_URL || ''
1514
export const SENTRY_DSN: string | null = process.env.REACT_APP_SENTRY_DSN || null

‎web-app/src/services/state/actions/editor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ export default (editorSend: any) => ({
7474
})
7575
}
7676
},
77+
syncLevelProgress(context: CR.MachineContext): void {
78+
editorSend({
79+
type: 'EDITOR_SYNC_PROGRESS',
80+
payload: {
81+
progress: context.progress,
82+
},
83+
})
84+
},
7785
clearStorage(): void {
7886
editorSend({ type: 'TUTORIAL_CLEAR' })
7987
},

‎web-app/src/services/state/machine.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,12 @@ export const createMachine = (options: any) => {
207207
target: 'Normal',
208208
actions: ['loadStep'],
209209
},
210-
LEVEL_COMPLETE: {
211-
target: 'LevelComplete',
212-
actions: ['updateLevelProgress'],
213-
},
210+
LEVEL_COMPLETE: 'LevelComplete',
214211
},
215212
},
216213
LevelComplete: {
214+
onEntry: ['updateLevelProgress'],
215+
onExit: ['syncLevelProgress'],
217216
on: {
218217
LEVEL_NEXT: {
219218
target: '#tutorial-load-next',

‎web-app/stories/Level.stories.tsx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,28 @@ import * as T from '../../typings'
66
import * as TT from '../../typings/tutorial'
77
import Level from '../src/containers/Tutorial/components/Level'
88
import SideBarDecorator from './utils/SideBarDecorator'
9+
import { Menu } from '@alifd/next'
10+
import Icon from '../src/components/Icon'
911

1012
type ModifiedLevel = TT.Level & {
1113
status: T.ProgressStatus
1214
index: number
1315
steps: Array<TT.Step & { status: T.ProgressStatus }>
1416
}
1517

18+
const menu = (
19+
<Menu>
20+
{[{ id: '1', title: 'First' }].map((level: TT.Level) => {
21+
const icon = <Icon type="eye" size="xs" />
22+
return (
23+
<Menu.Item key={level.id}>
24+
{icon}&nbsp;&nbsp;&nbsp;{level.title}
25+
</Menu.Item>
26+
)
27+
})}
28+
</Menu>
29+
)
30+
1631
storiesOf('Level', module)
1732
.addDecorator(SideBarDecorator)
1833
.addDecorator(withKnobs)
@@ -24,7 +39,7 @@ storiesOf('Level', module)
2439
description: 'A summary of the level',
2540
content: 'Some content here in markdown',
2641
setup: null,
27-
status: 'ACTIVE',
42+
status: 'ACTIVE' as 'ACTIVE',
2843
steps: [
2944
{
3045
id: 'L1:S1',
@@ -72,7 +87,12 @@ storiesOf('Level', module)
7287
}
7388
return (
7489
<Level
75-
level={level}
90+
menu={menu}
91+
title={level.title}
92+
content={level.content}
93+
index={0}
94+
status={level.status}
95+
steps={level.steps}
7696
onContinue={action('onContinue')}
7797
onLoadSolution={action('onLoadSolution')}
7898
processes={[]}
@@ -88,6 +108,7 @@ storiesOf('Level', module)
88108
description: 'A description',
89109
content: 'Should support markdown test\n ```js\nvar a = 1\n```\nwhew it works!',
90110
setup: { commits: ['77e57cd'], commands: ['npm install'], files: [] },
111+
status: 'ACTIVE' as 'ACTIVE',
91112
steps: [
92113
{
93114
id: 'L1:S1',
@@ -126,7 +147,12 @@ storiesOf('Level', module)
126147
}
127148
return (
128149
<Level
129-
level={level}
150+
menu={menu}
151+
title={level.title}
152+
content={level.content}
153+
index={0}
154+
status={level.status}
155+
steps={level.steps}
130156
onContinue={action('onContinue')}
131157
onLoadSolution={action('onLoadSolution')}
132158
processes={[
@@ -150,6 +176,7 @@ storiesOf('Level', module)
150176
commits: ['6adeb95'],
151177
commands: ['npm install'],
152178
},
179+
status: 'ACTIVE' as 'ACTIVE',
153180
steps: [
154181
{
155182
id: 'L1:S1',
@@ -209,7 +236,12 @@ storiesOf('Level', module)
209236
}
210237
return (
211238
<Level
212-
level={level}
239+
menu={menu}
240+
title={level.title}
241+
content={level.content}
242+
index={0}
243+
status={level.status}
244+
steps={level.steps}
213245
onContinue={action('onContinue')}
214246
onLoadSolution={action('onLoadSolution')}
215247
processes={[]}
@@ -228,6 +260,7 @@ storiesOf('Level', module)
228260
commits: ['0d7543c'],
229261
commands: ['npm install'],
230262
},
263+
status: 'ACTIVE' as 'ACTIVE',
231264
steps: [
232265
{
233266
id: 'L2:S1',
@@ -281,7 +314,12 @@ storiesOf('Level', module)
281314
}
282315
return (
283316
<Level
284-
level={level}
317+
menu={menu}
318+
title={level.title}
319+
content={level.content}
320+
index={0}
321+
status={level.status}
322+
steps={level.steps}
285323
onContinue={action('onContinue')}
286324
onLoadSolution={action('onLoadSolution')}
287325
processes={[]}
@@ -297,12 +335,17 @@ storiesOf('Level', module)
297335
description: 'A summary of the level',
298336
content: 'Some content here in markdown',
299337
setup: null,
300-
status: 'ACTIVE',
338+
status: 'ACTIVE' as 'ACTIVE',
301339
steps: [],
302340
}
303341
return (
304342
<Level
305-
level={level}
343+
menu={menu}
344+
title={level.title}
345+
content={level.content}
346+
index={0}
347+
status={level.status}
348+
steps={level.steps}
306349
onContinue={action('onContinue')}
307350
onLoadSolution={action('onLoadSolution')}
308351
processes={[]}

0 commit comments

Comments
 (0)
Please sign in to comment.