add video backend server, fix styling and more #54
+10
-2
@@ -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
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user