From 6fe1e2b4d966913bbb67a00c2405591d74af81f5 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Tue, 13 May 2025 19:57:27 +0200 Subject: [PATCH] feat: enhance lesson progress tracking and UI feedback --- src/index.html | 1 + src/js/app.js | 107 ++++++++++++++++++++++++++- src/js/renderer.js | 2 +- src/lessons/configs/tailwindcss.json | 0 src/styles/main.css | 12 +++ 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/lessons/configs/tailwindcss.json diff --git a/src/index.html b/src/index.html index 3b93c21..4c5ff30 100644 --- a/src/index.html +++ b/src/index.html @@ -2,6 +2,7 @@ + CODE CRISPIES - Learn CSS Interactively diff --git a/src/js/app.js b/src/js/app.js index 98b01a8..ee89c2c 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -32,6 +32,7 @@ const elements = { moduleSelectorBtn: document.getElementById('module-selector-btn'), resetBtn: document.getElementById('reset-btn'), helpBtn: document.getElementById('help-btn'), + lessonContainer: document.querySelector('.lesson-container'), }; // Initialize the lesson engine @@ -63,12 +64,59 @@ async function initializeModules() { } else if (state.modules.length > 0) { selectModule(state.modules[0].id); } + + // Update progress indicator on module selector button + updateModuleSelectorButtonProgress(); } catch (error) { console.error('Failed to load modules:', error); elements.lessonDescription.textContent = 'Failed to load modules. Please refresh the page.'; } } +// Update progress indicator on module selector button +function updateModuleSelectorButtonProgress() { + if (!state.modules.length) return; + + // Calculate overall progress across all modules + let totalLessons = 0; + let totalCompleted = 0; + + state.modules.forEach(module => { + totalLessons += module.lessons.length; + const progress = state.userProgress[module.id]; + if (progress && progress.completed) { + totalCompleted += progress.completed.length; + } + }); + + const percentComplete = totalLessons > 0 ? Math.round((totalCompleted / totalLessons) * 100) : 0; + + // Create progress indicator + const progressBar = document.createElement('div'); + progressBar.className = 'progress-indicator'; + progressBar.style.cssText = ` + position: absolute; + bottom: 0; + left: 0; + height: 3px; + width: ${percentComplete}%; + background-color: var(--success-color); + border-radius: 0 3px 3px 0; + `; + + // Add progress percentage text + elements.moduleSelectorBtn.innerHTML = `Progress ${percentComplete}%`; + elements.moduleSelectorBtn.style.position = 'relative'; + + // Remove any existing progress bar before adding new one + const existingBar = elements.moduleSelectorBtn.querySelector('.progress-indicator'); + if (existingBar) { + existingBar.remove(); + } + + elements.moduleSelectorBtn.appendChild(progressBar); +} + // Select a module function selectModule(moduleId) { const selectedModule = state.modules.find(module => module.id === moduleId); @@ -95,6 +143,17 @@ function selectModule(moduleId) { // Save the last selected module localStorage.setItem('lastModuleId', moduleId); + + // Reset any success indicators + resetSuccessIndicators(); +} + +// Reset success indicators +function resetSuccessIndicators() { + elements.lessonContainer.classList.remove('success-highlight'); + elements.lessonTitle.classList.remove('success-text'); + const headings = elements.lessonContainer.querySelectorAll('h2, h3, h4'); + headings.forEach(heading => heading.classList.remove('success-text')); } // Load the current lesson @@ -113,6 +172,9 @@ function loadCurrentLesson() { const lesson = state.currentModule.lessons[state.currentLessonIndex]; lessonEngine.setLesson(lesson); + // Reset any success indicators + resetSuccessIndicators(); + // Update UI renderLesson( elements.lessonTitle, @@ -138,6 +200,9 @@ function loadCurrentLesson() { // Save current progress state.userProgress[state.currentModule.id].current = state.currentLessonIndex; saveUserProgress(); + + // Update progress indicator on module selector button + updateModuleSelectorButtonProgress(); } // Update navigation buttons state @@ -191,11 +256,18 @@ function runCode() { if (!moduleProgress.completed.includes(state.currentLessonIndex)) { moduleProgress.completed.push(state.currentLessonIndex); saveUserProgress(); + updateModuleSelectorButtonProgress(); } - // Show success feedback + // Show success feedback with visual indicators showFeedback(true, validationResult.message || 'Great job! Your code works correctly.'); + // Add success visual indicators + elements.lessonContainer.classList.add('success-highlight'); + elements.lessonTitle.classList.add('success-text'); + const headings = elements.lessonContainer.querySelectorAll('h3, h4'); + headings.forEach(heading => heading.classList.add('success-text')); + // Apply the code to see the result lessonEngine.applyUserCode(userCode); @@ -205,8 +277,11 @@ function runCode() { elements.nextBtn.classList.remove('btn-disabled'); } } else { - // Show error feedback - showFeedback(false, validationResult.message || 'Your code doesn\'t seem to be correct. Try again!'); + // Reset any success indicators + resetSuccessIndicators(); + + // Show error feedback (with friendly message) + showFeedback(false, validationResult.message || 'Not quite there yet! Let\'s try again.'); } } @@ -285,7 +360,7 @@ function showHelp() { @@ -294,6 +369,8 @@ function showHelp() {
  • Use the preview area to see how your CSS affects the elements
  • Your progress is automatically saved in your browser storage
  • You can revisit completed lessons at any time
  • +
  • Press Tab in the code editor to indent with two spaces
  • +
  • Use Ctrl+Enter to quickly run your code
  • `; @@ -326,6 +403,9 @@ function resetProgress() { } else if (state.modules.length > 0) { selectModule(state.modules[0].id); } + + // Update progress indicator + updateModuleSelectorButtonProgress(); }); elements.modalContainer.classList.remove('hidden'); @@ -336,6 +416,22 @@ function closeModal() { elements.modalContainer.classList.add('hidden'); } +// Handle tab key in the code editor +function handleTabKey(e) { + if (e.key === 'Tab') { + e.preventDefault(); + + const start = e.target.selectionStart; + const end = e.target.selectionEnd; + + // Add two spaces at cursor position + e.target.value = e.target.value.substring(0, start) + ' ' + e.target.value.substring(end); + + // Move cursor position after the inserted spaces + e.target.selectionStart = e.target.selectionEnd = start + 2; + } +} + // Initialize the application function init() { loadUserProgress(); @@ -350,6 +446,9 @@ function init() { elements.resetBtn.addEventListener('click', resetProgress); elements.helpBtn.addEventListener('click', showHelp); + // Add tab key handler for the code input + elements.codeInput.addEventListener('keydown', handleTabKey); + // Handle keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl+Enter to run code diff --git a/src/js/renderer.js b/src/js/renderer.js index be8655e..60e7896 100644 --- a/src/js/renderer.js +++ b/src/js/renderer.js @@ -9,7 +9,7 @@ let feedbackElement = null; * Render the module list in the sidebar * @param {HTMLElement} container - The container element for the module list * @param {Array} modules - The list of modules - * @param {Function} onSelectModule - Callback when a module is selected + * @param { Function} onSelectModule - Callback when a module is selected */ export function renderModuleList(container, modules, onSelectModule) { // Clear the container diff --git a/src/lessons/configs/tailwindcss.json b/src/lessons/configs/tailwindcss.json new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/main.css b/src/styles/main.css index 9338fef..9638c91 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -107,10 +107,16 @@ body { /* Lesson Container */ .lesson-container { + display: flex; + flex-flow: column; + align-items: stretch; + justify-content: flex-start; + gap: 2rem; background-color: var(--panel-bg); border-radius: 8px; box-shadow: var(--shadow); padding: 2rem; + height: 100%; margin-bottom: 2rem; } @@ -128,6 +134,7 @@ body { /* Challenge Container */ .challenge-container { display: flex; + flex: 1; flex-direction: column; gap: 1.5rem; margin-bottom: 2rem; @@ -216,6 +223,11 @@ code { /* Controls */ .controls { + /*justify-self: end;*/ + /*position: absolute;*/ + /*left: 2rem;*/ + /*right: 2rem;*/ + /*bottom: 2rem;*/ display: flex; justify-content: space-between; align-items: center;