Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit80eee1b

Browse files
committed
fix save/continue routing, improve "loadNext"
1 parented19795 commit80eee1b

File tree

5 files changed

+239
-35
lines changed

5 files changed

+239
-35
lines changed

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,7 @@ const StageSummaryPageContainer = (props: PageProps) => {
4545
})
4646
stage.status=progress.stages[position.stageId] ?'COMPLETE' :'ACTIVE'
4747

48-
console.log('stage.status',stage.status)
49-
5048
return<Stagestage={stage}onContinue={onContinue}onSave={onSave}/>
5149
}
5250

5351
exportdefaultStageSummaryPageContainer
54-
55-
/*
56-
const formattedStage = {
57-
...stage,
58-
steps: stage.steps.map((step: G.Step) => {
59-
if (step.id === position.stepId) {
60-
return {
61-
...step,
62-
status: 'ACTIVE'
63-
}
64-
} else if (progress.steps[step.id]) {
65-
return {
66-
...step,
67-
status: 'COMPLETE'
68-
}
69-
} else {
70-
return {
71-
...step,
72-
status: 'INCOMPLETE'
73-
}
74-
}
75-
})
76-
*/

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

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import{assign}from'xstate'
1+
import{assign,send}from'xstate'
22
import*asGfrom'typings/graphql'
33
import*asCRfrom'typings'
44
import*asstoragefrom'./storage'
@@ -42,10 +42,15 @@ export default {
4242
.stages.find((s:G.Stage)=>s.id===position.stageId)
4343
.steps
4444

45+
46+
// final step but not completed
47+
if(steps[steps.length-1].id===position.stepId){
48+
returnposition
49+
}
50+
4551
conststepIndex=steps.findIndex((s:G.Step)=>s.id===position.stepId)
46-
conststep:G.Step=steps[stepIndex+1]
4752

48-
console.log('step load next',step.id,position.stepId)
53+
conststep:G.Step=steps[stepIndex+1]
4954

5055
constnextPosition:CR.Position={
5156
...position,
@@ -139,6 +144,82 @@ export default {
139144
},
140145
}),
141146
//@ts-ignore
147+
updatePosition:assign({
148+
position:(context:CR.MachineContext,event:CR.MachineEvent):CR.Progress=>{
149+
console.log('updatePosition event',event)
150+
const{position}=event.payload
151+
returnposition
152+
}
153+
}),
154+
loadNext:send((context:CR.MachineContext):CR.Action=>{
155+
const{tutorial, position, progress}=context
156+
157+
// has next step?
158+
constlevels:G.Level[]=tutorial.version.levels
159+
constlevel:G.Level|undefined=levels.find((l:G.Level)=>l.id===position.levelId)
160+
if(!level){
161+
thrownewError('No Level found')
162+
}
163+
conststages:G.Stage[]=level.stages
164+
conststage:G.Stage|undefined=stages.find((s:G.Stage)=>s.id===position.stageId)
165+
if(!stage){
166+
thrownewError('No Stage found')
167+
}
168+
conststeps:G.Step[]=stage.steps
169+
170+
conststepIndex=steps.findIndex((s:G.Step)=>s.id===position.stepId)
171+
conststepComplete=progress.steps[position.stepId]
172+
constfinalStep=(stepIndex>-1&&stepIndex===steps.length-1)
173+
consthasNextStep=(!finalStep&&!stepComplete)
174+
175+
176+
// NEXT STEP
177+
if(hasNextStep){
178+
constnextPosition={...position,stepId:steps[stepIndex+1].id}
179+
return{type:'NEXT_STEP',payload:{position:nextPosition}}
180+
}
181+
182+
// has next stage?
183+
184+
conststageIndex=stages.findIndex((s:G.Stage)=>s.id===position.stageId)
185+
conststageComplete=progress.stages[position.stageId]
186+
constfinalStage=(stageIndex>-1&&stageIndex===stages.length-1)
187+
consthasNextStage=(!finalStage)
188+
189+
// NEXT STAGE
190+
if(hasNextStage){
191+
constnextStage=stages[stageIndex+1]
192+
constnextPosition={
193+
levelId:position.levelId,
194+
stageId:nextStage.id,
195+
stepId:nextStage.steps[0].id,
196+
}
197+
return{type:'NEXT_STAGE',payload:{position:nextPosition}}
198+
}
199+
200+
// has next level?
201+
202+
constlevelIndex=levels.findIndex((l:G.Level)=>l.id===position.levelId)
203+
constlevelComplete=progress.levels[position.levelId]
204+
constfinalLevel=(levelIndex>-1&&levelIndex===levels.length-1)
205+
consthasNextLevel=(!finalLevel)
206+
207+
// NEXT LEVEL
208+
if(hasNextLevel){
209+
constnextLevel=levels[levelIndex+1]
210+
constnextStage=nextLevel.stages[0]
211+
constnextPosition={
212+
levelId:nextLevel.id,
213+
stageId:nextStage.id,
214+
stepId:nextStage.steps[0].id,
215+
}
216+
return{type:'NEXT_LEVEL',payload:{position:nextPosition}}
217+
}
218+
219+
// COMPLETED
220+
return{type:'COMPLETED'}
221+
}),
222+
//@ts-ignore
142223
reset:assign({
143224
tutorial(){
144225
storage.tutorial.set(null)

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ export default {
6363
// // tutorialModel.updateProgress()
6464
// // tutorialModel.nextPosition()
6565
// },
66-
// loadLevel(): void {
67-
// // tutorialModel.triggerCurrent('LEVEL')
68-
// },
66+
6967
// stageLoadNext() {
7068
// console.log('stageLoadNext')
7169
// // tutorialModel.nextPosition()

‎web-app/src/services/state/guards/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,30 @@ import * as CR from 'typings'
44
// // TODO: refactor into a single calculation
55
exportdefault{
66
hasNextStep:(context:CR.MachineContext):boolean=>{
7-
const{tutorial, position}=context
7+
const{tutorial, position, progress}=context
88
// TODO: protect against errors
99
conststeps:G.Step[]=tutorial.version
1010
.levels.find((l:G.Level)=>l.id===position.levelId)
1111
.stages.find((s:G.Stage)=>s.id===position.stageId)
1212
.steps
1313

1414
// TODO: verify not -1
15-
return!(steps[steps.length-1].id===position.stepId)
15+
constfinalStep=steps[steps.length-1].id===position.stepId
16+
conststepComplete=progress.steps[position.stepId]
17+
// not final step, or final step but not complete
18+
return!finalStep||!stepComplete
1619
},
1720
hasNextStage:(context:CR.MachineContext):boolean=>{
18-
const{tutorial, position}=context
21+
const{tutorial, position, progress}=context
1922
// TODO: protect against errors
2023
conststages:G.Stage[]=tutorial.version
2124
.levels.find((l:G.Level)=>l.id===position.levelId)
2225
.stages
2326

2427
// TODO: verify not -1
25-
return!(stages[stages.length-1].id===position.stageId)
28+
constfinalStage=stages[stages.length-1].id===position.stageId
29+
conststageComplete=progress.stages[position.stageId]
30+
return!finalStage||!stageComplete
2631
},
2732
hasNextLevel:(context:CR.MachineContext):boolean=>{
2833
const{tutorial, position}=context
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import{useState,useRef,useEffect}from'react'
2+
import{
3+
interpret,
4+
EventObject,
5+
StateMachine,
6+
State,
7+
Interpreter,
8+
InterpreterOptions,
9+
MachineOptions
10+
}from'xstate'
11+
12+
interfaceUseMachineOptions<TContext>{
13+
/**
14+
* If provided, will be merged with machine's context.
15+
*/
16+
context?:Partial<TContext>
17+
/**
18+
* If `true`, service will start immediately (before mount).
19+
*/
20+
immediate:boolean
21+
}
22+
23+
constdefaultOptions={
24+
immediate:false
25+
}
26+
27+
exportfunctionuseMachine<TContext,TEventextendsEventObject>(
28+
machine:StateMachine<TContext,any,TEvent>,
29+
options:Partial<InterpreterOptions>&
30+
Partial<UseMachineOptions<TContext>>&
31+
Partial<MachineOptions<TContext,TEvent>>=defaultOptions
32+
):[
33+
State<TContext,TEvent>,
34+
Interpreter<TContext,any,TEvent>['send'],
35+
Interpreter<TContext,any,TEvent>
36+
]{
37+
const{
38+
context,
39+
guards,
40+
actions,
41+
activities,
42+
services,
43+
delays,
44+
immediate,
45+
...interpreterOptions
46+
}=options
47+
48+
constmachineConfig={
49+
context,
50+
guards,
51+
actions,
52+
activities,
53+
services,
54+
delays
55+
}
56+
57+
// Reference the machine
58+
constmachineRef=useRef<StateMachine<TContext,any,TEvent>|null>(null)
59+
60+
// Create the machine only once
61+
// See https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily
62+
if(machineRef.current===null){
63+
machineRef.current=machine.withConfig(machineConfig,{
64+
...machine.context,
65+
...context
66+
}asTContext)
67+
}
68+
69+
// Reference the service
70+
constserviceRef=useRef<Interpreter<TContext,any,TEvent>|null>(null)
71+
72+
// Create the service only once
73+
if(serviceRef.current===null){
74+
serviceRef.current=interpret(
75+
machineRef.current,
76+
interpreterOptions
77+
).onTransition(state=>{
78+
// Update the current machine state when a transition occurs
79+
if(state.changed){
80+
setCurrent(state)
81+
}
82+
})
83+
}
84+
85+
constservice=serviceRef.current
86+
87+
// Make sure actions are kept updated when they change.
88+
// This mutation assignment is safe because the service instance is only used
89+
// in one place -- this hook's caller.
90+
useEffect(()=>{
91+
Object.assign(service.machine.options.actions,actions)
92+
},[actions])
93+
94+
// Start service immediately (before mount) if specified in options
95+
if(immediate){
96+
service.start()
97+
}
98+
99+
// Keep track of the current machine state
100+
const[current,setCurrent]=useState(service.initialState)
101+
102+
useEffect(()=>{
103+
// Start the service when the component mounts.
104+
// Note: the service will start only if it hasn't started already.
105+
service.start()
106+
107+
return()=>{
108+
// Stop the service when the component unmounts
109+
service.stop()
110+
}
111+
},[])
112+
113+
return[current,service.send,service]
114+
}
115+
116+
exportfunctionuseService<TContext,TEventextendsEventObject>(
117+
service:Interpreter<TContext,any,TEvent>
118+
):[
119+
State<TContext,TEvent>,
120+
Interpreter<TContext,any,TEvent>['send'],
121+
Interpreter<TContext,any,TEvent>
122+
]{
123+
const[current,setCurrent]=useState(service.state)
124+
125+
useEffect(()=>{
126+
// Set to current service state as there is a possibility
127+
// of a transition occurring between the initial useState()
128+
// initialization and useEffect() commit.
129+
setCurrent(service.state)
130+
131+
constlistener=(state:State<TContext,TEvent>)=>{
132+
if(state.changed){
133+
setCurrent(state)
134+
}
135+
}
136+
137+
service.onTransition(listener)
138+
139+
return()=>{
140+
service.off(listener)
141+
}
142+
},[service])
143+
144+
return[current,service.send,service]
145+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp