watchparty/client/src/views/artist/Artist.ts
Nicolas 5be839d2f6
All checks were successful
Publier l'image Docker / test (push) Successful in 23s
Publier l'image Docker / publish (push) Successful in 13s
Music bar: refresh vote count on join/leave, progress bar; catalog search
Four smaller fixes from real usage:

- MusicBar.ts: "votes needed" for skip depends on the connected-member
  count (renderMembers/"members") AND the vote tally (applyMusicState/
  "queueUpdated") - the label was only ever recomputed from the latter,
  so a plain join/leave with no vote left it stale. Cached the last
  MusicPublicState and factored the label math into its own
  updateVoteSkipLabel(), called from both handlers now.
- Home.ts: the listen party this tab is already in still showed
  "Rejoindre" on the home page. Checks each card's partyId against
  musicBar/MusicBar.ts's own getActiveMusicPartyId() (already tracked
  client-side) and swaps the button for an inert "En écoute" label on
  that one card. No equivalent case for video - its player is a
  full-screen takeover, so you can't be looking at this list from inside
  one.
- MusicBar.html/.css/.ts: a read-only progress bar (a thin strip along
  the bar's own top edge). Deliberately not seekable - no host to
  arbitrate a jump in this model, and MusicQueueService has no "seek"
  concept server-side today; that would be a real protocol addition, out
  of scope here. Fill width is just currentTime/duration, ticked by the
  same interval that already updates the position text.
- tiles.ts (new wireSearchFilter), views/library, views/artist,
  views/album: a search input per catalog page, filtering whatever's
  already rendered client-side (a library page tops out at a few hundred
  items - well within "just filter the DOM" territory, no server round
  trip per keystroke). Shared helper so the three pages behave
  identically. The existing /api/catalog/search endpoint (video types
  only, never wired into any UI) is a separate, unrelated feature - a
  global cross-library search - left as-is.

Verified against the real compiled bundle: a "members" push alone
updates the skip label; the home page's own party shows "En écoute" while
others still show "Rejoindre"; the progress fill tracks a simulated
currentTime correctly; typing into a library's search box filters tiles
live with the empty-state notice appearing only when nothing matches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:44:23 +02:00

38 lines
1.6 KiB
TypeScript

import html from './Artist.html';
import { getArtistAlbums, getItem } from '../../api/embyApi';
import type { RouteParams } from '../../router';
import { tileHtml, wireSearchFilter } from '../../tiles';
/** Route `/artist/:id` - one artist's albums, reached from a music
* library's top-level grid (see views/library) via a MusicArtist tile's
* "Voir les albums" link (tiles.ts's DRILL_DOWN table). Structurally the
* same "detail header + grid" shape as views/series, just one level up
* the music hierarchy (Artist -> Album, not Series -> Season/Episode) and
* with no per-item action beyond drilling further into an album (an
* artist has nothing itself to start a party from). */
export async function renderArtist(root: HTMLElement, params: RouteParams): Promise<void> {
const artistId = params.id as string;
root.innerHTML = '<p class="hint">Chargement...</p>';
let artist;
let albums;
try {
[artist, albums] = await Promise.all([getItem(artistId), getArtistAlbums(artistId)]);
} catch {
root.innerHTML = '<p class="error">Impossible de charger cet artiste.</p>';
return;
}
root.innerHTML = html;
root.querySelector('h1')!.textContent = artist.name;
root.querySelector('#back')!.addEventListener('click', () => history.back());
const grid = root.querySelector('#grid')!;
if (!albums.length) {
grid.innerHTML = '<p class="hint">Aucun album trouvé pour cet artiste.</p>';
root.querySelector('#searchInput')!.remove();
return;
}
grid.innerHTML = albums.map(tileHtml).join('');
wireSearchFilter(root.querySelector('#searchInput') as HTMLInputElement, root, '.tile');
}