my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(MusicStatusCard): simplify layout and remove autoscroll logic

- Removed autoscroll refs, container, and checkOverflow logic
- Replaced scrolling track/artist/album text with standard text wrapping
- Consolidated and updated icons (Music, Disc3, Users, Album, Clock, Radio)
- Simplified image error handling and artwork fallback
- Cleaned up DOM structure and class usage for better readability and maintainability

+56 -97
+56 -97
src/lib/components/layout/main/card/MusicStatusCard.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount, tick } from 'svelte'; 2 + import { onMount } from 'svelte'; 3 3 import { Card } from '$lib/components/ui'; 4 4 import { fetchMusicStatus, type MusicStatusData } from '$lib/services/atproto'; 5 5 import { formatRelativeTime } from '$lib/utils/formatDate'; 6 - import { Music, Disc3 } from '@lucide/svelte'; 6 + 7 + // Icons 8 + import { 9 + Music, 10 + Disc3, 11 + Users, 12 + Album, 13 + Clock, 14 + Radio 15 + } from '@lucide/svelte'; 7 16 8 17 let musicStatus: MusicStatusData | null = null; 9 18 let loading = true; 10 19 let error: string | null = null; 11 20 let artworkError = false; 12 21 13 - // Refs for autoscroll detection 14 - let trackNameEl: HTMLElement; 15 - let artistEl: HTMLElement; 16 - let albumEl: HTMLElement; 17 - 18 22 onMount(async () => { 19 23 try { 20 24 musicStatus = await fetchMusicStatus(); ··· 22 26 console.log('[MusicStatusCard] Music status loaded:', musicStatus); 23 27 console.log('[MusicStatusCard] Artwork URL:', musicStatus.artworkUrl); 24 28 console.log('[MusicStatusCard] Release MBID:', musicStatus.releaseMbId); 25 - 26 - // Wait for DOM to update then check for overflow 27 - await tick(); 28 - checkOverflow(); 29 29 } 30 30 } catch (err) { 31 31 console.error('[MusicStatusCard] Error loading music status:', err); ··· 35 35 } 36 36 }); 37 37 38 - function checkOverflow() { 39 - const elements = [trackNameEl, artistEl, albumEl].filter(Boolean); 40 - 41 - elements.forEach(el => { 42 - if (!el) return; 43 - 44 - const container = el.parentElement; 45 - if (!container) return; 46 - 47 - const isOverflowing = el.scrollWidth > container.clientWidth; 48 - 49 - if (isOverflowing) { 50 - const overflowAmount = el.scrollWidth - container.clientWidth; 51 - const duration = Math.max(8, overflowAmount / 20); // ~20px per second 52 - 53 - el.style.setProperty('--overflow-amount', `-${overflowAmount}px`); 54 - el.style.setProperty('--scroll-duration', `${duration}s`); 55 - el.classList.add('is-overflowing'); 56 - } else { 57 - el.classList.remove('is-overflowing'); 58 - } 59 - }); 60 - } 61 - 62 38 function formatArtists(artists: { artistName: string }[]): string { 63 39 if (!artists || artists.length === 0) return 'Unknown Artist'; 64 40 return artists.map(a => a.artistName).join(', '); ··· 76 52 return domain.replace('lastfm', 'Last.fm').replace('last.fm', 'Last.fm'); 77 53 } 78 54 79 - function handleImageError(event: Event) { 55 + function handleImageError() { 80 56 console.error('[MusicStatusCard] Artwork failed to load'); 81 57 artworkError = true; 82 58 } 83 59 </script> 84 60 85 - <style> 86 - .autoscroll-container { 87 - position: relative; 88 - overflow: hidden; 89 - width: 100%; 90 - max-width: 100%; 91 - } 92 - 93 - .autoscroll-text { 94 - display: inline-block; 95 - white-space: nowrap; 96 - } 97 - 98 - @keyframes autoscroll { 99 - 0%, 10% { 100 - transform: translateX(0); 101 - } 102 - 45%, 55% { 103 - transform: translateX(var(--overflow-amount, -100px)); 104 - } 105 - 90%, 100% { 106 - transform: translateX(0); 107 - } 108 - } 109 - </style> 110 - 111 61 <div class="mx-auto w-full max-w-2xl"> 112 62 {#if loading} 113 63 <Card loading={true} variant="elevated" padding="md"> ··· 126 76 </div> 127 77 {/snippet} 128 78 </Card> 79 + 129 80 {:else if error} 130 81 <Card error={true} errorMessage={error} /> 82 + 131 83 {:else if musicStatus} 132 84 {@const safeMusicStatus = musicStatus} 133 85 <Card variant="elevated" padding="md"> 134 86 {#snippet children()} 135 87 <div class="flex items-start gap-4"> 136 - <!-- Artwork Section --> 88 + 89 + <!-- Artwork --> 137 90 <div class="flex-shrink-0"> 138 91 {#if safeMusicStatus.artworkUrl && !artworkError} 139 92 <img ··· 144 97 onerror={handleImageError} 145 98 /> 146 99 {:else} 147 - <!-- Fallback icon when no artwork or artwork fails to load --> 148 100 <div class="h-20 w-20 rounded-lg bg-canvas-200 dark:bg-canvas-700 flex items-center justify-center shadow-md"> 149 101 <Disc3 class="h-10 w-10 text-ink-500 dark:text-ink-400" aria-hidden="true" /> 150 102 </div> 151 103 {/if} 152 104 </div> 153 105 106 + <!-- Info --> 154 107 <div class="flex-1 min-w-0"> 108 + <!-- Header (Now Listening / Last Played) --> 155 109 <div class="mb-2 flex items-center gap-2"> 156 110 <Music class="h-4 w-4 text-primary-600 dark:text-primary-400" aria-hidden="true" /> 157 - <span 158 - class="text-xs font-semibold tracking-wide text-ink-800 uppercase dark:text-ink-100" 159 - > 160 - {safeMusicStatus.$type === 'fm.teal.alpha.actor.status' 161 - ? 'Now Listening' 111 + <span class="text-xs font-semibold tracking-wide text-ink-800 uppercase dark:text-ink-100"> 112 + {safeMusicStatus.$type === 'fm.teal.alpha.actor.status' 113 + ? 'Now Listening' 162 114 : 'Last Played'} 163 115 </span> 164 116 </div> 165 117 118 + <!-- Content --> 166 119 <div class="mb-2"> 167 - <div class="autoscroll-container"> 168 - <a 169 - bind:this={trackNameEl} 170 - href={safeMusicStatus.originUrl || '#'} 171 - target="_blank" 172 - rel="noopener noreferrer" 173 - class="autoscroll-text text-lg font-semibold text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 transition-colors" 174 - class:pointer-events-none={!safeMusicStatus.originUrl} 175 - class:cursor-default={!safeMusicStatus.originUrl} 176 - class:opacity-70={!safeMusicStatus.originUrl} 177 - > 178 - {safeMusicStatus.trackName} 179 - </a> 180 - </div> 181 120 182 - <div class="autoscroll-container"> 183 - <p bind:this={artistEl} class="autoscroll-text text-base text-ink-800 dark:text-ink-100"> 184 - {formatArtists(safeMusicStatus.artists)} 185 - </p> 186 - </div> 121 + <!-- Track Name --> 122 + <a 123 + href={safeMusicStatus.originUrl || '#'} 124 + target="_blank" 125 + rel="noopener noreferrer" 126 + class="block text-lg font-semibold text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 transition-colors whitespace-normal break-words max-w-full" 127 + class:pointer-events-none={!safeMusicStatus.originUrl} 128 + class:cursor-default={!safeMusicStatus.originUrl} 129 + class:opacity-70={!safeMusicStatus.originUrl} 130 + > 131 + {safeMusicStatus.trackName} 132 + </a> 187 133 134 + <!-- Artists --> 135 + <p class="mt-1 flex items-start gap-1.5 text-base text-ink-800 dark:text-ink-100 whitespace-normal break-words max-w-full"> 136 + <Users class="h-4 w-4 text-ink-600 dark:text-ink-300 flex-shrink-0 mt-0.5" /> 137 + {formatArtists(safeMusicStatus.artists)} 138 + </p> 139 + 140 + <!-- Album + Duration --> 188 141 {#if safeMusicStatus.releaseName} 189 - <div class="autoscroll-container"> 190 - <p bind:this={albumEl} class="autoscroll-text text-sm text-ink-700 dark:text-ink-200"> 142 + <p class="mt-1 flex items-start gap-1.5 text-sm text-ink-700 dark:text-ink-200 whitespace-normal break-words max-w-full"> 143 + <Album class="h-4 w-4 text-ink-500 dark:text-ink-400 flex-shrink-0 mt-0.5" /> 144 + <span> 191 145 {safeMusicStatus.releaseName} 146 + 192 147 {#if safeMusicStatus.duration} 193 - <span class="text-ink-600 dark:text-ink-300"> 194 - · {formatDuration(safeMusicStatus.duration)} 148 + <span class="inline-flex items-center gap-1 ml-1 text-ink-600 dark:text-ink-300"> 149 + · <Clock class="h-3 w-3" /> {formatDuration(safeMusicStatus.duration)} 195 150 </span> 196 151 {/if} 197 - </p> 198 - </div> 152 + </span> 153 + </p> 199 154 {/if} 200 155 </div> 201 156 157 + <!-- Footer / Meta --> 202 158 <div class="flex items-center gap-2 text-xs text-ink-700 dark:text-ink-200"> 203 159 <time datetime={safeMusicStatus.playedTime}> 204 160 {formatRelativeTime(safeMusicStatus.playedTime)} 205 161 </time> 162 + 206 163 {#if safeMusicStatus.musicServiceBaseDomain} 207 164 <span class="text-ink-600 dark:text-ink-300">·</span> 165 + 208 166 <a 209 167 href="https://teal.fm" 210 168 target="_blank" 211 169 rel="noopener noreferrer" 212 - class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 170 + class="inline-flex items-center gap-1 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 213 171 title="Powered by teal.fm" 214 172 > 215 - {formatServiceName(safeMusicStatus.musicServiceBaseDomain)} via teal.fm 173 + <Radio class="h-3 w-3" /> 174 + {formatServiceName(safeMusicStatus.musicServiceBaseDomain)} via {safeMusicStatus.submissionClientAgent} 216 175 </a> 217 176 {/if} 218 177 </div> ··· 221 180 {/snippet} 222 181 </Card> 223 182 {/if} 224 - </div> 183 + </div>