feat: add module pill indicator and cross-module navigation
- Add module title pill above lesson title showing current category - Enable next/prev buttons to cross module boundaries - Automatically advance to next module when completing last lesson - Go to last lesson of previous module when navigating back 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
26
src/app.js
26
src/app.js
@@ -18,6 +18,7 @@ const elements = {
|
|||||||
helpBtn: document.getElementById("help-btn"),
|
helpBtn: document.getElementById("help-btn"),
|
||||||
|
|
||||||
// Left panel
|
// Left panel
|
||||||
|
modulePill: document.getElementById("module-pill"),
|
||||||
lessonTitle: document.getElementById("lesson-title"),
|
lessonTitle: document.getElementById("lesson-title"),
|
||||||
lessonDescription: document.getElementById("lesson-description"),
|
lessonDescription: document.getElementById("lesson-description"),
|
||||||
taskInstruction: document.getElementById("task-instruction"),
|
taskInstruction: document.getElementById("task-instruction"),
|
||||||
@@ -266,6 +267,11 @@ function loadCurrentLesson() {
|
|||||||
// Update UI based on mode
|
// Update UI based on mode
|
||||||
updateEditorForMode(mode);
|
updateEditorForMode(mode);
|
||||||
|
|
||||||
|
// Update module pill with category name
|
||||||
|
if (elements.modulePill && engineState.module) {
|
||||||
|
elements.modulePill.textContent = engineState.module.title;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset any success indicators
|
// Reset any success indicators
|
||||||
resetSuccessIndicators();
|
resetSuccessIndicators();
|
||||||
|
|
||||||
@@ -362,19 +368,39 @@ function updateNavigationButtons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nextLesson() {
|
function nextLesson() {
|
||||||
|
const prevModuleId = lessonEngine.getCurrentState().module?.id;
|
||||||
const success = lessonEngine.nextLesson();
|
const success = lessonEngine.nextLesson();
|
||||||
if (success) {
|
if (success) {
|
||||||
|
const newModuleId = lessonEngine.getCurrentState().module?.id;
|
||||||
|
if (newModuleId !== prevModuleId) {
|
||||||
|
updateModuleHighlight(newModuleId);
|
||||||
|
}
|
||||||
loadCurrentLesson();
|
loadCurrentLesson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevLesson() {
|
function prevLesson() {
|
||||||
|
const prevModuleId = lessonEngine.getCurrentState().module?.id;
|
||||||
const success = lessonEngine.previousLesson();
|
const success = lessonEngine.previousLesson();
|
||||||
if (success) {
|
if (success) {
|
||||||
|
const newModuleId = lessonEngine.getCurrentState().module?.id;
|
||||||
|
if (newModuleId !== prevModuleId) {
|
||||||
|
updateModuleHighlight(newModuleId);
|
||||||
|
}
|
||||||
loadCurrentLesson();
|
loadCurrentLesson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateModuleHighlight(moduleId) {
|
||||||
|
const moduleItems = elements.moduleList.querySelectorAll(".module-header");
|
||||||
|
moduleItems.forEach((item) => {
|
||||||
|
item.classList.remove("active");
|
||||||
|
if (item.dataset.moduleId === moduleId) {
|
||||||
|
item.classList.add("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ================= CODE EXECUTION =================
|
// ================= CODE EXECUTION =================
|
||||||
|
|
||||||
function resetCode() {
|
function resetCode() {
|
||||||
|
|||||||
@@ -101,19 +101,49 @@ export class LessonEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the next lesson
|
* Move to the next lesson (crosses module boundaries)
|
||||||
* @returns {boolean} Whether the operation was successful
|
* @returns {boolean} Whether the operation was successful
|
||||||
*/
|
*/
|
||||||
nextLesson() {
|
nextLesson() {
|
||||||
return this.setLessonByIndex(this.currentLessonIndex + 1);
|
// Try next lesson in current module
|
||||||
|
if (this.setLessonByIndex(this.currentLessonIndex + 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At end of module, try next module
|
||||||
|
const currentModuleIndex = this.modules.findIndex((m) => m.id === this.currentModule?.id);
|
||||||
|
if (currentModuleIndex >= 0 && currentModuleIndex < this.modules.length - 1) {
|
||||||
|
const nextModule = this.modules[currentModuleIndex + 1];
|
||||||
|
this.setModule(nextModule);
|
||||||
|
this.setLessonByIndex(0); // Start at first lesson
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the previous lesson
|
* Move to the previous lesson (crosses module boundaries)
|
||||||
* @returns {boolean} Whether the operation was successful
|
* @returns {boolean} Whether the operation was successful
|
||||||
*/
|
*/
|
||||||
previousLesson() {
|
previousLesson() {
|
||||||
return this.setLessonByIndex(this.currentLessonIndex - 1);
|
// Try previous lesson in current module
|
||||||
|
if (this.setLessonByIndex(this.currentLessonIndex - 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At start of module, try previous module
|
||||||
|
const currentModuleIndex = this.modules.findIndex((m) => m.id === this.currentModule?.id);
|
||||||
|
if (currentModuleIndex > 0) {
|
||||||
|
const prevModule = this.modules[currentModuleIndex - 1];
|
||||||
|
this.setModule(prevModule);
|
||||||
|
// Go to last lesson of previous module
|
||||||
|
const lastIndex = prevModule.lessons.length - 1;
|
||||||
|
this.setLessonByIndex(lastIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,6 +430,12 @@ export class LessonEngine {
|
|||||||
* @returns {Object} The current lesson state
|
* @returns {Object} The current lesson state
|
||||||
*/
|
*/
|
||||||
getCurrentState() {
|
getCurrentState() {
|
||||||
|
const currentModuleIndex = this.modules.findIndex((m) => m.id === this.currentModule?.id);
|
||||||
|
const isLastLesson = this.currentLessonIndex >= (this.currentModule ? this.currentModule.lessons.length - 1 : 0);
|
||||||
|
const isFirstLesson = this.currentLessonIndex === 0;
|
||||||
|
const isLastModule = currentModuleIndex >= this.modules.length - 1;
|
||||||
|
const isFirstModule = currentModuleIndex <= 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
module: this.currentModule,
|
module: this.currentModule,
|
||||||
lesson: this.currentLesson,
|
lesson: this.currentLesson,
|
||||||
@@ -407,8 +443,8 @@ export class LessonEngine {
|
|||||||
userCode: this.userCode,
|
userCode: this.userCode,
|
||||||
totalLessons: this.currentModule ? this.currentModule.lessons.length : 0,
|
totalLessons: this.currentModule ? this.currentModule.lessons.length : 0,
|
||||||
isCompleted: this.isCurrentLessonCompleted(),
|
isCompleted: this.isCurrentLessonCompleted(),
|
||||||
canGoNext: this.currentLessonIndex < (this.currentModule ? this.currentModule.lessons.length - 1 : 0),
|
canGoNext: !isLastLesson || !isLastModule,
|
||||||
canGoPrev: this.currentLessonIndex > 0
|
canGoPrev: !isFirstLesson || !isFirstModule
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<!-- Linke Spalte: Anleitung + Editor -->
|
<!-- Linke Spalte: Anleitung + Editor -->
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<section class="instructions">
|
<section class="instructions">
|
||||||
|
<span class="module-pill" id="module-pill">Laden...</span>
|
||||||
<h2 id="lesson-title">Laden...</h2>
|
<h2 id="lesson-title">Laden...</h2>
|
||||||
<div class="lesson-description" id="lesson-description">
|
<div class="lesson-description" id="lesson-description">
|
||||||
Bitte wähle eine Lektion aus, um zu beginnen.
|
Bitte wähle eine Lektion aus, um zu beginnen.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<!-- Left Panel: Instructions + Editor -->
|
<!-- Left Panel: Instructions + Editor -->
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<section class="instructions">
|
<section class="instructions">
|
||||||
|
<span class="module-pill" id="module-pill">Loading...</span>
|
||||||
<h2 id="lesson-title">Loading...</h2>
|
<h2 id="lesson-title">Loading...</h2>
|
||||||
<div class="lesson-description" id="lesson-description">
|
<div class="lesson-description" id="lesson-description">
|
||||||
Please select a lesson to begin.
|
Please select a lesson to begin.
|
||||||
|
|||||||
13
src/main.css
13
src/main.css
@@ -208,6 +208,19 @@ code, kbd {
|
|||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-pill {
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--primary-bg-medium);
|
||||||
|
color: var(--primary-color);
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
#lesson-title {
|
#lesson-title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
color: var(--primary-dark);
|
color: var(--primary-dark);
|
||||||
|
|||||||
Reference in New Issue
Block a user