diff --git a/src/api/types.ts b/src/api/types.ts index 682d4e4..5580e08 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -106,10 +106,19 @@ export interface LessonIntro { kind: 'quiz' | 'video'; example: string; steps: string[]; + /** شکل‌های ثابتِ درس (یک بار ساخته و تأییدشده؛ هیچ تولیدِ زنده‌ای نیست). */ + figures: LessonFigure[]; videoUrl: string; durationMinutes: number | null; } +export interface LessonFigure { + where: 'intro' | 'example' | 'step'; + /** فقط برای where='step'، از صفر. */ + index?: number; + diagram: DiagramSpec; +} + export interface QuestionOption { id: string; value: string; diff --git a/src/components/ui.tsx b/src/components/ui.tsx index eabab70..aeabac9 100644 --- a/src/components/ui.tsx +++ b/src/components/ui.tsx @@ -1,4 +1,4 @@ -import type { ButtonHTMLAttributes, CSSProperties, ReactNode } from 'react'; +import { useEffect, useState, type ButtonHTMLAttributes, type CSSProperties, type ReactNode } from 'react'; import { ChevronRight, RotateCw } from 'lucide-react'; import { SeyarMascot } from '@/mascots/SeyarMascot'; import type { SeyarChar, SeyarMood } from '@/mascots/seyar'; @@ -130,8 +130,19 @@ function StateBox({ char, mood, text, children }: { char: SeyarChar; mood: Seyar ); } -export function LoadingState({ text = 'یه لحظه صبر کن…', char = 'sigma' as SeyarChar }) { - return ; +/** + * لودر فقط بعد از `delayMs` دیده می‌شود. پاسخِ API معمولاً زیرِ ۳۰۰ms می‌رسد و نشان دادنِ لودر برای + * همان چند فریم، پرشِ ریزِ «لودر ← محتوا» می‌ساخت؛ حالا تا آن لحظه جای خالی می‌ماند. اگر پاسخ + * کند باشد کاربر باز هم لودر را می‌بیند. در مرورگر با شبکه‌ی کند آزموده نشده. + */ +export function LoadingState({ text = 'یه لحظه صبر کن…', char = 'sigma' as SeyarChar, delayMs = 300 }) { + const [show, setShow] = useState(delayMs <= 0); + useEffect(() => { + if (delayMs <= 0) return; + const t = setTimeout(() => setShow(true), delayMs); + return () => clearTimeout(t); + }, [delayMs]); + return show ? : null; } export function ErrorState({ error, onRetry, text }: { error?: unknown; onRetry?: () => void; text?: string }) { diff --git a/src/screens/LessonScreen.tsx b/src/screens/LessonScreen.tsx index 86e72a0..445bc97 100644 --- a/src/screens/LessonScreen.tsx +++ b/src/screens/LessonScreen.tsx @@ -9,6 +9,7 @@ import { moodFromServer } from '@/mascots/seyar'; import { ErrorState, LoadingState, PrimaryButton, Screen, ScreenBody, ScreenHeader, Sep } from '@/components/ui'; import { toFa } from '@/lib/format'; import { MathText } from '@/lib/MathText'; +import { Diagram } from '@/diagram'; /** معرفیِ درس، پیش از تمرین. */ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?: string }) { @@ -18,6 +19,10 @@ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?: const ref = findNode(home, nodeId) ?? findNodeOfLesson(home, lessonId); const color = ref ? unitColor(ref.unitIndex) : '#ED6956'; const data = intro.data; + const figs = (where: 'intro' | 'example' | 'step', index?: number) => + (data?.figures ?? []) + .filter((f) => f.where === where && (where !== 'step' || f.index === index)) + .map((f, k) => ); return ( @@ -47,6 +52,7 @@ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?:

+ {figs('intro')}
@@ -58,6 +64,7 @@ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?:

مثال

+ {figs('example')}
)} @@ -70,7 +77,7 @@ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?: {toFa(i + 1)} - + {figs('step', i)} ))}