Personal Site

Update metadata in place.

TODO:
- resolve issue where focusing on artists when artist list updates closes the metadata changes (fix: move focus to equivalent index in artist list)
- stop updating the data so frequently (only update on change)
- hide popup when nothing playing
- pause record player if nothing playing
- pause record head if nothing playing
- make record head move to stop position when changing song
- remove randomness from record head animation
- prolong the record head animation to 3 mins/length of song when js is enabled, to sync up better with real world (if it looks good)
- crossfade animations for images when changing song
- figure out what causes "The connection to http://localhost:3000/now-playing-sse was interrupted while the page was loading." error
- preload all assets
- figure out why firefox warns about unused box-circle-mask.png preload when it is used in bsky pfp in landing
- split up NowPlaying component if needed as is getting quite large

vielle.dev cfec434d 01ae5c63

verified
+54 -1
+54 -1
src/components/playing/NowPlaying.astro
··· 359 359 a { 360 360 color: black; 361 361 362 - &:focus, &:hover { 362 + &:focus, 363 + &:hover { 363 364 text-decoration-style: dashed; 364 365 } 365 366 ··· 373 374 <script> 374 375 import { type nowPlaying, isNowPlaying } from "./spotify/client"; 375 376 377 + class HTMLNowPlayingElement extends HTMLElement { 378 + internals = this.attachInternals(); 379 + 380 + elements = { 381 + // typecast bc checked in constructor 382 + title: this.querySelector("[slot=title]") as HTMLAnchorElement, 383 + album: this.querySelector("[slot=album]") as HTMLSpanElement, 384 + artists: this.querySelector("[slot=artists]") as HTMLSpanElement, 385 + art: this.querySelector("[slot=art]") as HTMLImageElement, 386 + } as const; 387 + 388 + constructor() { 389 + super(); 390 + 391 + if (!(this.elements.title instanceof HTMLAnchorElement)) 392 + throw new Error("[slot=title] not defined or wrong type"); 393 + 394 + if (!(this.elements.album instanceof HTMLSpanElement)) 395 + throw new Error("[slot=album] not defined or wrong type"); 396 + 397 + if (!(this.elements.artists instanceof HTMLSpanElement)) 398 + throw new Error("[slot=artists] not defined or wrong type"); 399 + 400 + if (!(this.elements.art instanceof HTMLImageElement)) 401 + throw new Error("[slot=art] not defined or wrong type"); 402 + } 403 + 404 + updateMetadata(metadata: Exclude<nowPlaying, null>) { 405 + this.elements.title.innerText = metadata.name; 406 + this.elements.title.href = metadata.external_urls.spotify; 407 + 408 + this.elements.album.innerText = metadata.album.name; 409 + 410 + this.elements.artists.innerText = ""; 411 + this.elements.artists.append(...metadata.artists.flatMap((artist, i) => { 412 + const a = document.createElement("a"); 413 + a.innerText = artist.name; 414 + a.href = artist.external_urls.spotify; 415 + return i === 0 ? a : [", ", a]; 416 + })); 417 + 418 + this.elements.art.src = metadata.album.images[0].url 419 + } 420 + } 421 + 376 422 const nowPlayingEl = document.getElementById("now-playing"); 377 423 if (!nowPlayingEl) throw new Error("Could not find #now-playing"); 378 424 const art = nowPlayingEl.querySelector(".art"); 379 425 if (!art || !(art instanceof HTMLImageElement)) 380 426 throw new Error("Could not find #now-playing img.art"); 427 + const nowPlayings = document.querySelectorAll("now-playing"); 428 + 429 + customElements.define( 430 + "now-playing", 431 + HTMLNowPlayingElement, 432 + ); 381 433 382 434 const renderNowPlaying = (playing: Exclude<nowPlaying, null>) => { 383 435 art.src = playing.album.images[0].url; 436 + nowPlayings.forEach(el => el instanceof HTMLNowPlayingElement && el.updateMetadata(playing)) 384 437 }; 385 438 386 439 const renderSilent = () => {