From a1295003fdb12d339aa2ccc4def61de61947f20c Mon Sep 17 00:00:00 2001 From: jeanotx32 Date: Wed, 12 Aug 2026 22:44:10 +0200 Subject: [PATCH] Feat : Interface scalable size --- README.md | 12 +++++++ packages/web/src/components/AgentCard.tsx | 5 ++- packages/web/src/components/StreamersPage.tsx | 36 +++++++++++++++++-- packages/web/src/streamers.ts | 30 ++++++++++++++++ packages/web/src/styles.css | 12 +++++++ 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0523acd..501e08a 100644 --- a/README.md +++ b/README.md @@ -708,6 +708,18 @@ inutile de forcer l'invalidation du cache navigateur en plus. Se rafraîchit donc au rythme de la veille (`WATCHLIST_INTERVAL_MS`, 30 s par défaut) — un aperçu, pas un flux temps réel. +**Sa taille se règle** depuis la barre de filtres de l'onglet Streamers : *Aucun*, *Petit*, +*Moyen* (par défaut) ou *Grand*. Selon la largeur de l'écran et le nombre de profils suivis, +un aperçu pleine largeur rend la liste agréable ou la rend illisible à force de faire défiler +— d'où un réglage plutôt qu'un choix imposé. C'est la **largeur** qui est bridée, pas la +hauteur : la boîte 16/9 entière rétrécit, là où une hauteur maximale l'aurait recadrée en +bandeau et rogné l'image. + +*Aucun* ne se contente pas de masquer : la vignette n'est alors pas rendue du tout, donc pas +téléchargée — ce sont des images distantes, autant ne pas les demander quand personne ne les +regarde. Le choix est mémorisé dans le navigateur : c'est un confort propre à l'écran qu'on a +sous les yeux, pas un réglage à imposer aux autres postes. + ## Frise des diffusions Deux vues sur le même historique : la fiche d'un profil (clic sur sa vignette, onglet diff --git a/packages/web/src/components/AgentCard.tsx b/packages/web/src/components/AgentCard.tsx index b79bf96..61b7177 100644 --- a/packages/web/src/components/AgentCard.tsx +++ b/packages/web/src/components/AgentCard.tsx @@ -63,8 +63,11 @@ export function AgentCard({

{agent.name}

