@@ -2,7 +2,6 @@ import { Dynamic } from '@solidjs/web'
22import { mapAsync } from 'es-toolkit'
33import {
44 createMemo ,
5- createProjection ,
65 createStore ,
76 For ,
87 Loading ,
@@ -61,43 +60,41 @@ type Schema<
6160 } = any ,
6261> = ReturnType < typeof defineSchema < N , Q , T , S > >
6362
64- function Part < T extends Schema , K extends keyof T [ 'steps' ] , G extends boolean = false > (
63+ function Part < T extends Schema , K extends keyof T [ 'steps' ] , S extends boolean = false > (
6564 schema : T ,
6665 step : K ,
67- graded ?: G ,
66+ withState ?: S ,
6867) {
6968 const base = v . object ( {
7069 step : v . literal ( step as K ) ,
71- state : v . object ( schema . steps [ step ] . state as T [ 'steps' ] [ K ] [ 'state' ] ) ,
7270 } )
7371 const extended = v . object ( {
7472 ...base . entries ,
75- correct : v . boolean ( ) ,
76- score : v . strictTuple ( [ v . number ( ) , v . number ( ) ] ) ,
73+ state : v . object ( schema . steps [ step ] . state as T [ 'steps' ] [ K ] [ 'state' ] ) ,
7774 } )
78- return ( graded ? extended : base ) as G extends true ? typeof extended : typeof base
75+ return ( withState ? extended : base ) as S extends true ? typeof extended : typeof base
7976}
8077
81- type Part < T extends Schema , K extends keyof T [ 'steps' ] , G extends boolean = false > = Infer <
82- typeof Part < T , K , G >
78+ type Part < T extends Schema , K extends keyof T [ 'steps' ] , S extends boolean = false > = Infer <
79+ typeof Part < T , K , S >
8380>
8481
8582function PartUnion <
8683 T extends Schema ,
8784 K extends readonly ( keyof T [ 'steps' ] ) [ ] ,
88- G extends boolean = false ,
89- > ( schema : T , steps : K , graded ?: G ) {
85+ S extends boolean = false ,
86+ > ( schema : T , steps : K , withState ?: S ) {
9087 return v . variant (
9188 'step' ,
92- steps . map ( ( step ) => Part ( schema , step , graded ) ) ,
93- ) as v . VariantSchema < 'step' , { [ I in keyof K ] : ReturnType < typeof Part < T , K [ I ] , G > > } , undefined >
89+ steps . map ( ( step ) => Part ( schema , step , withState ) ) ,
90+ ) as v . VariantSchema < 'step' , { [ I in keyof K ] : ReturnType < typeof Part < T , K [ I ] , S > > } , undefined >
9491}
9592
9693type PartUnion <
9794 T extends Schema ,
9895 K extends readonly ( keyof T [ 'steps' ] ) [ ] = readonly ( keyof T [ 'steps' ] ) [ ] ,
99- G extends boolean = false ,
100- > = Infer < typeof PartUnion < T , K , G > >
96+ S extends boolean = false ,
97+ > = Infer < typeof PartUnion < T , K , S > >
10198
10299type Props < T extends Schema , K extends keyof T [ 'steps' ] , F extends boolean = true > = {
103100 question : InferFromShape < T [ 'question' ] >
@@ -127,20 +124,18 @@ export function buildSchemas<T extends Schema>(
127124 v . object ( {
128125 name : v . literal ( schema . name as T [ 'name' ] ) ,
129126 question : v . object ( schema . question as T [ 'question' ] ) ,
130- attempt : v . array ( PartUnion ( schema , steps , false ) ) ,
127+ attempt : v . array (
128+ v . union ( [ PartUnion ( schema , steps , true ) , PartUnion ( schema , steps , false ) ] ) ,
129+ ) ,
131130 } ) ,
132131 // TODO: calls need to be deduped, waiting for solid-router release?
133132 v . transformAsync ( async ( { attempt, question, ...exercise } ) => {
134- let modifiedAttempt : (
135- | Part < T , T [ 'steps' ] [ keyof T [ 'steps' ] ] , true >
136- | { step : keyof T [ 'steps' ] }
137- ) [ ] = attempt ?? [ ]
133+ let modifiedAttempt = attempt
138134 if ( attempt . length === 0 && schema . transform ) {
139135 question = await schema . transform ( question )
140- modifiedAttempt = [ { step : 'start' } ]
141136 }
142137 modifiedAttempt = await mapAsync ( modifiedAttempt , async ( part , i ) => {
143- if ( 'state' in part ) {
138+ if ( 'state' in part && part . state ) {
144139 const result = await feedback [ part . step ] ( {
145140 question : question ,
146141 state : part . state ,
@@ -150,6 +145,11 @@ export function buildSchemas<T extends Schema>(
150145 }
151146 return part
152147 } )
148+ if ( modifiedAttempt . length === 0 ) modifiedAttempt . push ( { step : 'start' } )
149+ const lastPart = modifiedAttempt . at ( - 1 )
150+ if ( lastPart && 'next' in lastPart && lastPart . next ) {
151+ modifiedAttempt . push ( { step : lastPart . next } )
152+ }
153153 return { ...exercise , question, attempt : modifiedAttempt }
154154 } ) ,
155155 ) ,
@@ -175,10 +175,11 @@ export function createView<T extends Schema>(
175175) {
176176 const { Student, grade } = buildSchemas ( schema , feedback )
177177 return function Component ( props : FinalViewProps < T > ) {
178- const exercise = createProjection ( async ( ) => grade ( await props . fetch ( ) ) )
178+ const exercise = createMemo ( async ( ) => grade ( await props . fetch ( ) ) )
179179 return (
180180 < Loading fallback = "Génération de l'exercice..." >
181- < For each = { exercise . attempt } >
181+ < p > { exercise ( ) . attempt . length } étapes</ p >
182+ < For each = { exercise ( ) . attempt } >
182183 { < K extends keyof T [ 'steps' ] > (
183184 part : ( ) =>
184185 | Part < T , K , true >
@@ -187,18 +188,19 @@ export function createView<T extends Schema>(
187188 ) => {
188189 const [ state , setState ] = createStore < Partial < Part < T , K > > > ( ( ) => part ( ) . state ?? { } )
189190 const validated = createMemo ( ( ) =>
190- v . safeParse ( Part ( schema , part ( ) . step ) , {
191+ v . safeParse ( Part ( schema , part ( ) . step , true ) , {
191192 ...part ( ) ,
192193 state,
193194 } ) ,
194195 )
195196 const submit = async ( ) => {
196197 if ( ! validated ( ) . success ) return
197198 await props . save ( {
198- ...exercise ,
199- attempt : exercise . attempt . map ( ( p , j ) =>
200- j === i ( ) ? { ...p , ...( validated ( ) . output as Part < T , K > ) } : p ,
201- ) ,
199+ ...exercise ( ) ,
200+ attempt : [
201+ ...exercise ( ) . attempt . toSpliced ( - 1 ) ,
202+ validated ( ) . output as Part < T , K , true > ,
203+ ] ,
202204 } )
203205 refresh ( ( ) => exercise )
204206 }
@@ -207,11 +209,9 @@ export function createView<T extends Schema>(
207209 < Dynamic
208210 component = { view [ part ( ) . step ] }
209211 { ...( {
210- question : exercise . question ,
212+ question : exercise ( ) . question ,
211213 state : part ( ) . state ,
212- previous : exercise . attempt . slice ( 0 , i ( ) ) . toReversed ( ) as any ,
213- correct : part ( ) . correct ,
214- score : part ( ) . score ,
214+ previous : exercise ( ) . attempt . slice ( 0 , i ( ) ) . toReversed ( ) as any ,
215215 } satisfies Props < T , K > as ComponentProps < View < T > [ K ] > ) }
216216 />
217217 < Show when = { ! part ( ) . state && validated ( ) . success } >
@@ -221,7 +221,6 @@ export function createView<T extends Schema>(
221221 )
222222 } }
223223 </ For >
224- < pre > { JSON . stringify ( { ...exercise } , null , 2 ) } </ pre >
225224 </ Loading >
226225 )
227226 }
0 commit comments