From 15fb0f6c56951503b16524588bf5928afddd6e36 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:52:02 +0200 Subject: [PATCH] Theme store: version display + animated/static filter (#1104) * feat(themes): show theme version in the store and installed list * feat(themes): filter the theme store by animated / static * docs(changelog): add 1.49.0 entry for theme store version + animated filter --- CHANGELOG.md | 13 +++++++ src/components/settings/InstalledThemes.tsx | 9 ++++- src/components/settings/ThemeStoreSection.tsx | 37 ++++++++++++++++++- src/locales/de/settings.ts | 3 ++ src/locales/en/settings.ts | 3 ++ src/locales/es/settings.ts | 3 ++ src/locales/fr/settings.ts | 3 ++ src/locales/nb/settings.ts | 3 ++ src/locales/nl/settings.ts | 3 ++ src/locales/ro/settings.ts | 3 ++ src/locales/ru/settings.ts | 3 ++ src/locales/zh/settings.ts | 3 ++ 12 files changed, 83 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5edb54..58604bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > +## [1.49.0] + +## Added + +### Theme store — version numbers and an animated/static filter + +**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1104](https://github.com/Psychotoxical/psysonic/pull/1104)** + +* Theme versions now show in the store (next to the author) and under each installed community theme; when an update is available, the store shows the installed → available version. +* New store filter to show only animated themes or only static ones, next to the existing mode and sort controls. + + + ## [1.48.0] ## Added diff --git a/src/components/settings/InstalledThemes.tsx b/src/components/settings/InstalledThemes.tsx index 2405a041..8b06bd6a 100644 --- a/src/components/settings/InstalledThemes.tsx +++ b/src/components/settings/InstalledThemes.tsx @@ -33,6 +33,8 @@ interface Card { fixed: boolean; accessibility: boolean; animated: boolean; + /** Community themes carry a manifest version; fixed cores do not. */ + version?: string; } /** @@ -64,7 +66,7 @@ export function InstalledThemes() { ...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 { 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), version: it.version }; }), ]; @@ -102,6 +104,11 @@ export function InstalledThemes() { {c.label} + {c.version && ( + + v{c.version} + + )} {c.accessibility && ( ('all'); const [sortMode, setSortMode] = useState('newest'); + const [animFilter, setAnimFilter] = useState('all'); const [page, setPage] = useState(1); const [busyId, setBusyId] = useState(null); const [failedId, setFailedId] = useState(null); @@ -102,6 +104,8 @@ export function ThemeStoreSection() { const q = query.trim().toLowerCase(); const matched = themes.filter(th => { if (mode !== 'all' && th.mode !== mode) return false; + if (animFilter === 'animated' && !th.animated) return false; + if (animFilter === 'static' && th.animated) return false; if (!q) return true; return ( th.name.toLowerCase().includes(q) || @@ -115,11 +119,11 @@ export function ThemeStoreSection() { const byName = (a: RegistryTheme, b: RegistryTheme) => a.name.localeCompare(b.name); if (sortMode === 'name') return matched.sort(byName); return matched.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || '') || byName(a, b)); - }, [themes, query, mode, sortMode]); + }, [themes, query, mode, sortMode, animFilter]); // A changed filter can shrink the result set below the current page; reset to // the first page whenever the query or mode filter changes. - useEffect(() => { setPage(1); }, [query, mode, sortMode]); + useEffect(() => { setPage(1); }, [query, mode, sortMode, animFilter]); const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); // Clamp defensively so a stale `page` (e.g. after the registry shrank) never @@ -153,6 +157,12 @@ export function ThemeStoreSection() { { value: 'name', label: t('settings.themeStoreSortName') }, ]; + const animFilterOptions = [ + { value: 'all', label: t('settings.themeStoreAnimAll') }, + { value: 'animated', label: t('settings.themeStoreAnimAnimated') }, + { value: 'static', label: t('settings.themeStoreAnimStatic') }, + ]; + return ( {/* Submit-your-own-theme hint */} @@ -202,6 +212,23 @@ export function ThemeStoreSection() { lineHeight: 1, }} /> + setAnimFilter(v as AnimFilter)} + style={{ + width: 150, + flexShrink: 0, + alignSelf: 'stretch', + boxSizing: 'border-box', + padding: '0 var(--space-4)', + background: 'var(--input-bg)', + border: '1px solid var(--input-border)', + borderRadius: 'var(--radius-md)', + fontSize: 14, + lineHeight: 1, + }} + /> {modeBtns.map(b => ( {t('settings.themeStoreByAuthor', { author: th.author })} + {' · '} + {updateAvailable ? ( + <>v{inst!.version} → v{th.version}> + ) : ( + <>v{th.version}> + )} {th.description} diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index d1c78411..f2f71258 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -374,6 +374,9 @@ export const settings = { themeStoreEnlarge: 'Vorschau vergrößern', themeStoreSortNewest: 'Neueste', themeStoreSortName: 'Alphabetisch', + themeStoreAnimAll: 'Alle', + themeStoreAnimAnimated: 'Animiert', + themeStoreAnimStatic: 'Statisch', themeStoreByAuthor: 'von {{author}}', themeStoreLastChanged: 'Zuletzt geändert', themeMigrationNoticeTitle: 'Theme in den Store gezogen', diff --git a/src/locales/en/settings.ts b/src/locales/en/settings.ts index c6017cd7..c7322ab4 100644 --- a/src/locales/en/settings.ts +++ b/src/locales/en/settings.ts @@ -418,6 +418,9 @@ export const settings = { themeStoreEnlarge: 'Enlarge preview', themeStoreSortNewest: 'Newest', themeStoreSortName: 'Alphabetical', + themeStoreAnimAll: 'All', + themeStoreAnimAnimated: 'Animated', + themeStoreAnimStatic: 'Static', themeStoreByAuthor: 'by {{author}}', themeStoreLastChanged: 'Last changed', themeMigrationNoticeTitle: 'Theme moved to the Store', diff --git a/src/locales/es/settings.ts b/src/locales/es/settings.ts index cf987035..332c6379 100644 --- a/src/locales/es/settings.ts +++ b/src/locales/es/settings.ts @@ -372,6 +372,9 @@ export const settings = { themeStoreEnlarge: 'Ampliar vista previa', themeStoreSortNewest: 'Más recientes', themeStoreSortName: 'Alfabético', + themeStoreAnimAll: 'Todos', + themeStoreAnimAnimated: 'Animados', + themeStoreAnimStatic: 'Estáticos', themeStoreByAuthor: 'por {{author}}', themeStoreLastChanged: 'Última modificación', themeMigrationNoticeTitle: 'Tema movido a la tienda', diff --git a/src/locales/fr/settings.ts b/src/locales/fr/settings.ts index 8c2fbf7f..4facfe9e 100644 --- a/src/locales/fr/settings.ts +++ b/src/locales/fr/settings.ts @@ -360,6 +360,9 @@ export const settings = { themeStoreEnlarge: "Agrandir l'aperçu", themeStoreSortNewest: 'Plus récents', themeStoreSortName: 'Alphabétique', + themeStoreAnimAll: 'Tous', + themeStoreAnimAnimated: 'Animés', + themeStoreAnimStatic: 'Statiques', themeStoreByAuthor: 'par {{author}}', themeStoreLastChanged: 'Dernière modification', themeMigrationNoticeTitle: 'Thème déplacé vers la boutique', diff --git a/src/locales/nb/settings.ts b/src/locales/nb/settings.ts index fece0a9d..d50f2355 100644 --- a/src/locales/nb/settings.ts +++ b/src/locales/nb/settings.ts @@ -363,6 +363,9 @@ export const settings = { themeStoreEnlarge: 'Forstørr forhåndsvisning', themeStoreSortNewest: 'Nyeste', themeStoreSortName: 'Alfabetisk', + themeStoreAnimAll: 'Alle', + themeStoreAnimAnimated: 'Animerte', + themeStoreAnimStatic: 'Statiske', themeStoreByAuthor: 'av {{author}}', themeStoreLastChanged: 'Sist endret', themeMigrationNoticeTitle: 'Temaet er flyttet til butikken', diff --git a/src/locales/nl/settings.ts b/src/locales/nl/settings.ts index 95d9520a..d6e8fe2c 100644 --- a/src/locales/nl/settings.ts +++ b/src/locales/nl/settings.ts @@ -360,6 +360,9 @@ export const settings = { themeStoreEnlarge: 'Voorbeeld vergroten', themeStoreSortNewest: 'Nieuwste', themeStoreSortName: 'Alfabetisch', + themeStoreAnimAll: 'Alle', + themeStoreAnimAnimated: 'Geanimeerd', + themeStoreAnimStatic: 'Statisch', themeStoreByAuthor: 'door {{author}}', themeStoreLastChanged: 'Laatst gewijzigd', themeMigrationNoticeTitle: 'Thema verplaatst naar de store', diff --git a/src/locales/ro/settings.ts b/src/locales/ro/settings.ts index 19025f92..f2615cdb 100644 --- a/src/locales/ro/settings.ts +++ b/src/locales/ro/settings.ts @@ -376,6 +376,9 @@ export const settings = { themeStoreEnlarge: 'Mărește previzualizarea', themeStoreSortNewest: 'Cele mai noi', themeStoreSortName: 'Alfabetic', + themeStoreAnimAll: 'Toate', + themeStoreAnimAnimated: 'Animate', + themeStoreAnimStatic: 'Statice', themeStoreByAuthor: 'de {{author}}', themeStoreLastChanged: 'Ultima modificare', themeMigrationNoticeTitle: 'Tema a fost mutată în magazin', diff --git a/src/locales/ru/settings.ts b/src/locales/ru/settings.ts index fde12880..3e8ccc52 100644 --- a/src/locales/ru/settings.ts +++ b/src/locales/ru/settings.ts @@ -428,6 +428,9 @@ export const settings = { themeStoreEnlarge: 'Увеличить превью', themeStoreSortNewest: 'Новые', themeStoreSortName: 'По алфавиту', + themeStoreAnimAll: 'Все', + themeStoreAnimAnimated: 'Анимированные', + themeStoreAnimStatic: 'Статичные', themeStoreByAuthor: 'от {{author}}', themeStoreLastChanged: 'Изменён', themeMigrationNoticeTitle: 'Тема перенесена в магазин', diff --git a/src/locales/zh/settings.ts b/src/locales/zh/settings.ts index b777f408..9d0e0f3b 100644 --- a/src/locales/zh/settings.ts +++ b/src/locales/zh/settings.ts @@ -359,6 +359,9 @@ export const settings = { themeStoreEnlarge: '放大预览', themeStoreSortNewest: '最新', themeStoreSortName: '按字母', + themeStoreAnimAll: '全部', + themeStoreAnimAnimated: '动态', + themeStoreAnimStatic: '静态', themeStoreByAuthor: '作者:{{author}}', themeStoreLastChanged: '最后更新', themeMigrationNoticeTitle: '主题已移至商店',