feat: enhance lesson progress tracking and UI feedback

This commit is contained in:
Michael Czechowski
2025-05-13 19:57:27 +02:00
parent e96ff203db
commit f49541d610
5 changed files with 117 additions and 5 deletions

View File

@@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="./public/favicon.ico" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CODE CRISPIES - Learn CSS Interactively</title> <title>CODE CRISPIES - Learn CSS Interactively</title>
<link rel="stylesheet" href="./styles/main.css"> <link rel="stylesheet" href="./styles/main.css">

View File

@@ -32,6 +32,7 @@ const elements = {
moduleSelectorBtn: document.getElementById('module-selector-btn'), moduleSelectorBtn: document.getElementById('module-selector-btn'),
resetBtn: document.getElementById('reset-btn'), resetBtn: document.getElementById('reset-btn'),
helpBtn: document.getElementById('help-btn'), helpBtn: document.getElementById('help-btn'),
lessonContainer: document.querySelector('.lesson-container'),
}; };
// Initialize the lesson engine // Initialize the lesson engine
@@ -63,12 +64,59 @@ async function initializeModules() {
} else if (state.modules.length > 0) { } else if (state.modules.length > 0) {
selectModule(state.modules[0].id); selectModule(state.modules[0].id);
} }
// Update progress indicator on module selector button
updateModuleSelectorButtonProgress();
} catch (error) { } catch (error) {
console.error('Failed to load modules:', error); console.error('Failed to load modules:', error);
elements.lessonDescription.textContent = 'Failed to load modules. Please refresh the page.'; 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 <span style="font-size: 0.8em; opacity: 0.8;">${percentComplete}%</span>`;
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 // Select a module
function selectModule(moduleId) { function selectModule(moduleId) {
const selectedModule = state.modules.find(module => module.id === moduleId); const selectedModule = state.modules.find(module => module.id === moduleId);
@@ -95,6 +143,17 @@ function selectModule(moduleId) {
// Save the last selected module // Save the last selected module
localStorage.setItem('lastModuleId', moduleId); 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 // Load the current lesson
@@ -113,6 +172,9 @@ function loadCurrentLesson() {
const lesson = state.currentModule.lessons[state.currentLessonIndex]; const lesson = state.currentModule.lessons[state.currentLessonIndex];
lessonEngine.setLesson(lesson); lessonEngine.setLesson(lesson);
// Reset any success indicators
resetSuccessIndicators();
// Update UI // Update UI
renderLesson( renderLesson(
elements.lessonTitle, elements.lessonTitle,
@@ -138,6 +200,9 @@ function loadCurrentLesson() {
// Save current progress // Save current progress
state.userProgress[state.currentModule.id].current = state.currentLessonIndex; state.userProgress[state.currentModule.id].current = state.currentLessonIndex;
saveUserProgress(); saveUserProgress();
// Update progress indicator on module selector button
updateModuleSelectorButtonProgress();
} }
// Update navigation buttons state // Update navigation buttons state
@@ -191,11 +256,18 @@ function runCode() {
if (!moduleProgress.completed.includes(state.currentLessonIndex)) { if (!moduleProgress.completed.includes(state.currentLessonIndex)) {
moduleProgress.completed.push(state.currentLessonIndex); moduleProgress.completed.push(state.currentLessonIndex);
saveUserProgress(); saveUserProgress();
updateModuleSelectorButtonProgress();
} }
// Show success feedback // Show success feedback with visual indicators
showFeedback(true, validationResult.message || 'Great job! Your code works correctly.'); 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 // Apply the code to see the result
lessonEngine.applyUserCode(userCode); lessonEngine.applyUserCode(userCode);
@@ -205,8 +277,11 @@ function runCode() {
elements.nextBtn.classList.remove('btn-disabled'); elements.nextBtn.classList.remove('btn-disabled');
} }
} else { } else {
// Show error feedback // Reset any success indicators
showFeedback(false, validationResult.message || 'Your code doesn\'t seem to be correct. Try again!'); 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() {
<ul> <ul>
<li><strong>Run</strong> - Test your CSS code</li> <li><strong>Run</strong> - Test your CSS code</li>
<li><strong>Previous/Next</strong> - Navigate between lessons</li> <li><strong>Previous/Next</strong> - Navigate between lessons</li>
<li><strong>Modules</strong> - Select a different learning module</li> <li><strong>Progress</strong> - Select a different learning module</li>
<li><strong>Reset Progress</strong> - Clear all your saved progress</li> <li><strong>Reset Progress</strong> - Clear all your saved progress</li>
</ul> </ul>
@@ -294,6 +369,8 @@ function showHelp() {
<li>Use the preview area to see how your CSS affects the elements</li> <li>Use the preview area to see how your CSS affects the elements</li>
<li>Your progress is automatically saved in your browser storage</li> <li>Your progress is automatically saved in your browser storage</li>
<li>You can revisit completed lessons at any time</li> <li>You can revisit completed lessons at any time</li>
<li>Press Tab in the code editor to indent with two spaces</li>
<li>Use Ctrl+Enter to quickly run your code</li>
</ul> </ul>
`; `;
@@ -326,6 +403,9 @@ function resetProgress() {
} else if (state.modules.length > 0) { } else if (state.modules.length > 0) {
selectModule(state.modules[0].id); selectModule(state.modules[0].id);
} }
// Update progress indicator
updateModuleSelectorButtonProgress();
}); });
elements.modalContainer.classList.remove('hidden'); elements.modalContainer.classList.remove('hidden');
@@ -336,6 +416,22 @@ function closeModal() {
elements.modalContainer.classList.add('hidden'); 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 // Initialize the application
function init() { function init() {
loadUserProgress(); loadUserProgress();
@@ -350,6 +446,9 @@ function init() {
elements.resetBtn.addEventListener('click', resetProgress); elements.resetBtn.addEventListener('click', resetProgress);
elements.helpBtn.addEventListener('click', showHelp); elements.helpBtn.addEventListener('click', showHelp);
// Add tab key handler for the code input
elements.codeInput.addEventListener('keydown', handleTabKey);
// Handle keyboard shortcuts // Handle keyboard shortcuts
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
// Ctrl+Enter to run code // Ctrl+Enter to run code

View File

@@ -9,7 +9,7 @@ let feedbackElement = null;
* Render the module list in the sidebar * Render the module list in the sidebar
* @param {HTMLElement} container - The container element for the module list * @param {HTMLElement} container - The container element for the module list
* @param {Array} modules - The list of modules * @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) { export function renderModuleList(container, modules, onSelectModule) {
// Clear the container // Clear the container

View File

View File

@@ -107,10 +107,16 @@ body {
/* Lesson Container */ /* Lesson Container */
.lesson-container { .lesson-container {
display: flex;
flex-flow: column;
align-items: stretch;
justify-content: flex-start;
gap: 2rem;
background-color: var(--panel-bg); background-color: var(--panel-bg);
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
padding: 2rem; padding: 2rem;
height: 100%;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
@@ -128,6 +134,7 @@ body {
/* Challenge Container */ /* Challenge Container */
.challenge-container { .challenge-container {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
gap: 1.5rem; gap: 1.5rem;
margin-bottom: 2rem; margin-bottom: 2rem;
@@ -216,6 +223,11 @@ code {
/* Controls */ /* Controls */
.controls { .controls {
/*justify-self: end;*/
/*position: absolute;*/
/*left: 2rem;*/
/*right: 2rem;*/
/*bottom: 2rem;*/
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;