Lm : plus de AJAX, plus de soucis de token, mais loader pas fou, du a l'app en elle meme
This commit is contained in:
parent
d6f06dfd7b
commit
0125e2ae69
|
|
@ -512,3 +512,49 @@ a.grille-garanties:hover{
|
|||
@keyframes l13 {
|
||||
100% { transform: rotate(1turn); }
|
||||
}
|
||||
|
||||
/* Message d'erreur du loader */
|
||||
#error-message {
|
||||
display: none;
|
||||
color: #ff4444;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
white-space: pre-line;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Message de timeout du loader */
|
||||
#timeout-message {
|
||||
display: none;
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: #d0d0d0;
|
||||
font-size: 14px;
|
||||
max-width: 420px;
|
||||
line-height: 1.6;
|
||||
padding: 0 20px;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Lien cliquable pour annuler le chargement */
|
||||
#cancel-loading-link {
|
||||
color: #66B2FF;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
text-decoration-thickness: 1px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#cancel-loading-link:hover {
|
||||
color: #90CAF9;
|
||||
text-decoration-thickness: 2px;
|
||||
text-shadow: 0 0 10px rgba(102, 178, 255, 0.6);
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/* Overlay loader (css pris du site https://css-loaders.com/)*/
|
||||
#loader-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: linear-gradient(
|
||||
rgba(10, 20, 60, 0.2),
|
||||
rgba(0, 0, 0, 0.4)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(0px);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s ease, backdrop-filter 0.5s ease;
|
||||
}
|
||||
#loader-overlay.active {
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(3px);
|
||||
pointer-events: all;
|
||||
}
|
||||
#loader-overlay.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Spinner wrapper (fade/slide) */
|
||||
.loader-spin-wrap {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
transition-delay: 0.5s; /* apparaît après 0.5s */
|
||||
}
|
||||
#loader-overlay.active .loader-spin-wrap {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Spinner circulaire */
|
||||
.loader-spin {
|
||||
width: 50px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
mask:1;
|
||||
background:
|
||||
radial-gradient(farthest-side,darkblue 94%,transparent) top/8px 8px no-repeat,
|
||||
conic-gradient(transparent 30%,darkblue);
|
||||
-webkit-mask: radial-gradient(farthest-side,transparent calc(100% - 8px),#000 0);
|
||||
animation: l13 1s infinite linear;
|
||||
}
|
||||
@keyframes l13 {
|
||||
100% { transform: rotate(1turn); }
|
||||
}
|
||||
|
||||
/* Erreur */
|
||||
#error-message {
|
||||
display: none;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
|
@ -107,7 +107,15 @@ function isNullOrUndefined(value) {
|
|||
}
|
||||
|
||||
// Fonction pour éviter la duplication du code fetch
|
||||
async function fetchWithJson(url, method, body = null) {
|
||||
async function fetchWithJson(url, method, body = null, useLoader = true) {
|
||||
let loaderShown = false;
|
||||
|
||||
if (useLoader && typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
loaderShown = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
|
@ -117,7 +125,20 @@ async function fetchWithJson(url, method, body = null) {
|
|||
const response = await fetch(url, options);
|
||||
if (!response.ok) throw new Error('Réseau ou erreur serveur');
|
||||
|
||||
return await response.json();
|
||||
const data = await response.json();
|
||||
|
||||
if (loaderShown && typeof hideLoader === 'function') {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
// Toujours cacher le loader en cas d'erreur
|
||||
if (loaderShown && typeof hideLoader === 'function') {
|
||||
hideLoader();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction de load du parcours en session storage
|
||||
|
|
|
|||
|
|
@ -1,31 +1,129 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const loader = document.getElementById("loader-overlay"); // déjà présent dans le layout.ejs
|
||||
let activateTimeout = null;
|
||||
let timeoutWarning = null;
|
||||
let currentPageLoad = null;
|
||||
let safetyTimeout = null; // Timeout de sécurité pour éviter que le loader reste bloqué
|
||||
|
||||
/**
|
||||
* Active le loader et le rend visible
|
||||
* Affiche un message d'annulation après 8 secondes
|
||||
*/
|
||||
window.showLoader = function() {
|
||||
const loader = document.getElementById("loader-overlay");
|
||||
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
let activateTimeout = null;
|
||||
|
||||
//activer le loader et le montrer a l'écran
|
||||
window.showLoader = function() {
|
||||
// Nettoyer les timeouts précédents
|
||||
clearTimeout(activateTimeout);
|
||||
errorMessage.style.display = "none";
|
||||
clearTimeout(timeoutWarning);
|
||||
|
||||
// Réinitialiser l'affichage
|
||||
if (errorMessage) errorMessage.style.display = "none";
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
loader.classList.remove("hidden");
|
||||
|
||||
// Afficher le loader après 500ms (évite le flash sur les chargements rapides)
|
||||
activateTimeout = setTimeout(() => {
|
||||
if (loader && !loader.classList.contains("hidden")) {
|
||||
loader.classList.add("active");
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Afficher le message de timeout après 8 secondes
|
||||
timeoutWarning = setTimeout(() => {
|
||||
if (loader && loader.classList.contains("active") && !loader.classList.contains("hidden")) {
|
||||
if (timeoutMessage) {
|
||||
timeoutMessage.style.display = "block";
|
||||
}
|
||||
}
|
||||
}, 8000);
|
||||
|
||||
// Protection : cacher automatiquement le loader après 30 secondes maximum
|
||||
// Au cas où hideLoader ne serait jamais appelé (page qui ne charge pas, erreur, etc.)
|
||||
if (safetyTimeout) clearTimeout(safetyTimeout);
|
||||
safetyTimeout = setTimeout(() => {
|
||||
if (loader && !loader.classList.contains("hidden")) {
|
||||
hideLoader();
|
||||
}
|
||||
}, 30000); // 30 secondes max
|
||||
};
|
||||
|
||||
/**
|
||||
* Désactive et cache le loader
|
||||
*/
|
||||
window.hideLoader = function() {
|
||||
const loader = document.getElementById("loader-overlay");
|
||||
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
|
||||
clearTimeout(activateTimeout);
|
||||
clearTimeout(timeoutWarning);
|
||||
if (safetyTimeout) {
|
||||
clearTimeout(safetyTimeout);
|
||||
safetyTimeout = null;
|
||||
}
|
||||
|
||||
loader.classList.remove("active");
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
|
||||
setTimeout(() => {
|
||||
if (loader) {
|
||||
loader.classList.add("hidden");
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
//enlever le loader et le faire disparaitre
|
||||
window.hideLoader = function() {
|
||||
clearTimeout(activateTimeout);
|
||||
loader.classList.remove("active");
|
||||
setTimeout(() => loader.classList.add("hidden"), 500);
|
||||
};
|
||||
|
||||
//cas d'erreur
|
||||
/**
|
||||
* Affiche un message d'erreur dans le loader
|
||||
* @param {string} msg - Message d'erreur à afficher
|
||||
*/
|
||||
window.showError = function(msg) {
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
|
||||
if (!errorMessage) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
clearTimeout(activateTimeout);
|
||||
clearTimeout(timeoutWarning);
|
||||
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
|
||||
errorMessage.textContent = msg;
|
||||
errorMessage.style.display = "block";
|
||||
};
|
||||
|
||||
/**
|
||||
* Annule le chargement en cours et cache le loader
|
||||
*/
|
||||
const cancelLoadingLink = document.getElementById("cancel-loading-link");
|
||||
if (cancelLoadingLink) {
|
||||
cancelLoadingLink.addEventListener("click", () => {
|
||||
// Annuler le chargement de la page en cours si possible
|
||||
if (currentPageLoad && currentPageLoad.abort) {
|
||||
currentPageLoad.abort();
|
||||
}
|
||||
|
||||
hideLoader();
|
||||
|
||||
// Afficher une notification discrète
|
||||
if (typeof M !== 'undefined' && M.toast) {
|
||||
M.toast({
|
||||
html: 'Chargement annulé',
|
||||
classes: 'rounded grey darken-3',
|
||||
displayLength: 2500
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de stocker l'abort controller du chargement en cours
|
||||
* @param {AbortController} controller - Controller de la requête fetch
|
||||
*/
|
||||
window.setCurrentPageLoad = function(controller) {
|
||||
currentPageLoad = controller;
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,49 +1,40 @@
|
|||
/**
|
||||
* Affiche le loader lors des navigations entre pages (localhost)
|
||||
* Comme on est en localhost, on peut se permettre d'afficher le loader
|
||||
* au clic sur un lien, il disparaîtra automatiquement au chargement de la nouvelle page
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const container = document.querySelector(".container");
|
||||
|
||||
async function loadPage(url, push = true) {
|
||||
showLoader();
|
||||
|
||||
try {
|
||||
|
||||
const res = await fetch(url, { headers: { "X-Requested-With": "fetch" }});
|
||||
const html = await res.text();
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
const newContent = doc.querySelector(".container").innerHTML;
|
||||
|
||||
container.innerHTML = newContent;
|
||||
|
||||
if (push) history.pushState({}, "", url);
|
||||
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
showError("Impossible de charger la page. Vérifiez votre connexion.");
|
||||
}
|
||||
|
||||
finally {
|
||||
hideLoader();
|
||||
}
|
||||
}
|
||||
|
||||
// Intercepter les clics sur les liens internes
|
||||
// Intercepter les clics sur les liens internes pour afficher le loader
|
||||
document.body.addEventListener("click", (e) => {
|
||||
const link = e.target.closest("a");
|
||||
|
||||
if (link && link.getAttribute("href").startsWith("/")) {
|
||||
|
||||
e.preventDefault();
|
||||
loadPage(link.href);
|
||||
// Si c'est un lien interne (commence par /) et pas un lien spécial
|
||||
if (link &&
|
||||
link.getAttribute("href") &&
|
||||
link.getAttribute("href").startsWith("/") &&
|
||||
!link.classList.contains("modal-close") &&
|
||||
!link.getAttribute("href").startsWith("/#")) {
|
||||
|
||||
// Afficher le loader avant la navigation
|
||||
if (typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Gérer le bouton retour
|
||||
window.addEventListener("popstate", () => {
|
||||
|
||||
loadPage(location.pathname, false);
|
||||
// Afficher le loader lors du rechargement (F5, Cmd+R)
|
||||
window.addEventListener("beforeunload", () => {
|
||||
if (typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
}
|
||||
});
|
||||
|
||||
// Cacher le loader quand la page est complètement chargée
|
||||
window.addEventListener("load", () => {
|
||||
if (typeof hideLoader === 'function') {
|
||||
// Petit délai pour éviter le flash
|
||||
setTimeout(hideLoader, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -25,8 +25,6 @@
|
|||
<link rel="stylesheet" href="/css/global.css">
|
||||
<!-- DataTables CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.8/css/dataTables.dataTables.css">
|
||||
<!-- Loader CSS : style pour l'affichage du loader -->
|
||||
<link rel="stylesheet" href="/css/loader.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
@ -37,7 +35,10 @@
|
|||
<div class="loader-spin-wrap">
|
||||
<div class="loader-spin"></div>
|
||||
</div>
|
||||
<div id="error-message" class="helper-text error"></div>
|
||||
<div id="error-message"></div>
|
||||
<div id="timeout-message">
|
||||
Si le chargement ne finit pas, <span id="cancel-loading-link">cliquez ici</span> pour annuler et vérifier votre connexion.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navbar -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue