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 @@
-