Update UserProfile avatar size; add development-only debug tools in SettingsPage
This commit is contained in:
@@ -1 +1 @@
|
|||||||
NEXT_PUBLIC_COMMIT_SHA=74b9648
|
NEXT_PUBLIC_COMMIT_SHA=25e9bd6
|
||||||
|
|||||||
@@ -101,104 +101,89 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
|
|||||||
}
|
}
|
||||||
}, [lyrics, currentTime, currentLyricIndex]);
|
}, [lyrics, currentTime, currentLyricIndex]);
|
||||||
|
|
||||||
// Auto-scroll lyrics using lyricsRef
|
// Auto-scroll lyrics using lyricsRef - Simplified for iOS compatibility
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only auto-scroll if lyrics are visible
|
// Only auto-scroll if lyrics are visible and we're not on mobile to avoid iOS audio issues
|
||||||
const shouldScroll = isMobile ? (activeTab === 'lyrics' && lyrics.length > 0) : (showLyrics && lyrics.length > 0);
|
const shouldScroll = !isMobile && showLyrics && lyrics.length > 0;
|
||||||
|
|
||||||
if (currentLyricIndex >= 0 && shouldScroll && lyricsRef.current) {
|
if (currentLyricIndex >= 0 && shouldScroll && lyricsRef.current) {
|
||||||
const scrollTimeout = setTimeout(() => {
|
const scrollTimeout = setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
// Simplified scroll container detection for better iOS compatibility
|
const scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
|
||||||
let scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
|
|
||||||
|
|
||||||
// Fallback to the lyrics container itself on mobile (iOS)
|
|
||||||
if (!scrollContainer && isMobile && lyricsRef.current) {
|
|
||||||
scrollContainer = lyricsRef.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentLyricElement = lyricsRef.current?.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement;
|
const currentLyricElement = lyricsRef.current?.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement;
|
||||||
|
|
||||||
if (scrollContainer && currentLyricElement) {
|
if (scrollContainer && currentLyricElement) {
|
||||||
const containerHeight = scrollContainer.clientHeight;
|
const containerHeight = scrollContainer.clientHeight;
|
||||||
const elementTop = currentLyricElement.offsetTop;
|
const elementTop = currentLyricElement.offsetTop;
|
||||||
const elementHeight = currentLyricElement.offsetHeight;
|
const elementHeight = currentLyricElement.offsetHeight;
|
||||||
|
|
||||||
// Calculate scroll position to center the current lyric
|
|
||||||
const targetScrollTop = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
const targetScrollTop = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
||||||
|
|
||||||
// Use requestAnimationFrame for smoother iOS performance
|
scrollContainer.scrollTo({
|
||||||
requestAnimationFrame(() => {
|
top: Math.max(0, targetScrollTop),
|
||||||
try {
|
behavior: 'smooth'
|
||||||
scrollContainer.scrollTo({
|
|
||||||
top: Math.max(0, targetScrollTop),
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// Simple fallback for iOS
|
|
||||||
scrollContainer.scrollTop = Math.max(0, targetScrollTop);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail to prevent breaking audio playback
|
|
||||||
console.warn('Lyrics scroll failed:', error);
|
console.warn('Lyrics scroll failed:', error);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 200);
|
||||||
|
|
||||||
return () => clearTimeout(scrollTimeout);
|
return () => clearTimeout(scrollTimeout);
|
||||||
}
|
}
|
||||||
}, [currentLyricIndex, showLyrics, lyrics.length, isMobile, activeTab]);
|
}, [currentLyricIndex, showLyrics, lyrics.length, isMobile]);
|
||||||
|
|
||||||
// Reset lyrics to top when song changes
|
// Reset lyrics to top when song changes - Disabled on mobile to prevent iOS audio issues
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldReset = isMobile ? (activeTab === 'lyrics' && lyrics.length > 0) : (showLyrics && lyrics.length > 0);
|
// Only reset scroll on desktop to avoid iOS audio interference
|
||||||
|
const shouldReset = !isMobile && showLyrics && lyrics.length > 0;
|
||||||
|
|
||||||
if (currentTrack && shouldReset && lyricsRef.current) {
|
if (currentTrack && shouldReset && lyricsRef.current) {
|
||||||
// Simplified reset scroll logic for better iOS compatibility
|
const resetTimeout = setTimeout(() => {
|
||||||
const resetScroll = () => {
|
|
||||||
try {
|
try {
|
||||||
let scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
|
const scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
|
||||||
|
|
||||||
// Fallback to the lyrics container itself on mobile (iOS)
|
|
||||||
if (!scrollContainer && isMobile && lyricsRef.current) {
|
|
||||||
scrollContainer = lyricsRef.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollContainer) {
|
if (scrollContainer) {
|
||||||
// Use requestAnimationFrame for smoother iOS performance
|
scrollContainer.scrollTo({
|
||||||
requestAnimationFrame(() => {
|
top: 0,
|
||||||
try {
|
behavior: 'instant'
|
||||||
scrollContainer.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'instant'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
scrollContainer.scrollTop = 0;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail to prevent breaking audio playback
|
|
||||||
console.warn('Lyrics reset scroll failed:', error);
|
console.warn('Lyrics reset scroll failed:', error);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Small delay to ensure DOM is ready
|
|
||||||
const resetTimeout = setTimeout(() => {
|
|
||||||
resetScroll();
|
|
||||||
setCurrentLyricIndex(-1);
|
setCurrentLyricIndex(-1);
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
return () => clearTimeout(resetTimeout);
|
return () => clearTimeout(resetTimeout);
|
||||||
}
|
}
|
||||||
}, [currentTrack?.id, showLyrics, currentTrack, isMobile, activeTab, lyrics.length]); // Only reset when track ID changes
|
}, [currentTrack?.id, showLyrics, currentTrack, isMobile, lyrics.length]);
|
||||||
|
|
||||||
// Sync with main audio player (improved responsiveness)
|
// Sync with main audio player (improved responsiveness)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const syncWithMainPlayer = () => {
|
const syncWithMainPlayer = () => {
|
||||||
const mainAudio = document.querySelector('audio') as HTMLAudioElement;
|
const mainAudio = document.querySelector('audio') as HTMLAudioElement;
|
||||||
if (mainAudio && currentTrack) {
|
if (mainAudio && currentTrack) {
|
||||||
|
// Console log audio information for debugging
|
||||||
|
console.log('=== FULLSCREEN PLAYER AUDIO DEBUG ===');
|
||||||
|
console.log('Audio element src:', mainAudio.src);
|
||||||
|
console.log('Audio element currentSrc:', mainAudio.currentSrc);
|
||||||
|
console.log('Current track:', {
|
||||||
|
id: currentTrack.id,
|
||||||
|
name: currentTrack.name,
|
||||||
|
url: currentTrack.url,
|
||||||
|
artist: currentTrack.artist,
|
||||||
|
album: currentTrack.album
|
||||||
|
});
|
||||||
|
console.log('Audio state:', {
|
||||||
|
currentTime: mainAudio.currentTime,
|
||||||
|
duration: mainAudio.duration,
|
||||||
|
paused: mainAudio.paused,
|
||||||
|
ended: mainAudio.ended,
|
||||||
|
readyState: mainAudio.readyState,
|
||||||
|
networkState: mainAudio.networkState
|
||||||
|
});
|
||||||
|
console.log('==========================================');
|
||||||
|
|
||||||
const newCurrentTime = mainAudio.currentTime;
|
const newCurrentTime = mainAudio.currentTime;
|
||||||
const newDuration = mainAudio.duration || 0;
|
const newDuration = mainAudio.duration || 0;
|
||||||
const newIsPlaying = !mainAudio.paused;
|
const newIsPlaying = !mainAudio.paused;
|
||||||
@@ -268,12 +253,28 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
|
|||||||
|
|
||||||
const togglePlayPause = () => {
|
const togglePlayPause = () => {
|
||||||
const mainAudio = document.querySelector('audio') as HTMLAudioElement;
|
const mainAudio = document.querySelector('audio') as HTMLAudioElement;
|
||||||
if (!mainAudio) return;
|
if (!mainAudio) {
|
||||||
|
console.log('❌ No audio element found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🎵 Toggle Play/Pause clicked');
|
||||||
|
console.log('Audio src before toggle:', mainAudio.src);
|
||||||
|
console.log('Audio currentSrc before toggle:', mainAudio.currentSrc);
|
||||||
|
console.log('Audio paused state before toggle:', mainAudio.paused);
|
||||||
|
console.log('Audio currentTime before toggle:', mainAudio.currentTime);
|
||||||
|
console.log('Audio duration before toggle:', mainAudio.duration);
|
||||||
|
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
|
console.log('⏸️ Pausing audio');
|
||||||
mainAudio.pause();
|
mainAudio.pause();
|
||||||
} else {
|
} else {
|
||||||
mainAudio.play();
|
console.log('▶️ Playing audio');
|
||||||
|
mainAudio.play().then(() => {
|
||||||
|
console.log('✅ Audio play() succeeded');
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('❌ Audio play() failed:', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -537,12 +538,8 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
|
|||||||
{activeTab === 'lyrics' && lyrics.length > 0 && (
|
{activeTab === 'lyrics' && lyrics.length > 0 && (
|
||||||
<div className="h-full flex flex-col px-4">
|
<div className="h-full flex flex-col px-4">
|
||||||
<div
|
<div
|
||||||
className="flex-1 overflow-y-auto scrollable-area"
|
className="flex-1 overflow-y-auto"
|
||||||
ref={lyricsRef}
|
ref={lyricsRef}
|
||||||
style={{
|
|
||||||
WebkitOverflowScrolling: 'touch', // Enable momentum scrolling on iOS
|
|
||||||
scrollBehavior: 'smooth'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="space-y-3 py-4">
|
<div className="space-y-3 py-4">
|
||||||
{lyrics.map((line, index) => (
|
{lyrics.map((line, index) => (
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ export function UserProfile({ variant = 'desktop' }: UserProfileProps) {
|
|||||||
<Image
|
<Image
|
||||||
src={gravatarUrl}
|
src={gravatarUrl}
|
||||||
alt={`${userInfo.username}'s avatar`}
|
alt={`${userInfo.username}'s avatar`}
|
||||||
width={16}
|
width={32}
|
||||||
height={16}
|
height={32}
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
const target = e.target as HTMLImageElement;
|
const target = e.target as HTMLImageElement;
|
||||||
@@ -171,8 +171,8 @@ export function UserProfile({ variant = 'desktop' }: UserProfileProps) {
|
|||||||
<Image
|
<Image
|
||||||
src={gravatarUrl}
|
src={gravatarUrl}
|
||||||
alt={`${userInfo.username}'s avatar`}
|
alt={`${userInfo.username}'s avatar`}
|
||||||
width={16}
|
width={32}
|
||||||
height={16}
|
height={32}
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -789,6 +789,47 @@ const SettingsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Debug Section - Development Only */}
|
||||||
|
{process.env.NODE_ENV === 'development' && (
|
||||||
|
<Card className="mb-6 break-inside-avoid py-5 border-orange-200 bg-orange-50/50">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-orange-700">
|
||||||
|
<Settings className="w-5 h-5" />
|
||||||
|
Debug Tools
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-orange-600">
|
||||||
|
Development-only debugging utilities
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
// Save Navidrome config before clearing
|
||||||
|
const navidromeConfig = localStorage.getItem('navidrome-config');
|
||||||
|
|
||||||
|
// Clear all localStorage
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
// Restore Navidrome config
|
||||||
|
if (navidromeConfig) {
|
||||||
|
localStorage.setItem('navidrome-config', navidromeConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload page to reset state
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full bg-orange-100 border-orange-300 text-orange-700 hover:bg-orange-200"
|
||||||
|
>
|
||||||
|
Clear All Data (Keep Navidrome Config)
|
||||||
|
</Button>
|
||||||
|
<p className="text-xs text-orange-600 mt-2">
|
||||||
|
This will clear all localStorage data except your Navidrome server configuration, then reload the page.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user