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">
<head>
<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">
<title>CODE CRISPIES - Learn CSS Interactively</title>
<link rel="stylesheet" href="./styles/main.css">

View File

@@ -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 <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
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() {
<ul>
<li><strong>Run</strong> - Test your CSS code</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>
</ul>
@@ -294,6 +369,8 @@ function showHelp() {
<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>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>
`;
@@ -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

View File

@@ -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

View File

View File

@@ -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;