diff --git a/.gitignore b/.gitignore index 99d9898..0898abc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ vite.config.ts.timestamp-* *storybook.log storybook-static backups/ +server.log diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 0000000..f654ec4 --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/clock.svg b/src/assets/icons/clock.svg new file mode 100644 index 0000000..45f7c20 --- /dev/null +++ b/src/assets/icons/clock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/loader.svg b/src/assets/icons/loader.svg new file mode 100644 index 0000000..7904da8 --- /dev/null +++ b/src/assets/icons/loader.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/settings.svg b/src/assets/icons/settings.svg new file mode 100644 index 0000000..6284452 --- /dev/null +++ b/src/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/trash.svg b/src/assets/icons/trash.svg new file mode 100644 index 0000000..b95165c --- /dev/null +++ b/src/assets/icons/trash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/x.svg b/src/assets/icons/x.svg new file mode 100644 index 0000000..05eacec --- /dev/null +++ b/src/assets/icons/x.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/lib/components/Album.svelte b/src/lib/components/Album.svelte index cac6695..ea72e0e 100644 --- a/src/lib/components/Album.svelte +++ b/src/lib/components/Album.svelte @@ -2,10 +2,9 @@ import { Spring } from 'svelte/motion' import type { Album } from '$lib/types/lastfm' import { audioPreview } from '$lib/stores/audio-preview' - import { nowPlayingStream } from '$lib/stores/now-playing-stream' import NowPlaying from './NowPlaying.svelte' - import PlayIcon from '$icons/play.svg' - import PauseIcon from '$icons/pause.svg' + import PlayIcon from '$icons/play.svg?component' + import PauseIcon from '$icons/pause.svg?component' interface AlbumProps { album?: Album @@ -32,8 +31,8 @@ }) const scale = new Spring(1, { - stiffness: 0.2, - damping: 0.12 + stiffness: 0.3, + damping: 0.25 }) // Determine if this album should shrink @@ -41,9 +40,9 @@ $effect(() => { if (isHovering) { - scale.target = 1.1 + scale.target = 1.05 } else if (shouldShrink) { - scale.target = 0.95 + scale.target = 0.97 } else { scale.target = 1 } @@ -99,32 +98,17 @@ const hasPreview = $derived(!!album?.appleMusicData?.previewUrl) - // Subscribe to real-time now playing updates - let realtimeNowPlaying = $state<{ isNowPlaying: boolean; nowPlayingTrack?: string } | null>(null) - - $effect(() => { - if (album) { - const unsubscribe = nowPlayingStream.isAlbumPlaying.subscribe((checkAlbum) => { - const status = checkAlbum(album.artist.name, album.name) - if (status !== null) { - realtimeNowPlaying = status - } - }) - return unsubscribe - } - }) - - // Combine initial state with real-time updates - const isNowPlaying = $derived(realtimeNowPlaying?.isNowPlaying ?? album?.isNowPlaying ?? false) - const nowPlayingTrack = $derived(realtimeNowPlaying?.nowPlayingTrack ?? album?.nowPlayingTrack) - + // Use the album's isNowPlaying status directly - single source of truth + const isNowPlaying = $derived(album?.isNowPlaying ?? false) + const nowPlayingTrack = $derived(album?.nowPlayingTrack) + // Debug logging $effect(() => { - if (album && isNowPlaying) { - console.log(`Album "${album.name}" is now playing:`, { - fromRealtime: realtimeNowPlaying?.isNowPlaying, - fromAlbum: album?.isNowPlaying, - track: nowPlayingTrack + if (album && (isNowPlaying || album.isNowPlaying)) { + console.log(`🎵 Album component "${album.name}":`, { + isNowPlaying, + nowPlayingTrack, + albumData: album }) } }) @@ -165,9 +149,9 @@ class:playing={isPlaying} > {#if isPlaying} - + {:else} - + {/if} {/if} diff --git a/src/lib/components/AppleMusicSearchModal.svelte b/src/lib/components/AppleMusicSearchModal.svelte new file mode 100644 index 0000000..678c08a --- /dev/null +++ b/src/lib/components/AppleMusicSearchModal.svelte @@ -0,0 +1,441 @@ + + +{#if isOpen} + +{/if} + + \ No newline at end of file diff --git a/src/lib/components/Avatar.svelte b/src/lib/components/Avatar.svelte index 6911608..d5a982d 100644 --- a/src/lib/components/Avatar.svelte +++ b/src/lib/components/Avatar.svelte @@ -1,11 +1,9 @@ diff --git a/src/lib/components/DebugPanel.svelte b/src/lib/components/DebugPanel.svelte new file mode 100644 index 0000000..9543a6b --- /dev/null +++ b/src/lib/components/DebugPanel.svelte @@ -0,0 +1,1058 @@ + + +{#if dev} +
+
isMinimized = !isMinimized}> +

Debug Panel

