From 7c3d0b0436e676e507c03b3dc35713cd3681f06b Mon Sep 17 00:00:00 2001 From: Stephane Bouvard Date: Sat, 9 Aug 2025 14:17:54 +0200 Subject: [PATCH] Add image + noDelay --- modules/popup/css/popup.css | 11 ++++ modules/popup/css/popup.less | 11 ++++ modules/popup/popup.js | 120 ++++++++++++++++++++++++++--------- overlay.html | 1 - 4 files changed, 111 insertions(+), 32 deletions(-) diff --git a/modules/popup/css/popup.css b/modules/popup/css/popup.css index 22cc8de..f82ca7b 100644 --- a/modules/popup/css/popup.css +++ b/modules/popup/css/popup.css @@ -19,6 +19,17 @@ text-align: center; transition: opacity 0.5s ease-out; } +#popupContainer .overlay-popup .overlay-popup-image { + position: absolute; + top: 0; + right: 0; + width: 48px; + height: 48px; + transform: translate(50%, -50%); + border-radius: 50%; + object-fit: cover; + border: 2px solid #444; +} #popupContainer .overlay-popup.fade-out { opacity: 0; } diff --git a/modules/popup/css/popup.less b/modules/popup/css/popup.less index af5133f..6c3bfea 100644 --- a/modules/popup/css/popup.less +++ b/modules/popup/css/popup.less @@ -20,6 +20,17 @@ max-width: 20%; text-align: center; transition: opacity 0.5s ease-out; + .overlay-popup-image { + position: absolute; + top: 0; + right: 0; + width: 48px; + height: 48px; + transform: translate(50%, -50%); + border-radius: 50%; + object-fit: cover; + border: 2px solid #444; + } } .overlay-popup.fade-out { diff --git a/modules/popup/popup.js b/modules/popup/popup.js index d3f6fcb..4738703 100644 --- a/modules/popup/popup.js +++ b/modules/popup/popup.js @@ -2,6 +2,8 @@ const moduleUrl = window.getModuleUrl(); loadCSSModule('overlay-popup-css', moduleUrl + '/css/popup.css'); + const FADE_OUT_MS = 500; + function initModule() { const container = document.getElementById('mainContainer') || document.body; if (!document.getElementById('popupContainer')) { @@ -12,18 +14,10 @@ } initModule(); - if (window.SBdispatcher) { - SBdispatcher.on('stream-popup', data => { - showPopup( - data.param1, - data.param2, - parseFloat(data.param3) || 3, - data.param4 != null ? parseFloat(data.param4) : -1, - data.param5 != null ? parseFloat(data.param5) : -1 - ); - }); - } + // Tableau global des popups actifs pour éviter le chevauchement + window._activePopups = window._activePopups || []; + // ===== Helpers ===== function randomPercent(min = 0, max = 100) { const minClamped = Math.ceil(min); const maxClamped = Math.floor(max); @@ -34,46 +28,96 @@ return !(r1.right < r2.left || r1.left > r2.right || r1.bottom < r2.top || r1.top > r2.bottom); } - // Tableau global des popups actifs pour éviter le chevauchement - window._activePopups = window._activePopups || []; + function findPopupByRef(ref) { + if (!ref) return null; + return window._activePopups.find(el => el.dataset && el.dataset.ref === String(ref)) || null; + } + + function removePopupElement(popupEl) { + if (!popupEl || !popupEl.isConnected) return; + if (popupEl._timeoutId) { + clearTimeout(popupEl._timeoutId); + popupEl._timeoutId = null; + } + popupEl.classList.add('fade-out'); + setTimeout(() => { + if (popupEl && popupEl.remove) popupEl.remove(); + const idx = window._activePopups.indexOf(popupEl); + if (idx !== -1) window._activePopups.splice(idx, 1); + }, FADE_OUT_MS); + } + + // ===== API ===== + /** + * Supprime manuellement un popup par sa référence. + * @param {string} reference + * @returns {boolean} true si supprimé, false sinon + */ + function deletePopup(reference) { + const target = findPopupByRef(reference); + if (target) { + removePopupElement(target); + return true; + } + return false; + } + // Expose globalement + window.deletePopup = deletePopup; /** * Affiche un popup dans #mainContainer (ou body si absent). * @param {string} title Titre du popup * @param {string} message Contenu HTML du message - * @param {number} delaySeconds Durée d'affichage en secondes + * @param {number} delaySeconds Durée d'affichage en secondes. -1 = ne pas auto-supprimer * @param {number|null} posX Position horizontale en %, -1 ou null = aléatoire * @param {number|null} posY Position verticale en %, -1 ou null = aléatoire + * @param {string|null} reference Identifiant unique pour ce popup (permet deletePopup) + * @param {string|null} image URL d'une image à afficher dans le popup */ - function showPopup(title, message, delaySeconds, posX = -1, posY = -1) { + function showPopup(title, message, delaySeconds, posX = -1, posY = -1, reference = null, image = null) { const container = document.getElementById('popupContainer') || document.body; const activePopups = window._activePopups; const maxAttempts = 10; + + // Remplacer un popup existant de même référence + if (reference) { + const existing = findPopupByRef(reference); + if (existing) removePopupElement(existing); + } + let attempt = 0; let popup, rect; do { - if (popup) { - popup.remove(); - } - // Calcul position (entre 10% et 90% si aléatoire) + if (popup) popup.remove(); + const x = (posX == null || posX < 0) ? randomPercent(10, 90) : posX; const y = (posY == null || posY < 0) ? randomPercent(10, 90) : posY; - // Création de l'élément popup popup = document.createElement('div'); popup.classList.add('overlay-popup'); popup.style.position = 'absolute'; popup.style.left = `${x}%`; popup.style.top = `${y}%`; - // Contenu + if (reference) popup.dataset.ref = String(reference); + const header = document.createElement('h3'); header.innerText = title; + const body = document.createElement('div'); body.innerHTML = message; + popup.append(header, body); + if (image) { + const img = document.createElement('img'); + img.src = image; + img.alt = title || 'image'; + img.classList.add('overlay-popup-image'); + popup.append(img); + } + container.appendChild(popup); rect = popup.getBoundingClientRect(); attempt++; @@ -82,17 +126,31 @@ activePopups.some(other => isOverlapping(rect, other.getBoundingClientRect())) ); - // Conserve ce popup dans la liste des actifs activePopups.push(popup); - // Suppression après délai et retrait de la liste - setTimeout(() => { - popup.classList.add('fade-out'); - setTimeout(() => { - popup.remove(); - const idx = activePopups.indexOf(popup); - if (idx !== -1) activePopups.splice(idx, 1); - }, 500); - }, delaySeconds * 1000); + if (delaySeconds !== -1) { + popup._timeoutId = setTimeout(() => removePopupElement(popup), delaySeconds * 1000); + } + } + + // ===== Events ===== + if (window.SBdispatcher) { + SBdispatcher.on('stream-popup', data => { + showPopup( + data.param1, // title + data.param2, // message (HTML) + parseFloat(data.param3) || 3, // delaySeconds + data.param4 != null ? parseFloat(data.param4) : -1, // posX + data.param5 != null ? parseFloat(data.param5) : -1, // posY + data.param6 != null ? String(data.param6) : null, // reference + data.param7 != null ? String(data.param7) : null // image URL + ); + }); + + // >>> Nouveau : suppression par event + SBdispatcher.on('stream-popup-delete', data => { + // data.param1 = reference + deletePopup(data?.param1 != null ? String(data.param1) : null); + }); } })(); diff --git a/overlay.html b/overlay.html index 8e925dc..61ec306 100644 --- a/overlay.html +++ b/overlay.html @@ -17,5 +17,4 @@ - \ No newline at end of file