feat(03-03): render forest elements from IndexedDB in grid
- renderForestScene() loads elements from IDB and renders as images - Object URLs tracked and revoked on re-render to prevent memory leaks - Random +-10-20px offset for natural element placement - New element animation class applied via newElementId parameter - Integrated into initForestScreen alongside renderLevelMap
This commit is contained in:
+51
-1
@@ -1,6 +1,55 @@
|
|||||||
import type { Progress, LevelStatus } from "../types";
|
import type { Progress, LevelStatus } from "../types";
|
||||||
import { levels } from "../game/levels";
|
import { levels } from "../game/levels";
|
||||||
import { getProgress } from "../storage/db";
|
import { getProgress, getForestElements } from "../storage/db";
|
||||||
|
|
||||||
|
let activeObjectUrls: string[] = [];
|
||||||
|
|
||||||
|
export async function renderForestScene(
|
||||||
|
db: IDBDatabase,
|
||||||
|
newElementId?: number,
|
||||||
|
): Promise<void> {
|
||||||
|
const grid = document.getElementById("forest-grid");
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
// Revoke old object URLs to prevent memory leaks
|
||||||
|
for (const url of activeObjectUrls) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
activeObjectUrls = [];
|
||||||
|
|
||||||
|
const elements = await getForestElements(db);
|
||||||
|
grid.innerHTML = "";
|
||||||
|
|
||||||
|
// Render up to 12 elements (4x3 grid)
|
||||||
|
const maxSlots = 12;
|
||||||
|
const toRender = elements.slice(0, maxSlots);
|
||||||
|
|
||||||
|
for (const element of toRender) {
|
||||||
|
const slot = document.createElement("div");
|
||||||
|
slot.className = "forest__element";
|
||||||
|
|
||||||
|
const img = document.createElement("img");
|
||||||
|
const objectUrl = URL.createObjectURL(element.imageBlob);
|
||||||
|
activeObjectUrls.push(objectUrl);
|
||||||
|
img.src = objectUrl;
|
||||||
|
img.alt = element.description;
|
||||||
|
|
||||||
|
// Random offset +-10-20px for natural placement
|
||||||
|
const offsetX =
|
||||||
|
(Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10);
|
||||||
|
const offsetY =
|
||||||
|
(Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10);
|
||||||
|
img.style.transform = `translate(${Math.round(offsetX)}px, ${Math.round(offsetY)}px)`;
|
||||||
|
|
||||||
|
// Animate newly added element
|
||||||
|
if (element.id === newElementId) {
|
||||||
|
slot.classList.add("forest__element--new");
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.appendChild(img);
|
||||||
|
grid.appendChild(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getLevelStatus(
|
export function getLevelStatus(
|
||||||
levelNum: number,
|
levelNum: number,
|
||||||
@@ -62,6 +111,7 @@ export async function initForestScreen(
|
|||||||
if (!progress) return;
|
if (!progress) return;
|
||||||
|
|
||||||
renderLevelMap(mapContainer, progress, onStartLesson);
|
renderLevelMap(mapContainer, progress, onStartLesson);
|
||||||
|
await renderForestScene(db);
|
||||||
|
|
||||||
const allComplete = levels.every((l) =>
|
const allComplete = levels.every((l) =>
|
||||||
progress.completedLevels.includes(l.level),
|
progress.completedLevels.includes(l.level),
|
||||||
|
|||||||
Reference in New Issue
Block a user