feat(05-01): extend Progress type with currentForestPage, add paginated renderForestScene

- Add currentForestPage: number field to Progress interface in types.ts
- Export SLOTS_PER_PAGE = 12 constant from scene.ts
- Change renderForestScene signature to accept page: number parameter
- Slice elements by page (start/end = page * SLOTS_PER_PAGE)
- Update navigation UI (forest-nav) after rendering
- Add migrateProgress entry for currentForestPage in app.ts
- Fix Progress object literals in db.test.ts and screens.ts to include new field
This commit is contained in:
2026-03-29 22:57:32 +02:00
parent 7840bd5fdc
commit 559d57a013
5 changed files with 38 additions and 5 deletions
+4 -1
View File
@@ -38,6 +38,7 @@ function migrateProgress(
if (!progress.lastPlayedLevels) progress.lastPlayedLevels = []; if (!progress.lastPlayedLevels) progress.lastPlayedLevels = [];
if (progress.totalCorrect === undefined) progress.totalCorrect = 0; if (progress.totalCorrect === undefined) progress.totalCorrect = 0;
if (progress.totalErrors === undefined) progress.totalErrors = 0; if (progress.totalErrors === undefined) progress.totalErrors = 0;
if (progress.currentForestPage === undefined) progress.currentForestPage = 0;
} }
async function handleLessonComplete( async function handleLessonComplete(
@@ -120,7 +121,9 @@ async function handleLessonComplete(
async () => { async () => {
// "Zurück zum Wald" -- show forest with new element animated // "Zurück zum Wald" -- show forest with new element animated
await initForestScreen(db, startLessonFromForest); await initForestScreen(db, startLessonFromForest);
await renderForestScene(db, elementId); const prog = await getProgress(db);
const page = prog?.currentForestPage ?? 0;
await renderForestScene(db, page, elementId);
showScreen("forest"); showScreen("forest");
}, },
); );
+31 -4
View File
@@ -5,8 +5,11 @@ import { playForestElementSound } from "../audio/sounds";
let activeObjectUrls: string[] = []; let activeObjectUrls: string[] = [];
export const SLOTS_PER_PAGE = 12;
export async function renderForestScene( export async function renderForestScene(
db: IDBDatabase, db: IDBDatabase,
page: number,
newElementId?: number, newElementId?: number,
): Promise<void> { ): Promise<void> {
const grid = document.getElementById("forest-grid"); const grid = document.getElementById("forest-grid");
@@ -21,9 +24,10 @@ export async function renderForestScene(
const elements = await getForestElements(db); const elements = await getForestElements(db);
grid.innerHTML = ""; grid.innerHTML = "";
// Render up to 12 elements (4x3 grid) // Render elements for the given page (4x3 grid per page)
const maxSlots = 12; const start = page * SLOTS_PER_PAGE;
const toRender = elements.slice(0, maxSlots); const end = start + SLOTS_PER_PAGE;
const toRender = elements.slice(start, end);
for (const element of toRender) { for (const element of toRender) {
const slot = document.createElement("div"); const slot = document.createElement("div");
@@ -51,6 +55,28 @@ export async function renderForestScene(
slot.appendChild(img); slot.appendChild(img);
grid.appendChild(slot); grid.appendChild(slot);
} }
// Update navigation UI
const totalPages = Math.max(1, Math.ceil(elements.length / SLOTS_PER_PAGE));
const navContainer = document.getElementById("forest-nav");
if (navContainer) {
const prevBtn = document.getElementById(
"forest-nav-prev",
) as HTMLButtonElement;
const nextBtn = document.getElementById(
"forest-nav-next",
) as HTMLButtonElement;
const pageText = document.getElementById("forest-nav-text");
if (totalPages <= 1) {
navContainer.style.display = "none";
} else {
navContainer.style.display = "flex";
prevBtn.disabled = page <= 0;
nextBtn.disabled = page >= totalPages - 1;
if (pageText) pageText.textContent = `Wald ${page + 1} von ${totalPages}`;
}
}
} }
export function getLevelStatus( export function getLevelStatus(
@@ -113,7 +139,8 @@ export async function initForestScreen(
if (!progress) return; if (!progress) return;
renderLevelMap(mapContainer, progress, onStartLesson); renderLevelMap(mapContainer, progress, onStartLesson);
await renderForestScene(db); const currentPage = progress.currentForestPage ?? 0;
await renderForestScene(db, currentPage);
const allComplete = levels.every((l) => const allComplete = levels.every((l) =>
progress.completedLevels.includes(l.level), progress.completedLevels.includes(l.level),
+1
View File
@@ -37,6 +37,7 @@ describe("IndexedDB wrapper", () => {
totalErrors: 0, totalErrors: 0,
errorKeyCounts: {}, errorKeyCounts: {},
lastPlayedLevels: [], lastPlayedLevels: [],
currentForestPage: 0,
}; };
await saveProgress(db, progress); await saveProgress(db, progress);
const result = await getProgress(db); const result = await getProgress(db);
+1
View File
@@ -18,6 +18,7 @@ export interface Progress {
totalErrors: number; totalErrors: number;
errorKeyCounts: Record<string, number>; errorKeyCounts: Record<string, number>;
lastPlayedLevels: number[]; // last 3 played levels for review mechanic lastPlayedLevels: number[]; // last 3 played levels for review mechanic
currentForestPage: number; // 0-indexed, default 0
} }
export type LessonPhase = "discover" | "practice" | "words"; export type LessonPhase = "discover" | "practice" | "words";
+1
View File
@@ -83,6 +83,7 @@ export function initWelcomeScreen(
totalErrors: 0, totalErrors: 0,
errorKeyCounts: {}, errorKeyCounts: {},
lastPlayedLevels: [], lastPlayedLevels: [],
currentForestPage: 0,
}; };
await saveProgress(db, progress); await saveProgress(db, progress);
navigateTo("forest"); navigateTo("forest");