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>
38 lines
1.6 KiB
TypeScript
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');
|
|
}
|