1+ import * as G from 'typings/graphql'
2+ import * as CR from 'typings'
3+
4+ class Tutorial {
5+ public repo :G . TutorialRepo
6+ public config :{ codingLanguage :G . EnumCodingLanguage , testRunner :G . EnumTestRunner }
7+ private version :G . TutorialVersion
8+ private position :CR . Position
9+ private progress :CR . Progress
10+
11+ constructor ( tutorial :G . Tutorial ) {
12+ this . repo = tutorial . repo
13+ this . config = {
14+ codingLanguage :tutorial . codingLanguage ,
15+ testRunner :tutorial . testRunner ,
16+ }
17+ // TODO: allow specific version, currently defaults to latest
18+ this . version = tutorial . version
19+ // set initial position
20+ this . position = {
21+ levelId :this . version . levels [ 0 ] . id ,
22+ stageId :this . version . levels [ 0 ] . stages [ 0 ] . id ,
23+ stepId :this . version . levels [ 0 ] . stages [ 0 ] . steps [ 0 ] . id ,
24+ }
25+ // set empty initial progress
26+ this . progress = {
27+ levels :{ } ,
28+ stages :{ } ,
29+ steps :{ } ,
30+ complete :false ,
31+ }
32+ }
33+ public load = ( ) => {
34+ //
35+ }
36+ public level = ( levelId :string ) :G . Level | null => {
37+ const level :G . Level | undefined = this . version . levels . find ( ( l :G . Level ) => l . id === levelId )
38+ if ( ! level ) {
39+ console . warn ( `LevelId not found:${ levelId } ` )
40+ return null
41+ }
42+ return level
43+ }
44+ public stage = ( stageId :string ) :G . Stage | null => {
45+ const level :G . Level | null = this . level ( this . position . levelId )
46+ if ( ! level ) {
47+ return null
48+ }
49+ const stage :G . Stage | undefined = level . stages . find ( ( s :G . Stage ) => s . id === stageId )
50+ if ( ! stage ) {
51+ console . warn ( `StageId not found:${ stageId } ` )
52+ return null
53+ }
54+ return stage
55+ }
56+ public step = ( stepId :string ) :G . Step | null => {
57+ const stage :G . Stage | null = this . stage ( this . position . stageId )
58+ if ( ! stage ) {
59+ return null
60+ }
61+ const step :G . Step | undefined = stage . steps . find ( ( s :G . Step ) => s . id === stepId )
62+ if ( ! step ) {
63+ console . warn ( `StepId not found:${ stepId } ` )
64+ return null
65+ }
66+ return step
67+ }
68+ public updateProgress = ( ) :{ position :CR . Position , progress :CR . Progress } => {
69+ const { levelId, stageId, stepId} = this . position
70+ this . progress . levels [ levelId ] = true
71+ this . progress . stages [ stageId ] = true
72+ this . progress . steps [ stepId ] = true
73+ return {
74+ position :this . position ,
75+ progress :this . progress ,
76+ }
77+ }
78+ public nextPosition = ( ) :CR . Position => {
79+ const { levelId, stageId, stepId} = this . position
80+ // TODO: calculate and return next position
81+
82+ // is next step
83+ const stage :G . Stage | null = this . stage ( stageId )
84+ if ( ! stage ) {
85+ throw new Error ( 'Stage not found' )
86+ }
87+ const { steps} = stage
88+ const indexOfStep = steps . findIndex ( ( s :G . Step ) :boolean => s . id === stepId )
89+ if ( indexOfStep === - 1 ) {
90+ throw new Error ( 'Step not found' )
91+ }
92+ if ( indexOfStep < steps . length - 1 ) {
93+
94+ return {
95+ levelId,
96+ stageId,
97+ stepId :steps [ indexOfStep + 1 ] . id
98+ }
99+ }
100+
101+ // is next stage
102+ const level :G . Level | null = this . level ( levelId )
103+ if ( ! level ) {
104+ throw new Error ( 'Level not found' )
105+ }
106+ const { stages} = level
107+ const indexOfStage = stages . findIndex ( ( s :G . Stage ) :boolean => s . id === stageId )
108+ if ( indexOfStage === - 1 ) {
109+ throw new Error ( 'Stage not found' )
110+ }
111+ if ( indexOfStage < stages . length - 1 ) {
112+ // next stage
113+ const nextStage = stages [ indexOfStage + 1 ]
114+ return {
115+ levelId,
116+ stageId :nextStage . id ,
117+ stepId :nextStage . steps [ 0 ] . id
118+ }
119+ }
120+
121+ // is next level
122+ const levels = this . version . levels
123+ const indexOfLevel = levels . findIndex ( ( l :G . Level ) :boolean => l . id === levelId )
124+ if ( indexOfLevel === - 1 ) {
125+ throw new Error ( 'Level not found' )
126+ }
127+ if ( indexOfLevel < levels . length - 1 ) {
128+ const nextLevel = levels [ indexOfLevel + 1 ]
129+ const nextStage = nextLevel . stages [ 0 ]
130+ return {
131+ levelId :nextLevel . id ,
132+ stageId :nextStage . id ,
133+ stepId :nextStage . steps [ 0 ] . id ,
134+ }
135+ }
136+
137+ throw new Error ( 'Could not calculate next position' )
138+ }
139+ }
140+
141+ export default Tutorial