lighter first load, math reads left-to-right in quizzes, tickets from the server, readable separators
- Every screen but home is its own chunk and preloads when the browser is
idle; React and motion get their own long-cached chunks. The app entry
goes from 505 kB (160 kB gzip) to 85 kB (27 kB gzip).
- Quiz prompts, options and lesson examples go through MathText, so
'10 + 3 - 7 - 2' reads from the left; an unpaired bracket at the edge of
a math run stays with the Persian sentence.
- Support lists tickets from GET /support/tickets instead of this
browser's localStorage; a ticket's messages load when it is opened.
- '·' next to a Persian zero read as another digit ('فصلِ ۱ · ۰' looked
like ۱۰۰); a thin bar separates them now. Notification bodies show
Persian digits.
- A root or vertex the model already marked with coordinates is no longer
labelled a second time on top of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { lazy, Suspense, useEffect, useState, type ComponentType } from 'react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { CheckCircle2, XCircle } from 'lucide-react';
|
||||
import { api, type Grade, type UserProfile } from '@/api';
|
||||
@@ -8,25 +8,46 @@ import { isTab, type Route } from '@/nav/routes';
|
||||
import { BottomNav } from '@/components/BottomNav';
|
||||
import { Sheet } from '@/components/Sheet';
|
||||
import { useToast } from '@/components/Toast';
|
||||
import { PrimaryButton } from '@/components/ui';
|
||||
import { AskScreen } from '@/screens/AskScreen';
|
||||
import { CharactersScreen } from '@/screens/CharactersScreen';
|
||||
import { FamilyScreen } from '@/screens/FamilyScreen';
|
||||
import { LoadingState, PrimaryButton } from '@/components/ui';
|
||||
import { HomeScreen } from '@/screens/HomeScreen';
|
||||
import { InviteScreen } from '@/screens/InviteScreen';
|
||||
import { LeaderboardScreen } from '@/screens/LeaderboardScreen';
|
||||
import { LessonScreen } from '@/screens/LessonScreen';
|
||||
import { LoginScreen } from '@/screens/LoginScreen';
|
||||
import { MapScreen } from '@/screens/MapScreen';
|
||||
import { NotificationsScreen } from '@/screens/NotificationsScreen';
|
||||
import { ProfileScreen } from '@/screens/ProfileScreen';
|
||||
import { QuizScreen } from '@/screens/QuizScreen';
|
||||
import { StreakScreen } from '@/screens/StreakScreen';
|
||||
import { SubscriptionScreen } from '@/screens/SubscriptionScreen';
|
||||
import { SupportScreen } from '@/screens/SupportScreen';
|
||||
import { UnitScreen } from '@/screens/UnitScreen';
|
||||
import type { PaymentReturn } from './launchParams';
|
||||
|
||||
/**
|
||||
* خانه همراهِ خودِ اپ بار میشود؛ بقیهی صفحهها هر کدام فایلِ جدا دارند تا بارِ اول
|
||||
* سبک باشد (قبلاً کلِ اپ یک فایلِ ۵۰۵ کیلوبایتی بود، و دستیار با ماژولِ شکلها
|
||||
* بزرگترین تکهاش). بعد از بالا آمدنِ خانه همه در پسزمینه گرفته میشوند
|
||||
* (preloadScreens)، پس رفتن به صفحهی بعدی منتظرِ شبکه نمیماند.
|
||||
*/
|
||||
const loaders: (() => Promise<unknown>)[] = [];
|
||||
function screen<M extends Record<K, ComponentType<any>>, K extends keyof M>(load: () => Promise<M>, name: K) {
|
||||
loaders.push(load);
|
||||
return lazy(async () => ({ default: (await load())[name] }));
|
||||
}
|
||||
const AskScreen = screen(() => import('@/screens/AskScreen'), 'AskScreen');
|
||||
const CharactersScreen = screen(() => import('@/screens/CharactersScreen'), 'CharactersScreen');
|
||||
const FamilyScreen = screen(() => import('@/screens/FamilyScreen'), 'FamilyScreen');
|
||||
const InviteScreen = screen(() => import('@/screens/InviteScreen'), 'InviteScreen');
|
||||
const LeaderboardScreen = screen(() => import('@/screens/LeaderboardScreen'), 'LeaderboardScreen');
|
||||
const LessonScreen = screen(() => import('@/screens/LessonScreen'), 'LessonScreen');
|
||||
const MapScreen = screen(() => import('@/screens/MapScreen'), 'MapScreen');
|
||||
const NotificationsScreen = screen(() => import('@/screens/NotificationsScreen'), 'NotificationsScreen');
|
||||
const ProfileScreen = screen(() => import('@/screens/ProfileScreen'), 'ProfileScreen');
|
||||
const QuizScreen = screen(() => import('@/screens/QuizScreen'), 'QuizScreen');
|
||||
const StreakScreen = screen(() => import('@/screens/StreakScreen'), 'StreakScreen');
|
||||
const SubscriptionScreen = screen(() => import('@/screens/SubscriptionScreen'), 'SubscriptionScreen');
|
||||
const SupportScreen = screen(() => import('@/screens/SupportScreen'), 'SupportScreen');
|
||||
const UnitScreen = screen(() => import('@/screens/UnitScreen'), 'UnitScreen');
|
||||
|
||||
let preloaded = false;
|
||||
function preloadScreens() {
|
||||
if (preloaded) return;
|
||||
preloaded = true;
|
||||
const run = () => loaders.forEach((load) => void load().catch(() => {}));
|
||||
if ('requestIdleCallback' in window) window.requestIdleCallback(run, { timeout: 2500 });
|
||||
else setTimeout(run, 1200);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
profile: UserProfile;
|
||||
grades: Grade[];
|
||||
@@ -39,6 +60,7 @@ interface Props {
|
||||
|
||||
/** اپِ اصلی بعد از ورود: تبها، صفحهها و دادهی مشترک. */
|
||||
export function MainApp({ profile, grades, payment, start, onSignedOut, onRegistered }: Props) {
|
||||
useEffect(preloadScreens, []);
|
||||
return (
|
||||
<AppDataProvider initialProfile={profile} grades={grades}>
|
||||
<NavigatorProvider initial={start ?? { name: payment ? 'subscription' : 'home' }}>
|
||||
@@ -66,7 +88,9 @@ function Screens({ onSignedOut, onRegistered }: Pick<Props, 'onSignedOut' | 'onR
|
||||
exit={{ opacity: 0, x: 18 }}
|
||||
transition={{ duration: 0.18 }}
|
||||
>
|
||||
<RouteView route={route} onSignedOut={onSignedOut} onRegistered={onRegistered} />
|
||||
<Suspense fallback={<LoadingState />}>
|
||||
<RouteView route={route} onSignedOut={onSignedOut} onRegistered={onRegistered} />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user