+ {/* Le nom de machine ne figure plus ici : il redoublait le nom affiché + de la VM sans rien en dire de plus. Il reste porté par l'agent, et + donc consultable via l'API. */} - {agent.hostname ?? '—'} · {agent.platform} + {agent.platform} {agent.agentVersion ? ` · v${agent.agentVersion}` : ''} {status.buildId ? ` · build ${status.buildId}` : ''} diff --git a/packages/web/src/components/StreamersPage.tsx b/packages/web/src/components/StreamersPage.tsx index 2f7ce78..38b4820 100644 --- a/packages/web/src/components/StreamersPage.tsx +++ b/packages/web/src/components/StreamersPage.tsx @@ -4,12 +4,16 @@ import { DEFAULT_PRIORITY, priorityLabel } from '@stream-control/shared'; import { api } from '../api'; import { formatDateTime, formatDuration, formatRelative } from '../format'; import { + PREVIEW_SIZES, + loadPreviewSize, preemption, previewSrc, recordingContext, + savePreviewSize, stateLabel, tagStyle, usedTags, + type PreviewSize, type RecordingContext, } from '../streamers'; import { StreamerDetail } from './StreamerDetail'; @@ -44,6 +48,14 @@ export function StreamersPage({ const [openId, setOpenId] = useState(null); const [selectedTags, setSelectedTags] = useState>(new Set()); const [managingTags, setManagingTags] = useState(false); + const [previewSize, setPreviewSize] = useState(loadPreviewSize); + + function changePreviewSize(size: PreviewSize) { + setPreviewSize(size); + // Mémorisé côté navigateur : c'est un confort d'affichage propre à l'écran + // qu'on a sous les yeux, pas un réglage à imposer aux autres postes. + savePreviewSize(size); + } // Tirées des profils plutôt que du serveur : la liste arrive déjà entière par // le flux temps réel, donc toujours à jour, et une étiquette que personne ne @@ -142,6 +154,21 @@ export function StreamersPage({ ))}
+ Aperçus + {PREVIEW_SIZES.map((size) => ( + + ))} {visible.length} profil(s)
)} @@ -188,7 +215,7 @@ export function StreamersPage({ ) : visible.length === 0 ? (

Aucun profil pour ce filtre.

) : ( -
+
{visible.map((target) => ( setOpenId(target.id)} /> ))} @@ -235,12 +263,14 @@ function StreamerCard({ targets, agents, notify, + previewSize, onOpen, }: { target: WatchTarget; targets: WatchTarget[]; agents: AgentView[]; notify: (message: string, tone?: 'info' | 'error') => void; + previewSize: PreviewSize; onOpen: () => void; }) { const [busy, setBusy] = useState(false); @@ -255,7 +285,9 @@ function StreamerCard({ const context = recordingContext(target, agent, targets); const verdict = preemption(target, context); const isLive = target.state === 'public'; - const preview = isLive ? previewSrc(target) : null; + // Rien n'est calculé — ni chargé — quand les aperçus sont coupés : ce sont des + // images distantes, autant ne pas les demander si personne ne les regarde. + const preview = isLive && previewSize !== 'off' ? previewSrc(target) : null; const showPreview = preview !== null && preview !== brokenPreview; const liveFor = isLive && target.statusChangedAt ? Date.now() - target.statusChangedAt : null; diff --git a/packages/web/src/streamers.ts b/packages/web/src/streamers.ts index 8453a62..a4c0d07 100644 --- a/packages/web/src/streamers.ts +++ b/packages/web/src/streamers.ts @@ -166,6 +166,36 @@ export function usedTags(targets: WatchTarget[]): Array<{ id: string; name: stri return [...byId.values()].sort((a, b) => a.name.localeCompare(b.name)); } +/** + * Tailles proposées pour l'aperçu du flux sur les vignettes. + * + * Réglable plutôt que fixé : selon la largeur de l'écran et le nombre de + * profils suivis, un aperçu pleine largeur rend la liste agréable ou la rend + * illisible à force de faire défiler. `off` va jusqu'à ne rien charger du tout — + * ce sont des images distantes, autant ne pas les demander quand on ne les + * regarde pas. + */ +export const PREVIEW_SIZES = [ + { id: 'off', label: 'Aucun' }, + { id: 'sm', label: 'Petit' }, + { id: 'md', label: 'Moyen' }, + { id: 'lg', label: 'Grand' }, +] as const; + +export type PreviewSize = (typeof PREVIEW_SIZES)[number]['id']; + +const PREVIEW_SIZE_KEY = 'stream-control.previewSize'; + +/** Préférence enregistrée, ou « Moyen » à défaut. */ +export function loadPreviewSize(): PreviewSize { + const saved = localStorage.getItem(PREVIEW_SIZE_KEY); + return PREVIEW_SIZES.some((entry) => entry.id === saved) ? (saved as PreviewSize) : 'md'; +} + +export function savePreviewSize(size: PreviewSize): void { + localStorage.setItem(PREVIEW_SIZE_KEY, size); +} + /** Fenêtres proposées sous les frises. */ export const TIMELINE_RANGES = [ { days: 1, label: '24 h' }, diff --git a/packages/web/src/styles.css b/packages/web/src/styles.css index 95303cc..3bf8849 100644 --- a/packages/web/src/styles.css +++ b/packages/web/src/styles.css @@ -725,6 +725,18 @@ fieldset.group > legend { background: var(--panel-2); aspect-ratio: 16 / 9; cursor: zoom-in; + /* Bride la largeur, pas la hauteur : c'est la boîte 16/9 entière qui rétrécit, + là où une hauteur maximale l'aurait recadrée en bandeau et rogné l'image. */ + max-width: var(--preview-max, none); +} +/* Réglé sur la grille et hérité par les vignettes — voir le sélecteur de taille + sur la page Streamers. La taille « Aucun » ne passe pas par le CSS : la + vignette n'est alors pas rendue du tout, donc pas chargée. */ +.streamer-grid.preview-sm { + --preview-max: 150px; +} +.streamer-grid.preview-md { + --preview-max: 240px; } .streamer-preview img { width: 100%;