add video backend server, fix styling and more #54

Merged
quoije merged 131 commits from 2026-video.backend into prod 2025-12-22 05:42:04 -05:00
3 changed files with 88 additions and 40 deletions
Showing only changes of commit 282bc1750c - Show all commits
+10 -2
View File
@@ -58,7 +58,10 @@ footer {
.page-loader-overlay { .page-loader-overlay {
position: fixed; position: fixed;
inset: 0; top: var(--page-loader-top, 0px);
left: 0;
right: 0;
bottom: 0;
z-index: 2000; z-index: 2000;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -85,6 +88,11 @@ footer {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
} }
.page-loader-card.page-loader-card--baronly {
width: min(420px, 90vw);
padding: 14px;
}
.page-loader-title { .page-loader-title {
margin: 0 0 10px 0; margin: 0 0 10px 0;
font-weight: 700; font-weight: 700;
@@ -109,7 +117,7 @@ footer {
height: 100%; height: 100%;
width: 0%; width: 0%;
border-radius: 999px; border-radius: 999px;
background: linear-gradient(90deg, #ff4d4d 0%, #ffb200 50%, #4cd964 100%); background: #111;
transition: width 220ms ease; transition: width 220ms ease;
} }
+39 -19
View File
@@ -1,9 +1,11 @@
function createPageLoader(options = {}) { function createPageLoader(options = {}) {
const title = String(options.title || 'Chargement…'); const title = String(options.title || 'Chargement…');
// Subtitle kept for backwards-compat with callers, but we no longer render text in the UI.
const subtitle = String(options.subtitle || 'Préparation de la page…'); const subtitle = String(options.subtitle || 'Préparation de la page…');
let progress = 0; let progress = 0;
let removed = false; let removed = false;
let navResizeObserver = null;
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.className = 'page-loader-overlay'; overlay.className = 'page-loader-overlay';
@@ -12,22 +14,15 @@ function createPageLoader(options = {}) {
overlay.setAttribute('aria-busy', 'true'); overlay.setAttribute('aria-busy', 'true');
overlay.innerHTML = ` overlay.innerHTML = `
<div class="page-loader-card"> <div class="page-loader-card page-loader-card--baronly">
<p class="page-loader-title">${title}</p> <span class="visually-hidden">${title}</span>
<p class="page-loader-subtitle" id="page-loader-subtitle">${subtitle}</p>
<div class="page-loader-progress" aria-hidden="true"> <div class="page-loader-progress" aria-hidden="true">
<div class="page-loader-bar" id="page-loader-bar"></div> <div class="page-loader-bar" id="page-loader-bar"></div>
</div> </div>
<div class="page-loader-actions d-none" id="page-loader-actions">
<button type="button" class="btn btn-sm btn-outline-dark" id="page-loader-retry">Réessayer</button>
</div>
</div> </div>
`; `;
const barEl = () => overlay.querySelector('#page-loader-bar'); const barEl = () => overlay.querySelector('#page-loader-bar');
const subtitleEl = () => overlay.querySelector('#page-loader-subtitle');
const actionsEl = () => overlay.querySelector('#page-loader-actions');
const retryBtnEl = () => overlay.querySelector('#page-loader-retry');
function clampPct(p) { function clampPct(p) {
const n = Number(p); const n = Number(p);
@@ -41,16 +36,40 @@ function createPageLoader(options = {}) {
if (bar) bar.style.width = `${progress}%`; if (bar) bar.style.width = `${progress}%`;
} }
function setSubtitle(text) { function setSubtitle(_) {
const el = subtitleEl(); // Intentionally no-op (we only show the bar now).
if (el) el.textContent = String(text || ''); }
function getNavHeightPx() {
const nav = document.querySelector('nav.navbar');
if (!nav) return 0;
const rect = nav.getBoundingClientRect();
const h = Number(rect?.height) || 0;
return h > 0 ? Math.round(h) : 0;
}
function updateOverlayTopOffset() {
// Scope the offset to this overlay instance (no global CSS var).
overlay.style.setProperty('--page-loader-top', `${getNavHeightPx()}px`);
} }
function ensureMounted() { function ensureMounted() {
if (removed) return; if (removed) return;
if (!overlay.isConnected) { if (!overlay.isConnected) {
updateOverlayTopOffset();
document.body.classList.add('page-loading'); document.body.classList.add('page-loading');
document.body.appendChild(overlay); document.body.appendChild(overlay);
// Keep the overlay aligned if the navbar height changes (mobile collapse, resize, etc.)
const nav = document.querySelector('nav.navbar');
if (nav && typeof ResizeObserver !== 'undefined') {
try {
navResizeObserver = new ResizeObserver(() => updateOverlayTopOffset());
navResizeObserver.observe(nav);
} catch (_) {
navResizeObserver = null;
}
}
} }
} }
@@ -61,6 +80,10 @@ function createPageLoader(options = {}) {
document.body.classList.remove('page-loading'); document.body.classList.remove('page-loading');
window.setTimeout(() => { window.setTimeout(() => {
removed = true; removed = true;
if (navResizeObserver) {
try { navResizeObserver.disconnect(); } catch (_) {}
navResizeObserver = null;
}
try { overlay.remove(); } catch (_) {} try { overlay.remove(); } catch (_) {}
}, 220); }, 220);
} }
@@ -70,13 +93,10 @@ function createPageLoader(options = {}) {
hideAndRemoveSoon(); hideAndRemoveSoon();
} }
function fail(message) { function fail(_) {
setSubtitle(message || 'Erreur lors du chargement.'); // Don't block the UI on errors: let the page render its own error message.
setProgress(Math.max(progress, 90)); setProgress(Math.max(progress, 95));
const actions = actionsEl(); window.setTimeout(() => hideAndRemoveSoon(), 200);
if (actions) actions.classList.remove('d-none');
const btn = retryBtnEl();
if (btn) btn.onclick = () => window.location.reload();
} }
ensureMounted(); ensureMounted();
+39 -19
View File
@@ -1,9 +1,11 @@
function createPageLoader(options = {}) { function createPageLoader(options = {}) {
const title = String(options.title || 'Chargement…'); const title = String(options.title || 'Chargement…');
// Subtitle kept for backwards-compat with callers, but we no longer render text in the UI.
const subtitle = String(options.subtitle || 'Préparation de la page…'); const subtitle = String(options.subtitle || 'Préparation de la page…');
let progress = 0; let progress = 0;
let removed = false; let removed = false;
let navResizeObserver = null;
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.className = 'page-loader-overlay'; overlay.className = 'page-loader-overlay';
@@ -12,22 +14,15 @@ function createPageLoader(options = {}) {
overlay.setAttribute('aria-busy', 'true'); overlay.setAttribute('aria-busy', 'true');
overlay.innerHTML = ` overlay.innerHTML = `
<div class="page-loader-card"> <div class="page-loader-card page-loader-card--baronly">
<p class="page-loader-title">${title}</p> <span class="visually-hidden">${title}</span>
<p class="page-loader-subtitle" id="page-loader-subtitle">${subtitle}</p>
<div class="page-loader-progress" aria-hidden="true"> <div class="page-loader-progress" aria-hidden="true">
<div class="page-loader-bar" id="page-loader-bar"></div> <div class="page-loader-bar" id="page-loader-bar"></div>
</div> </div>
<div class="page-loader-actions d-none" id="page-loader-actions">
<button type="button" class="btn btn-sm btn-outline-dark" id="page-loader-retry">Réessayer</button>
</div>
</div> </div>
`; `;
const barEl = () => overlay.querySelector('#page-loader-bar'); const barEl = () => overlay.querySelector('#page-loader-bar');
const subtitleEl = () => overlay.querySelector('#page-loader-subtitle');
const actionsEl = () => overlay.querySelector('#page-loader-actions');
const retryBtnEl = () => overlay.querySelector('#page-loader-retry');
function clampPct(p) { function clampPct(p) {
const n = Number(p); const n = Number(p);
@@ -41,16 +36,40 @@ function createPageLoader(options = {}) {
if (bar) bar.style.width = `${progress}%`; if (bar) bar.style.width = `${progress}%`;
} }
function setSubtitle(text) { function setSubtitle(_) {
const el = subtitleEl(); // Intentionally no-op (we only show the bar now).
if (el) el.textContent = String(text || ''); }
function getNavHeightPx() {
const nav = document.querySelector('nav.navbar');
if (!nav) return 0;
const rect = nav.getBoundingClientRect();
const h = Number(rect?.height) || 0;
return h > 0 ? Math.round(h) : 0;
}
function updateOverlayTopOffset() {
// Scope the offset to this overlay instance (no global CSS var).
overlay.style.setProperty('--page-loader-top', `${getNavHeightPx()}px`);
} }
function ensureMounted() { function ensureMounted() {
if (removed) return; if (removed) return;
if (!overlay.isConnected) { if (!overlay.isConnected) {
updateOverlayTopOffset();
document.body.classList.add('page-loading'); document.body.classList.add('page-loading');
document.body.appendChild(overlay); document.body.appendChild(overlay);
// Keep the overlay aligned if the navbar height changes (mobile collapse, resize, etc.)
const nav = document.querySelector('nav.navbar');
if (nav && typeof ResizeObserver !== 'undefined') {
try {
navResizeObserver = new ResizeObserver(() => updateOverlayTopOffset());
navResizeObserver.observe(nav);
} catch (_) {
navResizeObserver = null;
}
}
} }
} }
@@ -61,6 +80,10 @@ function createPageLoader(options = {}) {
document.body.classList.remove('page-loading'); document.body.classList.remove('page-loading');
window.setTimeout(() => { window.setTimeout(() => {
removed = true; removed = true;
if (navResizeObserver) {
try { navResizeObserver.disconnect(); } catch (_) {}
navResizeObserver = null;
}
try { overlay.remove(); } catch (_) {} try { overlay.remove(); } catch (_) {}
}, 220); }, 220);
} }
@@ -70,13 +93,10 @@ function createPageLoader(options = {}) {
hideAndRemoveSoon(); hideAndRemoveSoon();
} }
function fail(message) { function fail(_) {
setSubtitle(message || 'Erreur lors du chargement.'); // Don't block the UI on errors: let the page render its own error message.
setProgress(Math.max(progress, 90)); setProgress(Math.max(progress, 95));
const actions = actionsEl(); window.setTimeout(() => hideAndRemoveSoon(), 200);
if (actions) actions.classList.remove('d-none');
const btn = retryBtnEl();
if (btn) btn.onclick = () => window.location.reload();
} }
ensureMounted(); ensureMounted();