fix(offline): replace blocking overlay with slim banner; add server settings link (#170)

When offline with no cached content, the full-screen OfflineOverlay blocked
all navigation including Settings, making it impossible to fix a broken
server config. Replace it with the same slim banner used in offline-cache mode.

- OfflineBanner now handles both cases: cache (existing) and no-cache (new)
- No-cache banner shows server name and a direct link to Settings → Server tab
- OfflineOverlay component is no longer used (import removed from App.tsx)
- All 8 locales: added offlineNoCacheBanner and serverSettings keys

Fixes #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-13 17:52:17 +02:00
parent 91d4cb58b8
commit dba89cc4e4
10 changed files with 37 additions and 13 deletions
+2 -10
View File
@@ -45,7 +45,6 @@ import { DragDropProvider } from './contexts/DragDropContext';
import TooltipPortal from './components/TooltipPortal'; import TooltipPortal from './components/TooltipPortal';
import ConnectionIndicator from './components/ConnectionIndicator'; import ConnectionIndicator from './components/ConnectionIndicator';
import LastfmIndicator from './components/LastfmIndicator'; import LastfmIndicator from './components/LastfmIndicator';
import OfflineOverlay from './components/OfflineOverlay';
import OfflineBanner from './components/OfflineBanner'; import OfflineBanner from './components/OfflineBanner';
import OfflineLibrary from './pages/OfflineLibrary'; import OfflineLibrary from './pages/OfflineLibrary';
import Genres from './pages/Genres'; import Genres from './pages/Genres';
@@ -341,17 +340,10 @@ function AppShell() {
{isQueueVisible ? <PanelRightClose size={18} /> : <PanelRight size={18} />} {isQueueVisible ? <PanelRightClose size={18} /> : <PanelRight size={18} />}
</button> </button>
</header> </header>
{connStatus === 'disconnected' && hasOfflineContent && ( {connStatus === 'disconnected' && (
<OfflineBanner onRetry={connRetry} isChecking={connRetrying} /> <OfflineBanner onRetry={connRetry} isChecking={connRetrying} showSettingsLink={!hasOfflineContent} serverName={serverName} />
)} )}
<div className="content-body" style={{ padding: 0, position: 'relative' }}> <div className="content-body" style={{ padding: 0, position: 'relative' }}>
{connStatus === 'disconnected' && !hasOfflineContent && (
<OfflineOverlay
serverName={serverName}
onRetry={connRetry}
isChecking={connRetrying}
/>
)}
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/albums" element={<Albums />} /> <Route path="/albums" element={<Albums />} />
+19 -3
View File
@@ -1,18 +1,34 @@
import React from 'react'; import React from 'react';
import { WifiOff, RefreshCw } from 'lucide-react'; import { WifiOff, RefreshCw, Settings } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
interface Props { interface Props {
onRetry: () => void; onRetry: () => void;
isChecking: boolean; isChecking: boolean;
showSettingsLink?: boolean;
serverName?: string;
} }
export default function OfflineBanner({ onRetry, isChecking }: Props) { export default function OfflineBanner({ onRetry, isChecking, showSettingsLink, serverName }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const message = showSettingsLink
? t('connection.offlineNoCacheBanner', { server: serverName })
: t('connection.offlineModeBanner');
return ( return (
<div className="offline-banner"> <div className="offline-banner">
<WifiOff size={14} /> <WifiOff size={14} />
<span>{t('connection.offlineModeBanner')}</span> <span>{message}</span>
{showSettingsLink && (
<button
className="offline-banner-retry"
onClick={() => navigate('/settings', { state: { tab: 'server' } })}
>
<Settings size={12} />
{t('connection.serverSettings')}
</button>
)}
<button <button
className="offline-banner-retry" className="offline-banner-retry"
onClick={onRetry} onClick={onRetry}
+2
View File
@@ -337,6 +337,7 @@ export const deTranslation = {
offlineTitle: 'Keine Serververbindung', offlineTitle: 'Keine Serververbindung',
offlineSubtitle: '{{server}} ist nicht erreichbar. Prüfe Netzwerk oder Server.', offlineSubtitle: '{{server}} ist nicht erreichbar. Prüfe Netzwerk oder Server.',
offlineModeBanner: 'Offline-Modus — Wiedergabe aus lokalem Cache', offlineModeBanner: 'Offline-Modus — Wiedergabe aus lokalem Cache',
offlineNoCacheBanner: 'Keine Serververbindung — {{server}} nicht erreichbar',
offlineLibraryTitle: 'Offline-Bibliothek', offlineLibraryTitle: 'Offline-Bibliothek',
offlineLibraryEmpty: 'Noch keine Alben gecacht. Online gehen, Album öffnen und "Offline verfügbar machen" klicken.', offlineLibraryEmpty: 'Noch keine Alben gecacht. Online gehen, Album öffnen und "Offline verfügbar machen" klicken.',
offlineAlbumCount: '{{n}} Album', offlineAlbumCount: '{{n}} Album',
@@ -346,6 +347,7 @@ export const deTranslation = {
offlineFilterPlaylists: 'Playlists', offlineFilterPlaylists: 'Playlists',
offlineFilterArtists: 'Diskografien', offlineFilterArtists: 'Diskografien',
retry: 'Erneut versuchen', retry: 'Erneut versuchen',
serverSettings: 'Server-Einstellungen',
lastfmConnected: 'Last.fm verbunden als @{{user}}', lastfmConnected: 'Last.fm verbunden als @{{user}}',
lastfmSessionInvalid: 'Session ungültig — klicken zum Neu-Verbinden', lastfmSessionInvalid: 'Session ungültig — klicken zum Neu-Verbinden',
}, },
+2
View File
@@ -338,6 +338,7 @@ export const enTranslation = {
offlineTitle: 'No server connection', offlineTitle: 'No server connection',
offlineSubtitle: 'Cannot reach {{server}}. Check your network or server.', offlineSubtitle: 'Cannot reach {{server}}. Check your network or server.',
offlineModeBanner: 'Offline Mode — playing from local cache', offlineModeBanner: 'Offline Mode — playing from local cache',
offlineNoCacheBanner: 'No server connection — cannot reach {{server}}',
offlineLibraryTitle: 'Offline Library', offlineLibraryTitle: 'Offline Library',
offlineLibraryEmpty: 'No albums cached yet. Go online, open an album and click "Make available offline".', offlineLibraryEmpty: 'No albums cached yet. Go online, open an album and click "Make available offline".',
offlineAlbumCount: '{{n}} album', offlineAlbumCount: '{{n}} album',
@@ -347,6 +348,7 @@ export const enTranslation = {
offlineFilterPlaylists: 'Playlists', offlineFilterPlaylists: 'Playlists',
offlineFilterArtists: 'Discographies', offlineFilterArtists: 'Discographies',
retry: 'Retry', retry: 'Retry',
serverSettings: 'Server Settings',
lastfmConnected: 'Last.fm connected as @{{user}}', lastfmConnected: 'Last.fm connected as @{{user}}',
lastfmSessionInvalid: 'Session invalid — click to re-connect', lastfmSessionInvalid: 'Session invalid — click to re-connect',
}, },
+2
View File
@@ -339,6 +339,7 @@ export const esTranslation = {
offlineTitle: 'Sin conexión al servidor', offlineTitle: 'Sin conexión al servidor',
offlineSubtitle: 'No se puede alcanzar {{server}}. Verifica tu red o servidor.', offlineSubtitle: 'No se puede alcanzar {{server}}. Verifica tu red o servidor.',
offlineModeBanner: 'Modo Offline — reproduciendo desde caché local', offlineModeBanner: 'Modo Offline — reproduciendo desde caché local',
offlineNoCacheBanner: 'Sin conexión al servidor — no se puede acceder a {{server}}',
offlineLibraryTitle: 'Biblioteca Offline', offlineLibraryTitle: 'Biblioteca Offline',
offlineLibraryEmpty: 'No hay álbumes en caché aún. Conéctate, abre un álbum y click en "Disponible offline".', offlineLibraryEmpty: 'No hay álbumes en caché aún. Conéctate, abre un álbum y click en "Disponible offline".',
offlineAlbumCount: '{{n}} álbum', offlineAlbumCount: '{{n}} álbum',
@@ -348,6 +349,7 @@ export const esTranslation = {
offlineFilterPlaylists: 'Listas', offlineFilterPlaylists: 'Listas',
offlineFilterArtists: 'Discografías', offlineFilterArtists: 'Discografías',
retry: 'Reintentar', retry: 'Reintentar',
serverSettings: 'Configuración del servidor',
lastfmConnected: 'Last.fm conectado como @{{user}}', lastfmConnected: 'Last.fm conectado como @{{user}}',
lastfmSessionInvalid: 'Sesión inválida — click para reconectar', lastfmSessionInvalid: 'Sesión inválida — click para reconectar',
}, },
+2
View File
@@ -337,6 +337,7 @@ export const frTranslation = {
offlineTitle: 'Pas de connexion au serveur', offlineTitle: 'Pas de connexion au serveur',
offlineSubtitle: 'Impossible d\'atteindre {{server}}. Vérifiez votre réseau ou le serveur.', offlineSubtitle: 'Impossible d\'atteindre {{server}}. Vérifiez votre réseau ou le serveur.',
offlineModeBanner: 'Mode hors ligne — lecture depuis le cache local', offlineModeBanner: 'Mode hors ligne — lecture depuis le cache local',
offlineNoCacheBanner: 'Pas de connexion au serveur — {{server}} inaccessible',
offlineLibraryTitle: 'Bibliothèque hors ligne', offlineLibraryTitle: 'Bibliothèque hors ligne',
offlineLibraryEmpty: 'Aucun album en cache. Connectez-vous, ouvrez un album et cliquez sur "Rendre disponible hors ligne".', offlineLibraryEmpty: 'Aucun album en cache. Connectez-vous, ouvrez un album et cliquez sur "Rendre disponible hors ligne".',
offlineAlbumCount: '{{n}} album', offlineAlbumCount: '{{n}} album',
@@ -346,6 +347,7 @@ export const frTranslation = {
offlineFilterPlaylists: 'Playlists', offlineFilterPlaylists: 'Playlists',
offlineFilterArtists: 'Discographies', offlineFilterArtists: 'Discographies',
retry: 'Réessayer', retry: 'Réessayer',
serverSettings: 'Paramètres serveur',
lastfmConnected: 'Last.fm connecté en tant que @{{user}}', lastfmConnected: 'Last.fm connecté en tant que @{{user}}',
lastfmSessionInvalid: 'Session invalide — cliquez pour vous reconnecter', lastfmSessionInvalid: 'Session invalide — cliquez pour vous reconnecter',
}, },
+2
View File
@@ -337,6 +337,7 @@ export const nbTranslation = {
offlineTitle: 'Ingen tjenertilkobling', offlineTitle: 'Ingen tjenertilkobling',
offlineSubtitle: 'Kan ikke nå {{server}}. Sjekk nettverket eller tjeneren din.', offlineSubtitle: 'Kan ikke nå {{server}}. Sjekk nettverket eller tjeneren din.',
offlineModeBanner: 'Frakoblet modus - spiller fra lokal hurtigbuffer', offlineModeBanner: 'Frakoblet modus - spiller fra lokal hurtigbuffer',
offlineNoCacheBanner: 'Ingen servertilkobling — kan ikke nå {{server}}',
offlineLibraryTitle: 'Frakoblet bibliotek', offlineLibraryTitle: 'Frakoblet bibliotek',
offlineLibraryEmpty: 'Ingen album bufret ennå. Kobl deg til nettverket, åpne et album og klikk "Gjør tilgjengelig frakoblet".', offlineLibraryEmpty: 'Ingen album bufret ennå. Kobl deg til nettverket, åpne et album og klikk "Gjør tilgjengelig frakoblet".',
offlineAlbumCount: '{{n}} album', offlineAlbumCount: '{{n}} album',
@@ -346,6 +347,7 @@ export const nbTranslation = {
offlineFilterPlaylists: 'Spillelister', offlineFilterPlaylists: 'Spillelister',
offlineFilterArtists: 'Diskografier', offlineFilterArtists: 'Diskografier',
retry: 'Prøv igjen', retry: 'Prøv igjen',
serverSettings: 'Serverinnstillinger',
lastfmConnected: 'Last.fm tilkoblet som bruker @{{user}}', lastfmConnected: 'Last.fm tilkoblet som bruker @{{user}}',
lastfmSessionInvalid: 'Sesjonen er ugyldig - klikk her for å koble til på nytt', lastfmSessionInvalid: 'Sesjonen er ugyldig - klikk her for å koble til på nytt',
}, },
+2
View File
@@ -336,6 +336,7 @@ export const nlTranslation = {
offlineTitle: 'Geen serververbinding', offlineTitle: 'Geen serververbinding',
offlineSubtitle: 'Kan {{server}} niet bereiken. Controleer je netwerk of server.', offlineSubtitle: 'Kan {{server}} niet bereiken. Controleer je netwerk of server.',
offlineModeBanner: 'Offline modus — afspelen vanuit lokale cache', offlineModeBanner: 'Offline modus — afspelen vanuit lokale cache',
offlineNoCacheBanner: 'Geen serververbinding — {{server}} niet bereikbaar',
offlineLibraryTitle: 'Offline bibliotheek', offlineLibraryTitle: 'Offline bibliotheek',
offlineLibraryEmpty: 'Nog geen albums gecached. Ga online, open een album en klik op "Offline beschikbaar maken".', offlineLibraryEmpty: 'Nog geen albums gecached. Ga online, open een album en klik op "Offline beschikbaar maken".',
offlineAlbumCount: '{{n}} album', offlineAlbumCount: '{{n}} album',
@@ -345,6 +346,7 @@ export const nlTranslation = {
offlineFilterPlaylists: 'Afspeellijsten', offlineFilterPlaylists: 'Afspeellijsten',
offlineFilterArtists: 'Discografieën', offlineFilterArtists: 'Discografieën',
retry: 'Opnieuw proberen', retry: 'Opnieuw proberen',
serverSettings: 'Serverinstellingen',
lastfmConnected: 'Last.fm verbonden als @{{user}}', lastfmConnected: 'Last.fm verbonden als @{{user}}',
lastfmSessionInvalid: 'Sessie ongeldig — klik om opnieuw te verbinden', lastfmSessionInvalid: 'Sessie ongeldig — klik om opnieuw te verbinden',
}, },
+2
View File
@@ -348,6 +348,7 @@ export const ruTranslation = {
offlineTitle: 'Нет связи с сервером', offlineTitle: 'Нет связи с сервером',
offlineSubtitle: 'Сервер {{server}} недоступен. Проверьте сеть и адрес.', offlineSubtitle: 'Сервер {{server}} недоступен. Проверьте сеть и адрес.',
offlineModeBanner: 'Офлайн — воспроизведение из локального кэша', offlineModeBanner: 'Офлайн — воспроизведение из локального кэша',
offlineNoCacheBanner: 'Нет соединения с сервером — {{server}} недоступен',
offlineLibraryTitle: 'Офлайн-библиотека', offlineLibraryTitle: 'Офлайн-библиотека',
offlineLibraryEmpty: offlineLibraryEmpty:
'Пока ничего не сохранено. Подключитесь к сети, откройте альбом и нажмите «Сохранить офлайн».', 'Пока ничего не сохранено. Подключитесь к сети, откройте альбом и нажмите «Сохранить офлайн».',
@@ -360,6 +361,7 @@ export const ruTranslation = {
offlineFilterPlaylists: 'Плейлисты', offlineFilterPlaylists: 'Плейлисты',
offlineFilterArtists: 'Дискографии', offlineFilterArtists: 'Дискографии',
retry: 'Повторить', retry: 'Повторить',
serverSettings: 'Настройки сервера',
lastfmConnected: 'Last.fm: @{{user}}', lastfmConnected: 'Last.fm: @{{user}}',
lastfmSessionInvalid: 'Сессия недействительна — подключите снова', lastfmSessionInvalid: 'Сессия недействительна — подключите снова',
}, },
+2
View File
@@ -336,11 +336,13 @@ export const zhTranslation = {
offlineTitle: '无服务器连接', offlineTitle: '无服务器连接',
offlineSubtitle: '无法连接到 {{server}}。请检查您的网络或服务器。', offlineSubtitle: '无法连接到 {{server}}。请检查您的网络或服务器。',
offlineModeBanner: '离线模式 — 正在从本地缓存播放', offlineModeBanner: '离线模式 — 正在从本地缓存播放',
offlineNoCacheBanner: '无服务器连接 — 无法访问 {{server}}',
offlineLibraryTitle: '离线音乐库', offlineLibraryTitle: '离线音乐库',
offlineLibraryEmpty: '尚未缓存任何专辑。请联网,打开专辑并点击"设为离线可用"。', offlineLibraryEmpty: '尚未缓存任何专辑。请联网,打开专辑并点击"设为离线可用"。',
offlineAlbumCount_one: '{{n}} 张专辑', offlineAlbumCount_one: '{{n}} 张专辑',
offlineAlbumCount_plural: '{{n}} 张专辑', offlineAlbumCount_plural: '{{n}} 张专辑',
retry: '重试', retry: '重试',
serverSettings: '服务器设置',
lastfmConnected: 'Last.fm 已连接为 @{{user}}', lastfmConnected: 'Last.fm 已连接为 @{{user}}',
lastfmSessionInvalid: '会话无效 — 点击重新连接', lastfmSessionInvalid: '会话无效 — 点击重新连接',
}, },