+ +
+ + {#if !isMinimized} +
+
+ + + +
+ +
+ {#if activeTab === 'nowplaying'} +
+

Connection

+

+ Status: {#if connected} Connected{:else} Disconnected{/if} +

+

+ Last Update: {lastUpdate ? lastUpdate.toLocaleTimeString() : 'Never'} +

+

Next Update: {formatTime(nextUpdateIn)}

+

Interval: {updateInterval}s {trackRemainingTime > 0 ? `(smart mode)` : nowPlaying ? '(fast mode)' : '(normal)'}

+ {#if trackRemainingTime > 0} +

Track Remaining: {formatTime(trackRemainingTime)}

+ {/if} +
+ +
+

Now Playing

+ {#if nowPlaying} +
+

{nowPlaying.album.artist.name}

+

{nowPlaying.album.name}

+ {#if nowPlaying.track} +

{nowPlaying.track}

+ {/if} + {#if nowPlaying.album.appleMusicData} +

+ Preview: {#if nowPlaying.album.appleMusicData.previewUrl} Available{:else} Not found{/if} +

+ {/if} +
+ {:else} +

No music playing

+ {/if} +
+ {/if} + + {#if activeTab === 'albums'} +
+

Recent Albums ({albums.length})

+
+ {#each albums as album} + {@const albumId = `${album.artist.name}:${album.name}`} +
+
expandedAlbumId = expandedAlbumId === albumId ? null : albumId}> +
+
+ {album.name} + by {album.artist.name} +
+ {#if album.isNowPlaying} + NOW + {/if} +
+ {#if album.appleMusicData} + + {album.appleMusicData.tracks?.length || 0} tracks + + + {#if album.appleMusicData.previewUrl} Preview{:else} No preview{/if} + + {:else} + No Apple Music data + {/if} +
+
+ +
+ + {#if expandedAlbumId === albumId} +
+ {#if album.appleMusicData} + {#if album.appleMusicData.searchMetadata} +
Search Information
+ + {/if} + + {#if album.appleMusicData.appleMusicId} +
Apple Music Details
+

Apple Music ID: {album.appleMusicData.appleMusicId}

+ {/if} + + {#if album.appleMusicData.releaseDate} +

Release Date: {album.appleMusicData.releaseDate}

+ {/if} + + {#if album.appleMusicData.recordLabel} +

Label: {album.appleMusicData.recordLabel}

+ {/if} + + {#if album.appleMusicData.genres?.length} +

Genres: {album.appleMusicData.genres.join(', ')}

+ {/if} + + {#if album.appleMusicData.previewUrl} +

Preview URL: {album.appleMusicData.previewUrl}

+ {/if} + + {#if album.appleMusicData.tracks?.length} +
+
Tracks ({album.appleMusicData.tracks.length})
+
+ {#each album.appleMusicData.tracks as track, i} +
+ {i + 1}. + {track.name} + {#if track.durationMs} + {Math.floor(track.durationMs / 60000)}:{String(Math.floor((track.durationMs % 60000) / 1000)).padStart(2, '0')} + {/if} + {#if track.previewUrl} + + {/if} +
+ {/each} +
+
+ {/if} + +
+
Raw Data
+
{JSON.stringify(album.appleMusicData, null, 2)}
+
+ {:else} +
No Apple Music Data
+

This album was not searched in Apple Music or the search is pending.

+ {/if} +
+ {/if} +
+ {/each} +
+
+ {/if} + + {#if activeTab === 'cache'} +
+

Redis Cache Management

+ +
+ + +
+ +
+ + + + + +
+ +
+

Key format: apple:album:ArtistName:AlbumName

+

Example: apple:album:藤井風:Hachikō

+
+
+ {/if} +
+
+ {/if} +
+ + +{/if} + + \ No newline at end of file diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 8e86cef..e743c0b 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -3,8 +3,7 @@ import SegmentedController from './SegmentedController.svelte' import NavDropdown from './NavDropdown.svelte' import NowPlayingBar from './NowPlayingBar.svelte' - import { albumStream } from '$lib/stores/album-stream' - import { nowPlayingStream } from '$lib/stores/now-playing-stream' + import { musicStream } from '$lib/stores/music-stream' import type { Album } from '$lib/types/lastfm' let scrollY = $state(0) @@ -18,40 +17,19 @@ let currentlyPlayingAlbum = $state(null) let isPlayingMusic = $state(false) - // Subscribe to album updates + // Subscribe to music stream updates - single source of truth $effect(() => { - const unsubscribe = albumStream.subscribe((state) => { - const nowPlaying = state.albums.find((album) => album.isNowPlaying) - currentlyPlayingAlbum = nowPlaying || null + const unsubscribe = musicStream.nowPlaying.subscribe((nowPlaying) => { + currentlyPlayingAlbum = nowPlaying?.album || null isPlayingMusic = !!nowPlaying // Debug logging - if (nowPlaying) { - console.log('Header: Now playing detected:', { - artist: nowPlaying.artist.name, - album: nowPlaying.name, - track: nowPlaying.nowPlayingTrack - }) - } - }) - - return unsubscribe - }) - - // Also check now playing stream for updates - $effect(() => { - const unsubscribe = nowPlayingStream.subscribe((state) => { - const hasNowPlaying = Array.from(state.updates.values()).some((update) => update.isNowPlaying) - console.log('Header: nowPlayingStream update:', { - hasNowPlaying, - updatesCount: state.updates.size + console.log('🎧 Header now playing update:', { + hasNowPlaying: !!nowPlaying, + album: nowPlaying?.album.name, + artist: nowPlaying?.album.artist.name, + track: nowPlaying?.track }) - // Only clear if we explicitly know music stopped - if (!hasNowPlaying && currentlyPlayingAlbum && state.updates.size > 0) { - // Music stopped - currentlyPlayingAlbum = null - isPlayingMusic = false - } }) return unsubscribe diff --git a/src/lib/components/RecentAlbums.svelte b/src/lib/components/RecentAlbums.svelte index 715f4bb..9606961 100644 --- a/src/lib/components/RecentAlbums.svelte +++ b/src/lib/components/RecentAlbums.svelte @@ -1,7 +1,7 @@