import React, { useState, useEffect } from 'react'; import WebsiteTile from './components/WebsiteTile'; import ConfigurationModal from './components/ConfigurationModal'; import Clock from './components/Clock'; import ServerWidget from './components/ServerWidget'; import { DEFAULT_CATEGORIES } from './constants'; import { Category, Website, Wallpaper } from './types'; import Dropdown from './components/Dropdown'; import WebsiteEditModal from './components/WebsiteEditModal'; import CategoryEditModal from './components/CategoryEditModal'; import { PlusCircle, Pencil } from 'lucide-react'; import { baseWallpapers } from './components/utils/baseWallpapers'; const defaultConfig = { title: 'Vision Start', subtitle: 'Your personal portal to the web.', backgroundUrl: 'https://i.imgur.com/C6ynAtX.jpeg', 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) { return { ...defaultConfig, ...JSON.parse(storedConfig) }; } } 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 allWallpapers = [...baseWallpapers, ...userWallpapers]; const selectedWallpaper = allWallpapers.find(w => w.url === config.backgroundUrl || w.base64 === config.backgroundUrl); 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 getClockSizeClass = (size: string) => { switch (size) { case 'tiny': return 'text-3xl'; case 'small': return 'text-4xl'; case 'medium': return 'text-5xl'; case 'large': return 'text-6xl'; default: return 'text-5xl'; } }; const getTitleSizeClass = (size: string) => { switch (size) { case 'tiny': return 'text-4xl'; case 'small': return 'text-5xl'; case 'medium': return 'text-6xl'; case 'large': return 'text-7xl'; default: return 'text-6xl'; } }; const getSubtitleSizeClass = (size: string) => { switch (size) { case 'tiny': return 'text-lg'; case 'small': return 'text-xl'; case 'medium': return 'text-2xl'; case 'large': return 'text-3xl'; default: return 'text-2xl'; } }; 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 (
{/* Absolute top-center Clock */} {config.clock.enabled && (
)}
{(config.title || config.subtitle) && (

{config.title}

{config.subtitle}

)}
{categories.map((category) => (

{category.name}

{isEditing && ( )}
{category.websites.map((website) => ( ))} {isEditing && ( )}
))} {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;