Skip to content

Commit b9e5763

Browse files
committed
feat: Show ContentNavigator sidebar on exercise/quiz/exam pages
Wrap exercise, quiz, and exam pages in ContentNavigator to display the topic/practice sidebar while working on assessments. This provides navigation context and quick access to other items in the same topic. - Add children and currentPracticeId props to ContentNavigator - Refactor quiz page to pure Preact component - Add active state styling for current practice item in sidebar - Fix exercise/quiz/exam navigation by adding key props
1 parent 8836bd1 commit b9e5763

6 files changed

Lines changed: 187 additions & 213 deletions

File tree

src/components/preact/ContentNavigator.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export function ContentNavigator({
101101
}, [currentTopic, currentSubtopicSlug]);
102102

103103
const topicIndex = currentTopic ? subject.topics.findIndex(t => t.id === currentTopicId) : -1;
104+
const subtopicIndex = useMemo(() => {
105+
if (!currentSubtopic || !currentTopic?.subtopics?.length) return -1;
106+
return currentTopic.subtopics.findIndex(st => st.id === currentSubtopic.id);
107+
}, [currentSubtopic, currentTopic]);
108+
const subtopicCount = currentTopic?.subtopics?.length ?? 0;
104109

105110
const hasPracticeItems = practiceQuizzes.length > 0
106111
|| practiceExercises.length > 0
@@ -118,7 +123,7 @@ export function ContentNavigator({
118123
if (currentSubtopic) {
119124
onSubtopicView(currentSubtopic.id);
120125
}
121-
}, [currentSubtopic?.id]);
126+
}, [currentSubtopic?.id, onSubtopicView]);
122127

123128
// Progress helpers
124129
const isTopicCompleted = useCallback((topic: Topic): boolean => {
@@ -566,7 +571,15 @@ export function ContentNavigator({
566571
<>
567572
<a href={`#/subject/${subject.id}`}>{subject.code}</a>
568573
<span class="separator" dangerouslySetInnerHTML={{ __html: Icons.ChevronRight }} />
569-
<span class="current">{currentTopic.title}</span>
574+
{currentSubtopic ? (
575+
<>
576+
<a href={`#/subject/${subject.id}/topic/${currentTopic.id}`}>{currentTopic.title}</a>
577+
<span class="separator" dangerouslySetInnerHTML={{ __html: Icons.ChevronRight }} />
578+
<span class="current">{currentSubtopic.title}</span>
579+
</>
580+
) : (
581+
<span class="current">{currentTopic.title}</span>
582+
)}
570583
</>
571584
) : (
572585
<span class="current">{subject.title}</span>
@@ -581,7 +594,11 @@ export function ContentNavigator({
581594
<h1>{currentTopic ? currentTopic.title : subject.title}</h1>
582595
</div>
583596
{currentTopic ? (
584-
<span class="topic-counter">Topic {topicIndex + 1} of {subject.topics.length}</span>
597+
currentSubtopic && subtopicIndex >= 0 ? (
598+
<span class="topic-counter">Section {subtopicIndex + 1} of {subtopicCount}</span>
599+
) : (
600+
<span class="topic-counter">Topic {topicIndex + 1} of {subject.topics.length}</span>
601+
)
585602
) : (
586603
<div class={`subject-status-badge status-${progressStatus.replace('_', '-')}`}>
587604
{formatStatus(progressStatus)}

src/components/preact/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { ProofEditor } from './ProofEditor';
55
export { ExercisePage } from './ExercisePage';
66
export { ExamPage } from './ExamPage';
77
export { PracticeMode } from './PracticeMode';
8+
export { ContentNavigator } from './ContentNavigator';

src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,15 @@ function initApp(): void {
271271
// Topic view (may redirect to subtopic if topic has subtopics)
272272
renderSubjectPage(mainEl, curriculum, subjectId, params.topicId, allProjects, allExams, undefined, allQuizzes, allExercises);
273273
} else if (params.quizId) {
274-
renderQuizPage(mainEl, curriculum, allQuizzes, subjectId, params.quizId);
274+
renderQuizPage(mainEl, curriculum, allQuizzes, subjectId, params.quizId, allExercises, allExams, allProjects);
275275
} else if (params.examId) {
276-
renderExamPage(mainEl, curriculum, allExams, subjectId, params.examId);
276+
renderExamPage(mainEl, curriculum, allExams, subjectId, params.examId, allQuizzes, allExercises, allProjects);
277277
} else if (params.exId) {
278278
renderExercisePage(mainEl, curriculum, allExercises, subjectId, params.exId, allQuizzes, allExams, allProjects);
279279
} else if (params.projId) {
280280
renderProjectPage(mainEl, curriculum, allProjects, subjectId, params.projId);
281281
} else {
282-
renderSubjectPage(mainEl, curriculum, subjectId, undefined, allProjects, allExams);
282+
renderSubjectPage(mainEl, curriculum, subjectId, undefined, allProjects, allExams, undefined, allQuizzes, allExercises);
283283
}
284284
}
285285
});

src/pages/exam-page.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Exam page rendering with Preact components
22
import { h } from 'preact';
33
import { render } from 'preact';
4-
import type { Subject, Exam } from '@/core/types';
5-
import { ExamPage } from '@/components/preact';
4+
import type { Subject, Exam, Quiz, Exercise, Project } from '@/core/types';
5+
import { progressStorage } from '@/core/storage';
6+
import { ExamPage, ContentNavigator } from '@/components/preact';
67
import { renderNotFound } from './assessment-utils';
78

89
/**
@@ -13,7 +14,10 @@ export function renderExamPage(
1314
subjects: Subject[],
1415
exams: Exam[],
1516
subjectId: string,
16-
examId: string
17+
examId: string,
18+
quizzes: Quiz[] = [],
19+
exercises: Exercise[] = [],
20+
projects: Project[] = []
1721
): void {
1822
const subject = subjects.find(s => s.id === subjectId);
1923
const exam = exams.find(e => e.id === examId);
@@ -23,11 +27,29 @@ export function renderExamPage(
2327
return;
2428
}
2529

26-
// Clear container and render Preact component
30+
// Get progress for the sidebar
31+
const userProgress = progressStorage.getProgress();
32+
const subjectProgress = userProgress.subjects[subjectId];
33+
34+
// Clear container and render Preact component wrapped in ContentNavigator
2735
// Key prop ensures Preact creates a new component instance on navigation
2836
container.innerHTML = '';
2937
render(
30-
<ExamPage key={examId} subject={subject} exam={exam} />,
38+
<ContentNavigator
39+
key={examId}
40+
subject={subject}
41+
currentTopicId={exam.topicId}
42+
progress={subjectProgress}
43+
progressStatus={subjectProgress?.status || 'not_started'}
44+
quizzes={quizzes}
45+
exercises={exercises}
46+
exams={exams}
47+
projects={projects}
48+
currentPracticeId={examId}
49+
onSubtopicView={() => {}}
50+
>
51+
<ExamPage subject={subject} exam={exam} />
52+
</ContentNavigator>,
3153
container
3254
);
3355
}

0 commit comments

Comments
 (0)