Files
Psychotoxical-psysonic/src/hooks/useConnectionStatus.ts
T
Psychotoxical 59115a09d2 feat: 10-band EQ, connection indicator, new icon (v1.5.0)
### 10-Band Graphic Equalizer
- Full EQ in Rust audio engine via EqSource<S> biquad peak filters
- 10 built-in presets + custom preset save/delete
- EqSource::try_seek() implemented — also fixes waveform seek which broke
  silently when EQ was introduced (rodio returned SeekError::NotSupported)
- EQ state persisted in localStorage, synced to Rust on startup

### Connection Indicator
- LED in header (green/red/pulsing) with server name and LAN/WAN label
- Offline overlay with retry button when server is unreachable
- useConnectionStatus hook

### New App Icon
- New logo (logo-psysonic.png) applied across Login, Sidebar, Settings,
  README and all Tauri platform icons (Windows, macOS, Linux, Android, iOS)

### Now Playing Page
- New /now-playing route added

### Fixes
- WaveformSeek: mousemove/mouseup on window to fix drag outside canvas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:25:32 +01:00

77 lines
2.1 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { useAuthStore } from '../store/authStore';
import { pingWithCredentials } from '../api/subsonic';
export type ConnectionStatus = 'connected' | 'disconnected' | 'checking';
export function isLanUrl(url: string): boolean {
try {
const hostname = new URL(url.startsWith('http') ? url : `http://${url}`).hostname;
return (
hostname === 'localhost' ||
hostname.endsWith('.local') ||
/^127\./.test(hostname) ||
/^10\./.test(hostname) ||
/^192\.168\./.test(hostname) ||
/^172\.(1[6-9]|2\d|3[01])\./.test(hostname)
);
} catch {
return false;
}
}
export function useConnectionStatus() {
const [status, setStatus] = useState<ConnectionStatus>('checking');
const [isRetrying, setIsRetrying] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const check = useCallback(async () => {
const server = useAuthStore.getState().getActiveServer();
if (!server) {
setStatus('disconnected');
return;
}
if (!navigator.onLine) {
setStatus('disconnected');
return;
}
const ok = await pingWithCredentials(server.url, server.username, server.password);
setStatus(ok ? 'connected' : 'disconnected');
}, []);
const retry = useCallback(async () => {
setIsRetrying(true);
await check();
setIsRetrying(false);
}, [check]);
useEffect(() => {
check();
intervalRef.current = setInterval(check, 30_000);
const handleOnline = () => check();
const handleOffline = () => setStatus('disconnected');
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, [check]);
const server = useAuthStore(s => s.getActiveServer());
return {
status,
isRetrying,
retry,
isLan: server ? isLanUrl(server.url) : false,
serverName: server?.name ?? '',
};
}