Archived
- Backend: Chapter, ReadingProgress, ReadingSettings models - Backend: Chapter API (TOC + content), progress tracking, settings CRUD - Frontend: ReadingPage with chapter navigation - Frontend: TableOfContents drawer - Frontend: ReadingSettingsPanel (theme, font, size, orientation) - Frontend: Custom hooks for settings, chapters, progress tracking - CSS: Mobile-first reading view with sepia/dark/light/paper themes - Route: /reader/:bookId reading view from book detail page - Docs: 001-customizable-mobile-reading-experience.md
61 lines
1.6 KiB
TypeScript
61 lines
1.6 KiB
TypeScript
/**
|
|
* App — root component with page navigation (library ↔ book detail ↔ reading view).
|
|
*/
|
|
|
|
import { useState, lazy, Suspense } from "react";
|
|
import "./App.css";
|
|
import "./reader.css";
|
|
|
|
const LibraryPage = lazy(() => import("./pages/LibraryPage"));
|
|
const BookDetailPage = lazy(() => import("./pages/BookDetailPage"));
|
|
const ReadingPage = lazy(() => import("./pages/ReadingPage"));
|
|
|
|
type View =
|
|
| { kind: "library" }
|
|
| { kind: "detail"; bookId: number }
|
|
| { kind: "reader"; book: import("./types").BookDetail };
|
|
|
|
function LoadingFallback() {
|
|
return (
|
|
<div className="loading-spinner">
|
|
<div className="spinner" />
|
|
<p>Loading...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
const [view, setView] = useState<View>({ kind: "library" });
|
|
|
|
const handleBookSelect = (id: number) => {
|
|
setView({ kind: "detail", bookId: id });
|
|
};
|
|
|
|
const handleBack = () => {
|
|
setView({ kind: "library" });
|
|
};
|
|
|
|
const handleStartReading = (book: import("./types").BookDetail) => {
|
|
setView({ kind: "reader", book });
|
|
};
|
|
|
|
return (
|
|
<div className="app">
|
|
<Suspense fallback={<LoadingFallback />}>
|
|
{view.kind === "library" && (
|
|
<LibraryPage onBookSelect={handleBookSelect} />
|
|
)}
|
|
{view.kind === "detail" && view.bookId !== undefined && (
|
|
<BookDetailPage
|
|
bookId={view.bookId}
|
|
onBack={handleBack}
|
|
onStartReading={handleStartReading}
|
|
/>
|
|
)}
|
|
{view.kind === "reader" && view.book !== undefined && (
|
|
<ReadingPage book={view.book} onBack={handleBack} />
|
|
)}
|
|
</Suspense>
|
|
</div>
|
|
);
|
|
} |