import React, { useState, useEffect } from 'react'; import ConfigurationModal from './components/ConfigurationModal'; import ServerWidget from './components/ServerWidget'; import { DEFAULT_CATEGORIES } from './constants'; import { Category, Website, Wallpaper, Config } from './types'; import WebsiteEditModal from './components/WebsiteEditModal'; import CategoryEditModal from './components/CategoryEditModal'; 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 { baseWallpapers } from './components/utils/baseWallpapers'; const defaultConfig: Config = { title: 'Vision Start', subtitle: 'Your personal portal to the web.', backgroundUrls: ['https://i.imgur.com/C6ynAtX.jpeg'], wallpaperFrequency: '1d', wallpaperBlur: 0, wallpaperBrightness: 100, wallpaperOpacity: 100, titleSize: 'medium', subtitleSize: 'medium', alignment: 'middle', horizontalAlignment: 'middle', clock: { enabled: true, size: 'medium', font: 'Helvetica', format: 'h:mm A', }, serverWidget: { enabled: false, pingFrequency: 15, servers: [], }, }; const App: React.FC = () => { const [categories, setCategories] = useState(() => { try { const storedCategories = localStorage.getItem('categories'); if (storedCategories) { return JSON.parse(storedCategories); } } catch (error) { console.error('Error parsing categories from localStorage', error); } return DEFAULT_CATEGORIES; }); 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(() => { try { const storedConfig = localStorage.getItem('config'); if (storedConfig) { const parsedConfig = JSON.parse(storedConfig); if (!parsedConfig.backgroundUrls) { parsedConfig.backgroundUrls = [parsedConfig.backgroundUrl].filter(Boolean); } return { ...defaultConfig, ...parsedConfig }; } } catch (error) { console.error('Error parsing config from localStorage', error); } return { ...defaultConfig }; }); const [userWallpapers, setUserWallpapers] = useState(() => { const storedUserWallpapers = localStorage.getItem('userWallpapers'); return storedUserWallpapers ? JSON.parse(storedUserWallpapers) : []; }); const [currentWallpaper, setCurrentWallpaper] = useState(''); const allWallpapers = [...baseWallpapers, ...userWallpapers]; useEffect(() => { const getFrequencyInMs = (frequency: string) => { const value = parseInt(frequency.slice(0, -1)); const unit = frequency.slice(-1); if (unit === 'h') return value * 60 * 60 * 1000; if (unit === 'd') return value * 24 * 60 * 60 * 1000; return 24 * 60 * 60 * 1000; // Default to 1 day }; const wallpaperState = JSON.parse(localStorage.getItem('wallpaperState') || '{}'); const lastChanged = wallpaperState.lastChanged ? new Date(wallpaperState.lastChanged).getTime() : 0; const frequency = getFrequencyInMs(config.wallpaperFrequency); const updateWallpaper = () => { const availableWallpapers = allWallpapers.filter(w => config.backgroundUrls.includes(w.url || w.base64)); if (availableWallpapers.length > 0) { const currentIndex = availableWallpapers.findIndex(w => (w.url || w.base64) === wallpaperState.current); const nextIndex = (currentIndex + 1) % availableWallpapers.length; const newWallpaper = availableWallpapers[nextIndex]; const newWallpaperUrl = newWallpaper.url || newWallpaper.base64; setCurrentWallpaper(newWallpaperUrl || ''); localStorage.setItem('wallpaperState', JSON.stringify({ current: newWallpaper.name, lastChanged: new Date().toISOString() })); } else { setCurrentWallpaper(''); } }; if (Date.now() - lastChanged > frequency) { updateWallpaper(); } else { const currentWallpaperName = wallpaperState.current; const wallpaper = allWallpapers.find(w => w.name === currentWallpaperName); if (wallpaper) { setCurrentWallpaper(wallpaper.url || wallpaper.base64 || ''); } else { const firstWallpaperUrl = config.backgroundUrls[0] || ''; const firstWallpaper = allWallpapers.find(w => (w.url || w.base64) === firstWallpaperUrl); setCurrentWallpaper(firstWallpaperUrl); if (firstWallpaper) { localStorage.setItem('wallpaperState', JSON.stringify({ current: firstWallpaper.name, lastChanged: new Date().toISOString() })); } } } }, [config.backgroundUrls, config.wallpaperFrequency, allWallpapers]); useEffect(() => { localStorage.setItem('categories', JSON.stringify(categories)); localStorage.setItem('config', JSON.stringify(config)); }, [categories, config]); const handleSaveConfig = (newConfig: any) => { setConfig(newConfig); setIsConfigModalOpen(false); }; const handleSaveWebsite = (website: Partial) => { if (editingWebsite) { const newCategories = categories.map(category => ({ ...category, websites: category.websites.map(w => w.id === website.id ? { ...w, ...website } : w ), })); setCategories(newCategories); setEditingWebsite(null); } else if (addingWebsite) { const newWebsite: Website = { id: Date.now().toString(), name: website.name || '', url: website.url || '', icon: website.icon || '', categoryId: addingWebsite.id, }; const newCategories = categories.map(category => category.id === addingWebsite.id ? { ...category, websites: [...category.websites, newWebsite] } : category ); setCategories(newCategories); setAddingWebsite(null); } }; const handleSaveCategory = (name: string) => { if (editingCategory) { const newCategories = categories.map(category => category.id === editingCategory.id ? { ...category, name } : category ); setCategories(newCategories); } else { const newCategory: Category = { id: Date.now().toString(), name, websites: [], }; setCategories([...categories, newCategory]); } setEditingCategory(null); setIsCategoryModalOpen(false); }; const handleDeleteWebsite = () => { if (!editingWebsite) return; const newCategories = categories.map(category => ({ ...category, websites: category.websites.filter(w => w.id !== editingWebsite.id), })); setCategories(newCategories); setEditingWebsite(null); }; const handleDeleteCategory = () => { if (!editingCategory) return; const newCategories = categories.filter(c => c.id !== editingCategory.id); setCategories(newCategories); setEditingCategory(null); setIsCategoryModalOpen(false); }; const handleMoveWebsite = (website: Website, direction: 'left' | 'right') => { const categoryIndex = categories.findIndex(c => c.id === website.categoryId); if (categoryIndex === -1) return; const category = categories[categoryIndex]; const websiteIndex = category.websites.findIndex(w => w.id === website.id); if (websiteIndex === -1) return; const newCategories = [...categories]; const newWebsites = [...category.websites]; const [movedWebsite] = newWebsites.splice(websiteIndex, 1); if (direction === 'left') { const newCategoryIndex = (categoryIndex - 1 + categories.length) % categories.length; newCategories[categoryIndex] = { ...category, websites: newWebsites }; const destCategory = newCategories[newCategoryIndex]; const destWebsites = [...destCategory.websites, { ...movedWebsite, categoryId: destCategory.id }]; newCategories[newCategoryIndex] = { ...destCategory, websites: destWebsites }; } else { const newCategoryIndex = (categoryIndex + 1) % categories.length; newCategories[categoryIndex] = { ...category, websites: newWebsites }; const destCategory = newCategories[newCategoryIndex]; const destWebsites = [...destCategory.websites, { ...movedWebsite, categoryId: destCategory.id }]; newCategories[newCategoryIndex] = { ...destCategory, websites: destWebsites }; } setCategories(newCategories); }; const getAlignmentClass = (alignment: string) => { switch (alignment) { case 'top': return 'justify-start'; case 'middle': return 'justify-center'; case 'bottom': return 'justify-end'; default: return 'justify-center'; } }; const getHorizontalAlignmentClass = (alignment: string) => { switch (alignment) { case 'left': return 'justify-start'; case 'middle': return 'justify-center'; case 'right': return 'justify-end'; default: return 'justify-center'; } }; 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} /> )}
); } export default App;