diff --git a/client/src/loading.ts b/client/src/loading.ts new file mode 100644 index 0000000..e70c326 --- /dev/null +++ b/client/src/loading.ts @@ -0,0 +1,17 @@ +// Shared "Chargement..." placeholder - spinner + text, see .spinner in +// styles/base.css for the actual CSS ring (no image/icon-font asset, just +// a bordered circle animated with a plain @keyframes rotation). One +// constant so every route/section that fetches before it has anything +// real to show (Album/Artist/Library/Playlist/Series's own route entry, +// Home's per-library carousels) renders the exact same markup instead of +// each hand-typing a slightly different one - a plain-text .hide/.hint +// mismatch is exactly the kind of drift a shared constant avoids. +// +// The sidebar's own library list (Shell.html) and Player.ts's video- +// buffering notice (Player.html's #loadingNotice) don't use this: the +// sidebar's markup is a static template with no JS import of its own +// (see Shell.html directly - same spinner markup, just pasted once by +// hand instead of imported), and the video notice is already a distinct, +// separately-styled centered overlay with its own text ("Chargement du +// flux..."), not a plain in-flow
. +export const LOADING_HINT = '
Chargement...
'; diff --git a/client/src/shell/Shell.html b/client/src/shell/Shell.html index de9bd4e..9fa156a 100644 --- a/client/src/shell/Shell.html +++ b/client/src/shell/Shell.html @@ -4,7 +4,7 @@ diff --git a/client/src/styles/base.css b/client/src/styles/base.css index 92e550d..5748950 100644 --- a/client/src/styles/base.css +++ b/client/src/styles/base.css @@ -51,6 +51,33 @@ h2 { font-size: 16px; margin: 0 0 12px; } .hint { color: var(--muted); font-size: 13px; margin: 0 0 16px; } .error { color: var(--danger); font-size: 13px; min-height: 18px; } +/* Plain CSS ring, not an image/icon-font asset - `display: inline-block` + + `vertical-align` so it drops into an existing text node (a .hint's + "Chargement...", Player.css's own .loadingNotice) without needing to + restructure the element into a flex row - see loading.ts's LOADING_HINT + for the one shared constant most call sites use instead of hand-typing + this markup. `prefers-reduced-motion` gets a static ring instead of an + endless spin, same reasoning as this app avoiding motion elsewhere. */ +.spinner { + display: inline-block; + vertical-align: -3px; + width: 14px; + height: 14px; + margin-right: 8px; + border: 2px solid rgba(255, 255, 255, .25); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin .7s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .spinner { animation: none; } +} + label { display: block; font-size: 13px; diff --git a/client/src/views/album/Album.ts b/client/src/views/album/Album.ts index eafdf1c..7876e3b 100644 --- a/client/src/views/album/Album.ts +++ b/client/src/views/album/Album.ts @@ -5,6 +5,7 @@ import { getAlbumTracks, getItem } from '../../api/embyApi'; import { addTrackToQueue, createParty } from '../../api/party'; import { getActiveMusicPartyId, joinMusicParty } from '../../musicBar/MusicBar'; import { escapeHtml, formatDuration, wireSearchFilter } from '../../tiles'; +import { LOADING_HINT } from '../../loading'; import type { ItemDto, MusicTrackInput } from '../../types'; import type { RouteParams } from '../../router'; @@ -16,7 +17,7 @@ const TICKS_PER_SECOND = 10000000; // Emby/.NET convention: 1 tick = 100ns, same * series' seasons do). */ export async function renderAlbum(root: HTMLElement, params: RouteParams): PromiseChargement...
'; + root.innerHTML = LOADING_HINT; let album: ItemDto; let tracks: ItemDto[]; diff --git a/client/src/views/artist/Artist.ts b/client/src/views/artist/Artist.ts index 62a0d96..43e7644 100644 --- a/client/src/views/artist/Artist.ts +++ b/client/src/views/artist/Artist.ts @@ -2,6 +2,7 @@ import html from './Artist.html'; import { getArtistAlbums, getItem } from '../../api/embyApi'; import type { RouteParams } from '../../router'; import { tileHtml, wireSearchFilter } from '../../tiles'; +import { LOADING_HINT } from '../../loading'; /** Route `/artist/:id` - one artist's albums, reached from a music * library's top-level grid (see views/library) via a MusicArtist tile's @@ -12,7 +13,7 @@ import { tileHtml, wireSearchFilter } from '../../tiles'; * artist has nothing itself to start a party from). */ export async function renderArtist(root: HTMLElement, params: RouteParams): PromiseChargement...
'; + root.innerHTML = LOADING_HINT; let artist; let albums; diff --git a/client/src/views/home/Home.ts b/client/src/views/home/Home.ts index 45af6e7..9086edf 100644 --- a/client/src/views/home/Home.ts +++ b/client/src/views/home/Home.ts @@ -5,6 +5,7 @@ import { connectLobby, createParty, joinParty } from '../../api/party'; import { getActiveMusicPartyId } from '../../musicBar/MusicBar'; import { navigate } from '../../router'; import { escapeHtml, tileHtml, wireTileButtons } from '../../tiles'; +import { LOADING_HINT } from '../../loading'; import type { LobbyPartyDto } from '../../types'; export function renderHome(root: HTMLElement): () => void { @@ -148,7 +149,7 @@ async function loadRecentSections(root: HTMLElement): PromiseChargement...
Chargement...
Chargement...
Chargement...
'; + root.innerHTML = LOADING_HINT; let libraries; try { diff --git a/client/src/views/player/Player.html b/client/src/views/player/Player.html index fa79e74..1cdc540 100644 --- a/client/src/views/player/Player.html +++ b/client/src/views/player/Player.html @@ -1,7 +1,7 @@Chargement du flux...
+Chargement du flux...