import { useMemo, useState } from 'react'; import { Check, RefreshCw, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useThemeStore } from '../../store/themeStore'; import { useInstalledThemesStore } from '../../store/installedThemesStore'; import { uninstallTheme } from '../../utils/themes/uninstallTheme'; import { installThemeFromRegistry } from '../../utils/themes/installThemeFromRegistry'; import { useThemeUpdates } from '../../hooks/useThemeUpdates'; import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk'; import { showToast } from '../../utils/ui/toast'; import { AnimatedThemeBadge } from './AnimatedThemeBadge'; import { FIXED_THEMES } from './fixedThemes'; /** Pull a 3-band swatch (bg / card / accent) out of an installed theme's CSS. */ function swatch(css: string): { bg: string; card: string; accent: string } { const read = (name: string, fallback: string) => { const m = css.match(new RegExp(`--${name}\\s*:\\s*([^;]+);`)); return m ? m[1].trim() : fallback; }; return { bg: read('bg-app', '#1e1e2e'), card: read('bg-card', '#313244'), accent: read('accent', '#cba6f7'), }; } interface Card { id: string; label: string; bg: string; card: string; accent: string; fixed: boolean; accessibility: boolean; animated: boolean; } /** * Flat card grid of the user's available themes: the fixed cores plus every * installed community theme. Click a card to apply it; community themes carry an * uninstall control (the fixed cores do not). No accordion — this page is only * about themes, so everything is shown at once. */ export function InstalledThemes() { const { t } = useTranslation(); const active = useThemeStore(s => s.theme); const setTheme = useThemeStore(s => s.setTheme); const installed = useInstalledThemesStore(s => s.themes); const animRisk = useThemeAnimationRisk(); const updates = useThemeUpdates(); const updateById = useMemo(() => new Map(updates.map(u => [u.id, u])), [updates]); const [updatingId, setUpdatingId] = useState(null); const handleUpdate = async (id: string) => { const th = updateById.get(id); if (!th) return; setUpdatingId(id); const result = await installThemeFromRegistry(th); setUpdatingId(null); if (result !== 'ok') showToast(t('settings.themeStoreInstallFailed'), 4000, 'error'); }; const cards: Card[] = [ ...FIXED_THEMES.map(f => ({ id: f.id, label: f.label, bg: f.bg, card: f.card, accent: f.accent, fixed: true, accessibility: !!f.accessibility, animated: false })), ...installed.map(it => { const s = swatch(it.css); return { id: it.id, label: it.name, bg: s.bg, card: s.card, accent: s.accent, fixed: false, accessibility: (it.tags || []).includes('accessibility'), animated: /@(?:-[a-z]+-)?keyframes\b/i.test(it.css) }; }), ]; return (
{cards.map(c => { const isActive = active === c.id; return (
{!c.fixed && ( )} {updateById.has(c.id) && ( )}
); })}
); }