mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(themes): offline banner for the Theme Store (#1013)
* fix(themes): show an offline banner when the Theme Store is unavailable When the registry can't be fetched and no catalogue is cached, the store now shows a clear offline banner (icon, message, retry) in place of the list, and hides the search/filter toolbar — there is nothing to browse. The cached-catalogue fallback with its offline indicator is unchanged, so the store still works offline when a catalogue was previously cached. * docs(themes): note the offline-banner PR in the Theme Store entry Add PR #1013 to the still-unreleased Theme Store changelog and credits entry, alongside the other follow-ups.
This commit is contained in:
+1
-1
@@ -56,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Themes — community Theme Store
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1009](https://github.com/Psychotoxical/psysonic/pull/1009), [#1011](https://github.com/Psychotoxical/psysonic/pull/1011), [#1012](https://github.com/Psychotoxical/psysonic/pull/1012)**
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1009](https://github.com/Psychotoxical/psysonic/pull/1009), [#1011](https://github.com/Psychotoxical/psysonic/pull/1011), [#1012](https://github.com/Psychotoxical/psysonic/pull/1012), [#1013](https://github.com/Psychotoxical/psysonic/pull/1013)**
|
||||
|
||||
* New **Settings → Themes** tab: pick a theme, set the day/night scheduler, and browse a built-in **Theme Store** to install, update and uninstall community themes — with search, a dark/light filter, and full-size thumbnail previews.
|
||||
* The app now bundles six core themes (Catppuccin Mocha & Latte, Kanagawa Wave, Stark HUD, and the colour-blind-safe Vision Dark / Vision Navy); every other palette installs on demand from the [psysonic-themes](https://github.com/Psysonic/psysonic-themes) repo. Installed themes are saved locally and apply instantly at startup, even offline.
|
||||
|
||||
@@ -143,4 +143,14 @@ describe('ThemeStoreSection — pagination & refresh', () => {
|
||||
await waitFor(() => expect(container.querySelector('.animate-spin')).toBeNull());
|
||||
expect(rows(container)).toHaveLength(12);
|
||||
});
|
||||
|
||||
it('shows an offline banner and hides the toolbar when the registry is unavailable', async () => {
|
||||
fetchRegistryMock.mockRejectedValue(new Error('offline'));
|
||||
renderWithProviders(<ThemeStoreSection />);
|
||||
|
||||
expect(await screen.findByText('The Theme Store is offline')).toBeInTheDocument();
|
||||
// No catalogue to browse → the search/filter toolbar is not rendered.
|
||||
expect(screen.queryByRole('searchbox')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { Check, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, WifiOff } from 'lucide-react';
|
||||
import { open as openUrl } from '@tauri-apps/plugin-shell';
|
||||
import CoverLightbox from '../CoverLightbox';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
@@ -159,7 +159,9 @@ export function ThemeStoreSection() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Toolbar: search + mode filter + refresh */}
|
||||
{/* Toolbar: search + mode filter + refresh. Hidden when offline with no
|
||||
catalogue to browse — the offline banner below stands in for it. */}
|
||||
{!error && (
|
||||
<div ref={topRef} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', marginBottom: '1rem', scrollMarginTop: 8 }}>
|
||||
<input
|
||||
type="search"
|
||||
@@ -195,6 +197,7 @@ export function ThemeStoreSection() {
|
||||
<RefreshCw size={15} className={refreshing ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && stale && (
|
||||
<div className="settings-hint settings-hint-info" role="status" style={{ marginBottom: '0.75rem' }}>
|
||||
@@ -207,9 +210,28 @@ export function ThemeStoreSection() {
|
||||
)}
|
||||
|
||||
{!loading && error && (
|
||||
<div role="alert" style={{ fontSize: 13, color: 'var(--text-muted)' }}>
|
||||
<p style={{ marginBottom: 8 }}>{t('settings.themeStoreError')}</p>
|
||||
<button className="btn btn-ghost" onClick={() => load(true)}>{t('settings.themeStoreRetry')}</button>
|
||||
<div
|
||||
role="alert"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
gap: 12,
|
||||
padding: '28px 16px',
|
||||
border: '1px dashed var(--border)',
|
||||
borderRadius: 'var(--radius-md, 10px)',
|
||||
background: 'var(--bg-elevated)',
|
||||
}}
|
||||
>
|
||||
<WifiOff size={28} style={{ color: 'var(--text-muted)' }} aria-hidden="true" />
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>{t('settings.themeStoreOfflineTitle')}</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{t('settings.themeStoreError')}</div>
|
||||
</div>
|
||||
<button className="btn btn-ghost" onClick={() => load(true)} disabled={refreshing}>
|
||||
{t('settings.themeStoreRetry')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ const CONTRIBUTOR_ENTRIES = [
|
||||
'Performance Probe: Monitor/Toggles redesign, live CPU/RSS/thread metrics, overlay pins and sparklines, macOS snapshots (PR #890)',
|
||||
'Performance Probe: opt-in thread-group CPU poll toggle and includeThreadGroups IPC fix (PR #891)',
|
||||
'Queue: switchable display mode — Queue (upcoming only) vs Playlist (full list), with header toggle and settings entry (PR #922)',
|
||||
'Community Theme Store: semantic-token refactor, dedicated Themes tab with day/night scheduler, install/update/uninstall, local .zip import with full contract validation, and 80+ palettes moved to an on-demand CDN repo (PR #1009, #1011, #1012)',
|
||||
'Community Theme Store: semantic-token refactor, dedicated Themes tab with day/night scheduler, install/update/uninstall, local .zip import with full contract validation, and 80+ palettes moved to an on-demand CDN repo (PR #1009, #1011, #1012, #1013)',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -336,6 +336,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Themes werden geladen…',
|
||||
themeStoreError: 'Theme-Store konnte nicht geladen werden. Prüfe deine Verbindung.',
|
||||
themeStoreRetry: 'Erneut versuchen',
|
||||
themeStoreOfflineTitle: 'Der Theme-Store ist offline',
|
||||
themeStoreEmpty: 'Keine Themes passen zu deinen Filtern.',
|
||||
themeStoreActive: 'Aktiv',
|
||||
themeStoreEnlarge: 'Vorschau vergrößern',
|
||||
|
||||
@@ -403,6 +403,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Loading themes…',
|
||||
themeStoreError: 'Could not load the theme store. Check your connection.',
|
||||
themeStoreRetry: 'Retry',
|
||||
themeStoreOfflineTitle: 'The Theme Store is offline',
|
||||
themeStoreEmpty: 'No themes match your filters.',
|
||||
themeStoreActive: 'Active',
|
||||
themeStoreEnlarge: 'Enlarge preview',
|
||||
|
||||
@@ -334,6 +334,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Cargando temas…',
|
||||
themeStoreError: 'No se pudo cargar la tienda de temas. Comprueba tu conexión.',
|
||||
themeStoreRetry: 'Reintentar',
|
||||
themeStoreOfflineTitle: 'La tienda de temas está sin conexión',
|
||||
themeStoreEmpty: 'Ningún tema coincide con tus filtros.',
|
||||
themeStoreActive: 'Activo',
|
||||
themeStoreEnlarge: 'Ampliar vista previa',
|
||||
|
||||
@@ -332,6 +332,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Chargement des thèmes…',
|
||||
themeStoreError: 'Impossible de charger la boutique de thèmes. Vérifiez votre connexion.',
|
||||
themeStoreRetry: 'Réessayer',
|
||||
themeStoreOfflineTitle: 'Le Theme Store est hors ligne',
|
||||
themeStoreEmpty: 'Aucun thème ne correspond à vos filtres.',
|
||||
themeStoreActive: 'Actif',
|
||||
themeStoreEnlarge: "Agrandir l'aperçu",
|
||||
|
||||
@@ -335,6 +335,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Laster temaer…',
|
||||
themeStoreError: 'Kunne ikke laste temabutikken. Sjekk tilkoblingen din.',
|
||||
themeStoreRetry: 'Prøv igjen',
|
||||
themeStoreOfflineTitle: 'Theme Store er frakoblet',
|
||||
themeStoreEmpty: 'Ingen temaer samsvarer med filtrene dine.',
|
||||
themeStoreActive: 'Aktiv',
|
||||
themeStoreEnlarge: 'Forstørr forhåndsvisning',
|
||||
|
||||
@@ -332,6 +332,7 @@ export const settings = {
|
||||
themeStoreLoading: "Thema's laden…",
|
||||
themeStoreError: 'Kan de themawinkel niet laden. Controleer je verbinding.',
|
||||
themeStoreRetry: 'Opnieuw proberen',
|
||||
themeStoreOfflineTitle: 'De Theme Store is offline',
|
||||
themeStoreEmpty: "Geen thema's komen overeen met je filters.",
|
||||
themeStoreActive: 'Actief',
|
||||
themeStoreEnlarge: 'Voorbeeld vergroten',
|
||||
|
||||
@@ -338,6 +338,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Se încarcă temele…',
|
||||
themeStoreError: 'Magazinul de teme nu a putut fi încărcat. Verifică conexiunea.',
|
||||
themeStoreRetry: 'Reîncearcă',
|
||||
themeStoreOfflineTitle: 'Theme Store este offline',
|
||||
themeStoreEmpty: 'Nicio temă nu corespunde filtrelor tale.',
|
||||
themeStoreActive: 'Activă',
|
||||
themeStoreEnlarge: 'Mărește previzualizarea',
|
||||
|
||||
@@ -414,6 +414,7 @@ export const settings = {
|
||||
themeStoreLoading: 'Загрузка тем…',
|
||||
themeStoreError: 'Не удалось загрузить магазин тем. Проверьте подключение.',
|
||||
themeStoreRetry: 'Повторить',
|
||||
themeStoreOfflineTitle: 'Магазин тем недоступен',
|
||||
themeStoreEmpty: 'Нет тем, соответствующих фильтрам.',
|
||||
themeStoreActive: 'Активна',
|
||||
themeStoreEnlarge: 'Увеличить превью',
|
||||
|
||||
@@ -331,6 +331,7 @@ export const settings = {
|
||||
themeStoreLoading: '正在加载主题…',
|
||||
themeStoreError: '无法加载主题商店。请检查网络连接。',
|
||||
themeStoreRetry: '重试',
|
||||
themeStoreOfflineTitle: '主题商店已离线',
|
||||
themeStoreEmpty: '没有符合筛选条件的主题。',
|
||||
themeStoreActive: '使用中',
|
||||
themeStoreEnlarge: '放大预览',
|
||||
|
||||
Reference in New Issue
Block a user