Files
MathApp_frontend/src/state/subject.ts

69 lines
3.1 KiB
TypeScript

import { useSyncExternalStore } from 'react';
import { api, ApiError, type HomeResponse } from '@/api';
import { KEYS, store } from '@/lib/storage';
/**
* کتابی که دانش‌آموز در هر پایه انتخاب کرده («هندسه»، «حسابان»، …). یک منبعِ حقیقت برای هر دو تبِ «آزمون‌ها» و
* «درس‌ها»: هر دو از `home` در AppData می‌خوانند و `home` همیشه نقشه‌ی همین کتاب است، پس جابه‌جایی در یکی در
* دیگری هم دیده می‌شود.
*
* هر پایه جدا نگه داشته می‌شود (با عوض‌کردنِ پایه، انتخابِ پایه‌ی قبلی نباید به دوازدهم بپرد) و در
* localStorage می‌ماند تا بعد از بستنِ اپ همان کتاب باز شود. منبعِ حقیقتِ فهرستِ کتاب‌ها سرور است
* (`home.subjects`)؛ این فقط «کدام» را می‌گوید.
*/
type SubjectMap = Record<string, string>;
function read(): SubjectMap {
try {
const parsed: unknown = JSON.parse(store.get(KEYS.subjects) ?? '{}');
return parsed && typeof parsed === 'object' ? (parsed as SubjectMap) : {};
} catch {
return {};
}
}
let current: SubjectMap = read();
const listeners = new Set<() => void>();
export const subjectStore = {
get: (gradeId: number): string | null => current[String(gradeId)] ?? null,
set(gradeId: number, key: string | null) {
const id = String(gradeId);
if ((current[id] ?? null) === key) return;
const next = { ...current };
if (key) next[id] = key;
else delete next[id];
current = next;
store.set(KEYS.subjects, JSON.stringify(next));
listeners.forEach((l) => l());
},
subscribe(listener: () => void) {
listeners.add(listener);
return () => listeners.delete(listener);
},
snapshot: () => current,
};
/** کتابِ انتخاب‌شده‌ی این پایه؛ null یعنی هنوز انتخابی نشده (سرور اولین کتاب را می‌دهد). */
export function useSelectedSubject(gradeId: number | null | undefined): string | null {
const map = useSyncExternalStore(subjectStore.subscribe, subjectStore.snapshot, subjectStore.snapshot);
return gradeId == null ? null : map[String(gradeId)] ?? null;
}
/**
* نقشه‌ی کتابِ انتخاب‌شده. کلیدِ ذخیره‌شده‌ای که سرور دیگر نمی‌شناسد (کتاب برداشته شده یا پایه عوض شده)
* ۴۰۰ می‌گیرد؛ آن را پاک می‌کنیم و نقشه‌ی پیش‌فرض را می‌گیریم تا کاربر صفحه‌ی خالی نبیند.
*/
export async function fetchHome(gradeId: number | null | undefined): Promise<HomeResponse> {
const wanted = gradeId == null ? null : subjectStore.get(gradeId);
try {
return await api.home(wanted ?? undefined);
} catch (err) {
if (wanted && gradeId != null && err instanceof ApiError && err.status === 400) {
subjectStore.set(gradeId, null);
return api.home();
}
throw err;
}
}