-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
124 lines (106 loc) · 3.56 KB
/
App.tsx
File metadata and controls
124 lines (106 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import React, { useState, useEffect } from 'react';
import { INITIAL_BOOK } from './constants';
import { Book } from './types';
import { Bookshelf } from './components/Bookshelf';
import { BookWorkspace } from './components/BookWorkspace';
import { createNewBook } from './utils/bookFactory';
const App: React.FC = () => {
// Load books from localStorage or use initial demo
const [books, setBooks] = useState<Book[]>(() => {
const saved = localStorage.getItem('codex_books');
if (saved) {
try {
return JSON.parse(saved);
} catch (e) {
console.error("Failed to parse saved books", e);
return [INITIAL_BOOK];
}
}
return [INITIAL_BOOK];
});
const [activeBookId, setActiveBookId] = useState<string | null>(null);
const [theme, setTheme] = useState<'light' | 'dark'>(() => {
const savedTheme = localStorage.getItem('codex_theme');
if (savedTheme) return savedTheme as 'light' | 'dark';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
});
// PWA Install State
const [installPrompt, setInstallPrompt] = useState<any>(null);
useEffect(() => {
const handleBeforeInstallPrompt = (e: Event) => {
e.preventDefault();
setInstallPrompt(e);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}, []);
const handleInstallApp = async () => {
if (!installPrompt) return;
installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
if (outcome === 'accepted') {
setInstallPrompt(null);
}
};
// Persistence for books
useEffect(() => {
localStorage.setItem('codex_books', JSON.stringify(books));
}, [books]);
// Persistence for theme
useEffect(() => {
localStorage.setItem('codex_theme', theme);
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [theme]);
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
const activeBook = books.find(b => b.id === activeBookId);
const handleUpdateBook = (updatedBook: Book) => {
setBooks(prev => prev.map(b => b.id === updatedBook.id ? updatedBook : b));
};
const handleCreateBook = (title: string) => {
const newBook = createNewBook(title);
setBooks(prev => [...prev, newBook]);
setActiveBookId(newBook.id);
};
const handleDeleteBook = (id: string) => {
if (window.confirm("Are you sure you want to delete this entire book? This cannot be undone.")) {
setBooks(prev => prev.filter(b => b.id !== id));
if (activeBookId === id) setActiveBookId(null);
}
};
const handleImportLibrary = (importedBooks: Book[]) => {
setBooks(importedBooks);
};
if (activeBookId && activeBook) {
return (
<BookWorkspace
book={activeBook}
onUpdateBook={handleUpdateBook}
onBack={() => setActiveBookId(null)}
theme={theme}
onToggleTheme={toggleTheme}
isInstallable={!!installPrompt}
onInstallApp={handleInstallApp}
/>
);
}
return (
<Bookshelf
books={books}
onOpenBook={setActiveBookId}
onCreateBook={handleCreateBook}
onDeleteBook={handleDeleteBook}
onImportLibrary={handleImportLibrary}
theme={theme}
onToggleTheme={toggleTheme}
isInstallable={!!installPrompt}
onInstallApp={handleInstallApp}
/>
);
};
export default App;