11import { LessonEngine } from "./impl/LessonEngine.js" ;
22import { renderLesson , renderModuleList , renderLevelIndicator , showFeedback , updateActiveLessonInSidebar } from "./helpers/renderer.js" ;
3- import { validateUserCode } from "./helpers/validator.js" ;
43import { loadModules } from "./config/lessons.js" ;
54
6- // Main Application state
5+ // Simplified state - LessonEngine now manages lesson state and progress
76const state = {
8- currentModule : null ,
9- currentLessonIndex : 0 ,
10- modules : [ ] ,
11- userCode : new Map ( ) , // Store user code for each lesson
12- userProgress : { } , // Format: { moduleId: { completed: [0, 2, 3], current: 4 } }
13- userCodeBeforeValidation : "" , // Track user code state before validation
147 userSettings : {
158 disableFeedbackErrors : false
169 }
@@ -44,7 +37,7 @@ const elements = {
4437 disableFeedbackToggle : document . getElementById ( "disable-feedback-toggle" )
4538} ;
4639
47- // Initialize the lesson engine
40+ // Initialize the lesson engine - now the single source of truth
4841const lessonEngine = new LessonEngine ( ) ;
4942
5043// Load user progress from localStorage
@@ -89,17 +82,20 @@ function initFeedbackToggle() {
8982// Initialize the module list
9083async function initializeModules ( ) {
9184 try {
92- state . modules = await loadModules ( ) ;
85+ const modules = await loadModules ( ) ;
86+ lessonEngine . setModules ( modules ) ;
9387
9488 // Use the new renderModuleList function with both callbacks
95- renderModuleList ( elements . moduleList , state . modules , selectModule , selectLesson ) ;
89+ renderModuleList ( elements . moduleList , modules , selectModule , selectLesson ) ;
9690
97- // Select the first module or the last one user was on
98- const lastModuleId = localStorage . getItem ( "codeCrispies.lastModuleId" ) ;
99- if ( lastModuleId && state . modules . find ( ( m ) => m . id === lastModuleId ) ) {
91+ // Load saved progress and select appropriate module
92+ const progressData = lessonEngine . loadUserProgress ( ) ;
93+ const lastModuleId = progressData ?. lastModuleId ;
94+
95+ if ( lastModuleId && modules . find ( m => m . id === lastModuleId ) ) {
10096 selectModule ( lastModuleId ) ;
101- } else if ( state . modules . length > 0 ) {
102- selectModule ( state . modules [ 0 ] . id ) ;
97+ } else if ( modules . length > 0 ) {
98+ selectModule ( modules [ 0 ] . id ) ;
10399 }
104100
105101 // Update progress indicator on module selector button
@@ -112,21 +108,7 @@ async function initializeModules() {
112108
113109// Update progress indicator on module selector button
114110function updateModuleSelectorButtonProgress ( ) {
115- if ( ! state . modules . length ) return ;
116-
117- // Calculate overall progress across all modules
118- let totalLessons = 0 ;
119- let totalCompleted = 0 ;
120-
121- state . modules . forEach ( ( module ) => {
122- totalLessons += module . lessons . length ;
123- const progress = state . userProgress [ module . id ] ;
124- if ( progress && progress . completed ) {
125- totalCompleted += progress . completed . length ;
126- }
127- } ) ;
128-
129- const percentComplete = totalLessons > 0 ? Math . round ( ( totalCompleted / totalLessons ) * 100 ) : 0 ;
111+ const stats = lessonEngine . getProgressStats ( ) ;
130112
131113 // Create progress indicator
132114 const progressBar = document . createElement ( "div" ) ;
@@ -136,13 +118,13 @@ function updateModuleSelectorButtonProgress() {
136118 bottom: 0;
137119 left: 0;
138120 height: 3px;
139- width: ${ percentComplete } %;
121+ width: ${ stats . percentComplete } %;
140122 background-color: var(--primary-light);
141123 border-radius: 0 3px 3px 0;
142124 ` ;
143125
144126 // Add progress percentage text
145- elements . moduleSelectorBtn . innerHTML = `Progress <span style="font-size: 0.8em; opacity: 0.8;">${ percentComplete } %</span>` ;
127+ elements . moduleSelectorBtn . innerHTML = `Progress <span style="font-size: 0.8em; opacity: 0.8;">${ stats . percentComplete } %</span>` ;
146128 elements . moduleSelectorBtn . style . position = "relative" ;
147129
148130 // Remove any existing progress bar before adding new one
@@ -154,12 +136,10 @@ function updateModuleSelectorButtonProgress() {
154136 elements . moduleSelectorBtn . appendChild ( progressBar ) ;
155137}
156138
157- // Select a module
139+ // Select a module - delegate to LessonEngine
158140function selectModule ( moduleId ) {
159- const selectedModule = state . modules . find ( ( module ) => module . id === moduleId ) ;
160- if ( ! selectedModule ) return ;
161-
162- state . currentModule = selectedModule ;
141+ const success = lessonEngine . setModuleById ( moduleId ) ;
142+ if ( ! success ) return ;
163143
164144 // Update module list UI to highlight the active module
165145 const moduleItems = elements . moduleList . querySelectorAll ( ".module-header" ) ;
@@ -170,35 +150,21 @@ function selectModule(moduleId) {
170150 }
171151 } ) ;
172152
173- // Load user progress for this module
174- if ( ! state . userProgress [ moduleId ] ) {
175- state . userProgress [ moduleId ] = { completed : [ ] , current : 0 } ;
176- }
177-
178- state . currentLessonIndex = state . userProgress [ moduleId ] . current || 0 ;
179153 loadCurrentLesson ( ) ;
180154
181- // Save the last selected module
182- localStorage . setItem ( "codeCrispies.lastModuleId" , moduleId ) ;
183-
184155 // Reset any success indicators
185156 resetSuccessIndicators ( ) ;
186157}
187158
188159function selectLesson ( moduleId , lessonIndex ) {
189160 // Select the module first if it's not already selected
190- if ( ! state . currentModule || state . currentModule . id !== moduleId ) {
191- selectModule ( moduleId ) ;
161+ const currentState = lessonEngine . getCurrentState ( ) ;
162+ if ( ! currentState . module || currentState . module . id !== moduleId ) {
163+ lessonEngine . setModuleById ( moduleId ) ;
192164 }
193165
194- // Update current lesson index
195- state . currentLessonIndex = lessonIndex ;
196-
197- // Update user progress
198- state . userProgress [ moduleId ] . current = lessonIndex ;
199- saveUserProgress ( ) ;
200-
201- // Load the lesson
166+ // Set the lesson
167+ lessonEngine . setLessonByIndex ( lessonIndex ) ;
202168 loadCurrentLesson ( ) ;
203169}
204170
@@ -229,22 +195,16 @@ function resetEditorLayout(lesson) {
229195 elements . validationIndicators . innerHTML = "" ;
230196}
231197
232- // Load the current lesson
198+ // Load the current lesson - now delegates to LessonEngine
233199function loadCurrentLesson ( ) {
234- if ( ! state . currentModule || ! state . currentModule . lessons ) {
235- return ;
236- }
200+ const engineState = lessonEngine . getCurrentState ( ) ;
237201
238- // Make sure lesson index is in bounds
239- if ( state . currentLessonIndex >= state . currentModule . lessons . length ) {
240- state . currentLessonIndex = state . currentModule . lessons . length - 1 ;
241- } else if ( state . currentLessonIndex < 0 ) {
242- state . currentLessonIndex = 0 ;
202+ if ( ! engineState . module || ! engineState . lesson ) {
203+ return ;
243204 }
244205
245- const lesson = state . currentModule . lessons [ state . currentLessonIndex ] ;
246- const mode = lesson . mode || state . currentModule ?. mode || "css" ;
247- lessonEngine . setLesson ( lesson ) ;
206+ const lesson = engineState . lesson ;
207+ const mode = lesson . mode || engineState . module ?. mode || "css" ;
248208
249209 // Update UI based on mode
250210 updateEditorForMode ( mode ) ;
@@ -264,12 +224,14 @@ function loadCurrentLesson() {
264224 lesson
265225 ) ;
266226
227+ // Set user code in input
228+ elements . codeInput . value = engineState . userCode ;
229+
267230 // Configure editor layout based on lesson settings
268231 resetEditorLayout ( lesson ) ;
269232
270233 // Update Run button text based on completion status
271- const moduleProgress = state . userProgress [ state . currentModule . id ] ;
272- if ( moduleProgress && moduleProgress . completed . includes ( state . currentLessonIndex ) ) {
234+ if ( engineState . isCompleted ) {
273235 elements . runBtn . innerHTML = '<img src="./gear.svg" />Re-run' ;
274236
275237 // Add completion badge next to title if not already present
@@ -290,27 +252,20 @@ function loadCurrentLesson() {
290252 }
291253
292254 // Update level indicator
293- renderLevelIndicator ( elements . levelIndicator , state . currentLessonIndex + 1 , state . currentModule . lessons . length ) ;
255+ renderLevelIndicator ( elements . levelIndicator , engineState . lessonIndex + 1 , engineState . totalLessons ) ;
294256
295257 // Update active lesson in sidebar
296- updateActiveLessonInSidebar ( state . currentModule . id , state . currentLessonIndex ) ;
258+ updateActiveLessonInSidebar ( engineState . module . id , engineState . lessonIndex ) ;
297259
298260 // Update navigation buttons
299261 updateNavigationButtons ( ) ;
300262
301- // Save current progress
302- state . userProgress [ state . currentModule . id ] . current = state . currentLessonIndex ;
303- saveUserProgress ( ) ;
304-
305263 // Update progress indicator on module selector button
306264 updateModuleSelectorButtonProgress ( ) ;
307265
308266 // Focus on the code editor by default
309267 elements . codeInput . focus ( ) ;
310268
311- // Store current code
312- state . userCodeBeforeValidation = elements . codeInput . value ;
313-
314269 // Track live changes and update preview when the user pauses typing
315270 setupLivePreview ( ) ;
316271}
@@ -334,19 +289,16 @@ function handleUserInput() {
334289
335290 // Set a new timer for preview update after user stops typing
336291 previewTimer = setTimeout ( ( ) => {
337- // Apply the code for preview without validation
338- // lessonEngine.applyUserCode(elements.codeInput.value);
339292 runCode ( ) ;
340- } , 800 ) ; // Update preview 500ms after user stops typing
341-
342- // Store current code state
343- state . userCodeBeforeValidation = elements . codeInput . value ;
293+ } , 800 ) ; // Update preview 800ms after user stops typing
344294}
345295
346296// Update navigation buttons state
347297function updateNavigationButtons ( ) {
348- elements . prevBtn . disabled = state . currentLessonIndex === 0 ;
349- elements . nextBtn . disabled = ! state . currentModule || state . currentLessonIndex === state . currentModule . lessons . length - 1 ;
298+ const engineState = lessonEngine . getCurrentState ( ) ;
299+
300+ elements . prevBtn . disabled = ! engineState . canGoPrev ;
301+ elements . nextBtn . disabled = ! engineState . canGoNext ;
350302
351303 // Style changes for disabled buttons
352304 if ( elements . prevBtn . disabled ) {
@@ -362,42 +314,36 @@ function updateNavigationButtons() {
362314 }
363315}
364316
365- // Go to the next lesson
317+ // Go to the next lesson - delegate to LessonEngine
366318function nextLesson ( ) {
367- if ( ! state . currentModule ) return ;
368-
369- if ( state . currentLessonIndex < state . currentModule . lessons . length - 1 ) {
370- state . currentLessonIndex ++ ;
319+ const success = lessonEngine . nextLesson ( ) ;
320+ if ( success ) {
371321 loadCurrentLesson ( ) ;
372322 }
373323}
374324
375- // Go to the previous lesson
325+ // Go to the previous lesson - delegate to LessonEngine
376326function prevLesson ( ) {
377- if ( state . currentLessonIndex > 0 ) {
378- state . currentLessonIndex -- ;
327+ const success = lessonEngine . previousLesson ( ) ;
328+ if ( success ) {
379329 loadCurrentLesson ( ) ;
380330 }
381331}
382332
383- // Run the user code
333+ // Run the user code - now uses LessonEngine validation
384334function runCode ( ) {
385335 const userCode = elements . codeInput . value ;
386- const lesson = state . currentModule . lessons [ state . currentLessonIndex ] ;
387336
388337 // Rotate the Run button icon
389338 const runButtonImg = document . querySelector ( "#run-btn img" ) ;
390339 const runButtonRotationDegree = Number ( runButtonImg . style . transform . match ( / \d + / ) ?. pop ( ) ?? 0 ) ;
391340 document . querySelector ( "#run-btn img" ) . style . transform = `rotate(${ runButtonRotationDegree + 180 } deg)` ;
392341
393- // Always apply the code to the preview, regardless of validation result
342+ // Apply the code to the preview via LessonEngine
394343 lessonEngine . applyUserCode ( userCode , true ) ;
395344
396- // Backup code in local storage
397- state . userCode . set ( state . currentLessonIndex , userCode ) ;
398- localStorage . setItem ( "codeCrispies.userCode" , JSON . stringify ( Array . from ( state . userCode . entries ( ) ) ) ) ;
399-
400- const validationResult = validateUserCode ( userCode , lesson ) ;
345+ // Validate code using LessonEngine
346+ const validationResult = lessonEngine . validateCode ( ) ;
401347
402348 // Add validation indicators based on validCases count if available
403349 if ( validationResult . validCases ) {
@@ -412,18 +358,10 @@ function runCode() {
412358 }
413359
414360 if ( validationResult . isValid ) {
415- // Mark lesson as completed
416- const moduleProgress = state . userProgress [ state . currentModule . id ] ;
417- if ( ! moduleProgress . completed . includes ( state . currentLessonIndex ) ) {
418- moduleProgress . completed . push ( state . currentLessonIndex ) ;
419- saveUserProgress ( ) ;
420- updateModuleSelectorButtonProgress ( ) ;
421- }
422-
423361 // Show success feedback with visual indicators
424362 showFeedback ( true , validationResult . message || "Great job! Your code works correctly." ) ;
425363
426- // Add this block to update the Run button to Re-run
364+ // Update the Run button to Re-run
427365 elements . runBtn . innerHTML = '<img src="./gear.svg" />Re-run' ;
428366 elements . runBtn . classList . add ( "re-run" ) ;
429367
@@ -441,11 +379,11 @@ function runCode() {
441379 elements . nextBtn . classList . add ( "success" ) ;
442380 elements . taskInstruction . classList . add ( "success-instruction" ) ;
443381
444- // Enable the next button if not already on the last lesson
445- if ( state . currentLessonIndex < state . currentModule . lessons . length - 1 ) {
446- elements . nextBtn . disabled = false ;
447- elements . nextBtn . classList . remove ( "btn-disabled" ) ;
448- }
382+ // Update navigation buttons
383+ updateNavigationButtons ( ) ;
384+
385+ // Update progress indicator
386+ updateModuleSelectorButtonProgress ( ) ;
449387 } else {
450388 // Reset any success indicators
451389 resetSuccessIndicators ( ) ;
@@ -459,8 +397,11 @@ function runCode() {
459397function showModuleSelector ( ) {
460398 elements . modalTitle . textContent = "Select a Module" ;
461399
400+ const engineState = lessonEngine . getCurrentState ( ) ;
401+ const modules = lessonEngine . modules ;
402+
462403 // Create module buttons
463- const moduleButtons = state . modules . map ( ( module ) => {
404+ const moduleButtons = modules . map ( ( module ) => {
464405 const button = document . createElement ( "button" ) ;
465406 button . classList . add ( "btn" , "module-button" ) ;
466407 button . style . display = "block" ;
@@ -469,9 +410,8 @@ function showModuleSelector() {
469410 button . style . padding = "15px" ;
470411 button . style . textAlign = "left" ;
471412
472- // Add completion status
473- const progress = state . userProgress [ module . id ] ;
474- const completedCount = progress ? progress . completed . length : 0 ;
413+ // Add completion status using LessonEngine
414+ const completedCount = lessonEngine . userProgress [ module . id ] ?. completed . length || 0 ;
475415 const totalLessons = module . lessons . length ;
476416 const percentComplete = Math . round ( ( completedCount / totalLessons ) * 100 ) ;
477417
0 commit comments