Files
FolderGameChallenge/src/lib/folder-game/storage.ts
jimmy 1957e96363 Add folder game, file browser UI, and automated tests.
Introduces a localStorage-backed messy-desktop challenge with sub-steps,
drag-and-drop and Places/Back navigation, IBM Plex Mono, and a /api/health
endpoint. Adds Vitest coverage for the API and level logic plus Playwright
smoke tests.

Made-with: Cursor
2026-04-11 18:22:50 +12:00

58 lines
1.5 KiB
TypeScript

import type { SaveV1 } from "./types";
import {
LAST_PLAYABLE,
freshSave,
migrateSubIndex,
subCount,
} from "./levels";
export const STORAGE_KEY = "folder-game-challenge:v1";
export function parseSave(raw: string | null): SaveV1 | null {
if (!raw) return null;
try {
const v = JSON.parse(raw) as SaveV1;
if (v?.version !== 1) return null;
if (typeof v.levelIndex !== "number") return null;
if (v.levelIndex < 0 || v.levelIndex > LAST_PLAYABLE + 1) return null;
if (!v.fs?.nodes || typeof v.fs.viewParentId !== "string") return null;
if (!Array.isArray(v.fs.visitedFolderIds)) return null;
const subIndex = migrateSubIndex(
v.levelIndex,
v.fs,
typeof v.subIndex === "number" ? v.subIndex : undefined,
);
const maxSub = Math.max(0, subCount(v.levelIndex) - 1);
return { ...v, subIndex: Math.min(subIndex, maxSub) };
} catch {
return null;
}
}
export function loadSave(): SaveV1 | null {
if (typeof window === "undefined") return null;
return parseSave(localStorage.getItem(STORAGE_KEY));
}
export function writeSave(save: SaveV1) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(save));
}
export function clearSave() {
localStorage.removeItem(STORAGE_KEY);
}
export function resumeOrNew(): SaveV1 {
return loadSave() ?? freshSave(0);
}
export function hasInProgressSave(): boolean {
const s = loadSave();
return s !== null && s.levelIndex <= LAST_PLAYABLE;
}
export function isFinished(save: SaveV1) {
return save.levelIndex > LAST_PLAYABLE;
}