import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react'; import ServerWidget from './components/ServerWidget'; import { Category, Website, Config } from './types'; import Header from './components/layout/Header'; import EditButton from './components/layout/EditButton'; import ConfigurationButton from './components/layout/ConfigurationButton'; import CategoryGroup from './components/layout/CategoryGroup'; import Wallpaper from './components/Wallpaper'; import { ConfigurationService } from './components/services/ConfigurationService'; import { getAlignmentClass } from './components/utils/styleUtils'; import { getRandomWallpaperIndex, loadWallpaperState, saveWallpaperState } from './components/utils/wallpaperUtils'; import { PlusIcon } from './components/icons'; import { createId } from './components/utils/idUtils'; import { pruneCachedIconsFromChromeStorageLocal } from './components/utils/StorageLocalManager'; const ConfigurationModal = lazy(() => import('./components/ConfigurationModal')); const WebsiteEditModal = lazy(() => import('./components/WebsiteEditModal')); const CategoryEditModal = lazy(() => import('./components/CategoryEditModal')); const App: React.FC = () => { const [categories, setCategories] = useState(() => ConfigurationService.loadCategories()); const [isEditing, setIsEditing] = useState(false); const [isConfigModalOpen, setIsConfigModalOpen] = useState(false); const [editingWebsite, setEditingWebsite] = useState(null); const [addingWebsite, setAddingWebsite] = useState(null); const [editingCategory, setEditingCategory] = useState(null); const [isCategoryModalOpen, setIsCategoryModalOpen] = useState(false); const [config, setConfig] = useState(() => ConfigurationService.loadConfig()); const [wallpaperVersion, setWallpaperVersion] = useState(0); const categoriesRef = useRef(categories); categoriesRef.current = categories; useEffect(() => { const timer = setTimeout(() => { const icons = categoriesRef.current.flatMap(category => category.websites.map(website => website.icon)); void pruneCachedIconsFromChromeStorageLocal(icons); }, 5000); return () => clearTimeout(timer); }, []); useEffect(() => { ConfigurationService.saveConfig(config); }, [config]); useEffect(() => { try { localStorage.setItem('categories', JSON.stringify(categories)); } catch (error) { console.error('Error saving categories to localStorage', error); } }, [categories]); const handleSaveConfig = useCallback((newConfig: Config) => { setConfig(newConfig); setIsConfigModalOpen(false); }, []); const handleWallpaperChange = useCallback((newConfig: Partial) => { setConfig(prev => ({ ...prev, ...newConfig })); }, []); const handleRandomWallpaper = useCallback(() => { const names = config.currentWallpapers; if (names.length === 0) return; try { const { currentIndex } = loadWallpaperState(names); const randomIndex = getRandomWallpaperIndex(names.length, currentIndex); saveWallpaperState(names, randomIndex, Date.now()); } catch (error) { console.error('Error randomizing wallpaper state', error); } setWallpaperVersion(v => v + 1); }, [config.currentWallpapers]); const handleSaveWebsite = useCallback((website: Partial) => { if (editingWebsite) { const idToUpdate = website.id ?? editingWebsite.id; setCategories(previous => previous.map(category => category.id === editingWebsite.categoryId ? { ...category, websites: category.websites.map(w => w.id === idToUpdate ? { ...w, ...website, id: idToUpdate, categoryId: category.id } : w ), } : category)); setEditingWebsite(null); } else if (addingWebsite) { const newWebsite: Website = { id: createId(), name: website.name || '', url: website.url || '', icon: website.icon || '', categoryId: addingWebsite.id, }; setCategories(previous => previous.map(category => category.id === addingWebsite.id ? { ...category, websites: [...category.websites, newWebsite] } : category )); setAddingWebsite(null); } }, [editingWebsite, addingWebsite]); const handleSaveCategory = useCallback((name: string) => { if (editingCategory) { setCategories(previous => previous.map(category => category.id === editingCategory.id ? { ...category, name } : category )); } else { const newCategory: Category = { id: createId(), name, websites: [], }; setCategories(previous => [...previous, newCategory]); } setEditingCategory(null); setIsCategoryModalOpen(false); }, [editingCategory]); const handleDeleteWebsite = useCallback(() => { if (!editingWebsite) return; setCategories(previous => previous.map(category => category.id === editingWebsite.categoryId ? { ...category, websites: category.websites.filter(w => w.id !== editingWebsite.id), } : category)); setEditingWebsite(null); }, [editingWebsite]); const handleDeleteCategory = useCallback(() => { if (!editingCategory) return; const websiteCount = editingCategory.websites.length; if (websiteCount > 0 && !confirm(`Delete "${editingCategory.name}" and its ${websiteCount} ${websiteCount === 1 ? 'website' : 'websites'}?`)) return; setCategories(previous => previous.filter(c => c.id !== editingCategory.id)); setEditingCategory(null); setIsCategoryModalOpen(false); }, [editingCategory]); const handleMoveWebsite = useCallback((website: Website, direction: 'left' | 'right') => { setCategories(previous => { const categoryIndex = previous.findIndex(cat => cat.id === website.categoryId); if (categoryIndex === -1) return previous; const category = previous[categoryIndex]; const websiteIndex = category.websites.findIndex(w => w.id === website.id); if (websiteIndex === -1) return previous; const targetIndex = direction === 'left' ? websiteIndex - 1 : websiteIndex + 1; if (targetIndex < 0 || targetIndex >= category.websites.length) return previous; const newWebsites = [...category.websites]; const [movedWebsite] = newWebsites.splice(websiteIndex, 1); newWebsites.splice(targetIndex, 0, movedWebsite); const newCategories = [...previous]; newCategories[categoryIndex] = { ...category, websites: newWebsites }; return newCategories; }); }, []); return (
setIsEditing(!isEditing)} /> setIsConfigModalOpen(true)} />
{categories.map((category) => ( ))} {isEditing && (
)}
{config.serverWidget.enabled && } {(editingWebsite || addingWebsite) && ( { setEditingWebsite(null); setAddingWebsite(null); }} onSave={handleSaveWebsite} onDelete={handleDeleteWebsite} /> )} {isCategoryModalOpen && ( { setEditingCategory(null); setIsCategoryModalOpen(false); }} onSave={handleSaveCategory} onDelete={handleDeleteCategory} /> )} {isConfigModalOpen && ( setIsConfigModalOpen(false)} onSave={handleSaveConfig} onWallpaperChange={handleWallpaperChange} onRandomWallpaper={handleRandomWallpaper} /> )}
); } export default App;