diff --git a/src/App.tsx b/src/App.tsx index c43beee..03c69f5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ * @license * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Play, Pause, SkipForward, SkipBack, Menu, Search, BookOpen, Disc, Bookmark, Star, Headphones, Rocket, ArrowLeft, Grid, Download, AlertCircle, LoaderCircle } from 'lucide-react'; // --- Types & Mock Data --- @@ -85,6 +85,9 @@ const MOCK_BOOKS: Book[] = [ export default function App() { const [currentView, setCurrentView] = useState('library'); const [selectedBook, setSelectedBook] = useState(null); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); // Audio State const audioRef = useRef(null); @@ -98,6 +101,16 @@ export default function App() { const [isStandalone, setIsStandalone] = useState(false); const [isIos, setIsIos] = useState(false); + const filteredBooks = useMemo(() => { + const normalizedQuery = searchQuery.trim().toLowerCase(); + if (!normalizedQuery) return MOCK_BOOKS; + return MOCK_BOOKS.filter((book) => { + const bookFields = [book.title, book.author, book.year, book.tag, book.description].join(' ').toLowerCase(); + const trackFields = book.tracks.map((track) => track.title).join(' ').toLowerCase(); + return bookFields.includes(normalizedQuery) || trackFields.includes(normalizedQuery); + }); + }, [searchQuery]); + // Fetch real data from IA (Optional enhancement, using mock for stability as requested) useEffect(() => { // In a full implementation, we'd fetch from https://archive.org/metadata/IsaacAsimovAudioBookCollection @@ -169,6 +182,16 @@ export default function App() { }; }, []); + useEffect(() => { + const handleEscape = (event: KeyboardEvent) => { + if (event.key !== 'Escape') return; + setIsMenuOpen(false); + setIsSearchOpen(false); + }; + window.addEventListener('keydown', handleEscape); + return () => window.removeEventListener('keydown', handleEscape); + }, []); + const handlePlayPause = async () => { if (!audioRef.current || !currentTrack) return; if (isPlaying) { @@ -231,6 +254,11 @@ export default function App() { setDeferredInstallPrompt(null); }; + const navigateTo = (view: ViewState) => { + setCurrentView(view); + setIsMenuOpen(false); + }; + const formatTime = (time: number) => { if (isNaN(time)) return '00:00'; const m = Math.floor(time / 60); @@ -251,7 +279,7 @@ export default function App() { ) : ( - )} @@ -261,7 +289,7 @@ export default function App() { {currentView === 'library' ? 'Archive Sector 7G' : currentView === 'collection' ? 'Vault Status: Encrypted' : ''} - {!isStandalone && deferredInstallPrompt && ( @@ -276,10 +304,44 @@ export default function App() { + {isSearchOpen && ( +
+
+ setSearchQuery(event.target.value)} + placeholder="Search titles, authors, tags, descriptions..." + className="w-full rounded-md border border-primary/30 bg-slate-900 px-4 py-3 text-sm text-on-surface-variant outline-none transition-colors focus:border-primary" + /> +
+
+ )} + + {isMenuOpen && currentView !== 'details' && ( + <> + + + + + + )} + {/* Main Content Area */} -
+
{currentView === 'library' && ( - { setSelectedBook(b); setCurrentView('details'); }} /> + { setSelectedBook(b); setCurrentView('details'); setIsMenuOpen(false); }} /> )} {currentView === 'details' && selectedBook && ( playTrack(selectedBook, t)} /> @@ -299,7 +361,7 @@ export default function App() { /> )} {currentView === 'collection' && ( - { setSelectedBook(b); setCurrentView('details'); }} /> + { setSelectedBook(b); setCurrentView('details'); setIsMenuOpen(false); }} /> )}
@@ -352,24 +414,30 @@ function LibraryView({ books, onSelectBook }: { books: Book[], onSelectBook: (b:
- {books.map((book, idx) => ( -
onSelectBook(book)} className={`group cursor-pointer ${idx % 2 !== 0 ? 'md:translate-y-16' : ''}`}> -
- {book.title} -
-
- + {books.length === 0 ? ( +
+ No archive entries matched your search. +
+ ) : ( + books.map((book, idx) => ( +
onSelectBook(book)} className={`group cursor-pointer ${idx % 2 !== 0 ? 'md:translate-y-16' : ''}`}> +
+ {book.title} +
+
+ +
-
-
-
-

{book.title}

-

{book.year} • {book.author}

+
+
+

{book.title}

+

{book.year} • {book.author}

+
+ {book.tag}
- {book.tag}
-
- ))} + )) + )}
); @@ -562,23 +630,29 @@ function CollectionView({ books, onSelectBook }: { books: Book[], onSelectBook:
- {books.map(book => ( -
onSelectBook(book)} className="flex flex-col gap-3 cursor-pointer"> -
- {book.title} -
-
- + {books.length === 0 ? ( +
+ No collection items matched your search. +
+ ) : ( + books.map(book => ( +
onSelectBook(book)} className="flex flex-col gap-3 cursor-pointer"> +
+ {book.title} +
+
+ +
+
+
+
{book.title}
+

{book.author}

-
-
{book.title}
-

{book.author}

-
-
- ))} + )) + )}