From f6dc9eaf7bb3095fc04bd38dd2b3b503fe29dbc7 Mon Sep 17 00:00:00 2001 From: Quentin Rouiller Date: Thu, 23 Apr 2026 15:03:54 +0200 Subject: [PATCH] =?UTF-8?q?v2026.5.21=20=E2=80=94=20Polish=20positionnemen?= =?UTF-8?q?t=20popups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- viewer.css | 30 ++++++++--- viewer.js | 140 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 145 insertions(+), 27 deletions(-) diff --git a/manifest.json b/manifest.json index ddc2bc2..0d63aa1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Planification", - "version": "2026.5.20", + "version": "2026.5.21", "description": "Vue claire et rapide du planning des techniciens EasyVista. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.", "permissions": ["activeTab", "scripting", "storage", "tabs", "alarms"], "host_permissions": [ diff --git a/viewer.css b/viewer.css index 58044e9..dfa5bd0 100644 --- a/viewer.css +++ b/viewer.css @@ -2591,15 +2591,16 @@ header.topbar::before { /* ========================================================================== v2026.5.17 : mode Minimisé (popup flottant compact, juste la ref) v2026.5.19 : refonte — élément .pinned-popup-minref créé à la volée + v2026.5.21 : agrandi pour que la ref tienne sans déborder ========================================================================== */ .pinned-popup.pinned-popup-minimized { - min-width: 180px !important; - max-width: 260px !important; - width: auto !important; + min-width: 240px !important; + max-width: 320px !important; + width: 260px !important; height: auto !important; - min-height: 60px !important; - padding: 28px 10px 10px 10px !important; - overflow: hidden; + min-height: 70px !important; + padding: 36px 14px 14px 14px !important; + overflow: visible; background: var(--bg-elevated) !important; border: 1px solid var(--border) !important; } @@ -2611,15 +2612,18 @@ header.topbar::before { .pinned-popup-minref { display: block; text-align: center; - padding: 6px 8px; + padding: 8px 10px; font-family: var(--mono, monospace); - font-size: 14px; + font-size: 15px; font-weight: 700; color: var(--text); cursor: pointer; user-select: none; border-radius: 4px; transition: background 0.12s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .pinned-popup-minref:hover { background: var(--bg-muted); @@ -2891,3 +2895,13 @@ body.popup-dragging .pinned-popup { width: 16px; text-align: center; } + +/* ========================================================================== + v2026.5.21 : icône 📍 "active" dans le tooltip hover = déjà épinglée + ========================================================================== */ +.tooltip-pinbtn.tooltip-pinbtn-active { + opacity: 1 !important; + filter: none !important; + background: rgba(239, 68, 68, 0.15); + border-radius: 4px; +} diff --git a/viewer.js b/viewer.js index 84b8a77..5bcc08e 100644 --- a/viewer.js +++ b/viewer.js @@ -6554,6 +6554,34 @@ function pinTooltip() { if (!srcEl) return; const iv = state.currentTooltipIv; + // v2026.5.21 : unicité actionId + date. Si un popup pour la même ref + // ET la même date est déjà épinglé, on le supprime et on re-crée un nouveau + // (user a choisi ce comportement : "tu supprime le popup actuellement + // épinglé et tu répingle la nouvelle fenêtre"). + const currentDate = state.currentDate || ""; + const existingKey = (iv.actionId || "") + "|" + currentDate; + for (let i = pinnedPopups.length - 1; i >= 0; i--) { + const p = pinnedPopups[i]; + if (!p || !p.el) continue; + const aid = p.el.dataset.actionId || ""; + const d = p.el.dataset.originDate || ""; + if (aid + "|" + d === existingKey) { + // Retirer l'ancien (popup + pastille dock éventuelle) + if (p.el._linkedPill) { + try { p.el._linkedPill.remove(); } catch (e) {} + } + try { p.el.remove(); } catch (e) {} + pinnedPopups.splice(i, 1); + } + } + // Nettoyer un éventuel dock devenu vide + const dockEl = document.getElementById("pinned-popups-dock"); + if (dockEl && dockEl.querySelectorAll(".pinned-popup-dock-pill").length === 0) { + dockEl.classList.remove("visible"); + const closeAllBtn = document.getElementById("pinned-popups-close-all"); + if (closeAllBtn) closeAllBtn.remove(); + } + // Chercher la ligne source (row iv-v2) let rowEl = null; if (iv.actionId) { @@ -7592,10 +7620,10 @@ function bindTooltipInteractions() { } }); - // Double-Ctrl : v4.3.0 - // - Si 0 popup épinglé ET un tooltip live visible : épingler - // - Si EXACTEMENT 1 popup épinglé ET souris pas dessus : le fermer - // - Si 2+ popups épinglés : ne fait rien (ambigu, user doit utiliser Échap) + // Double-Ctrl : v2026.5.21 — toggle sur l'intervention survolée + // - Si tooltip visible et iv pas encore épinglée pour cette date : épingle + // - Si tooltip visible et iv déjà épinglée pour cette date : désépingle + // - Sinon (pas de tooltip visible) : rien // On détecte 2 keydown Control dans une fenêtre de 400 ms. let lastCtrlTs = 0; document.addEventListener("keydown", (e) => { @@ -7604,17 +7632,36 @@ function bindTooltipInteractions() { const now = performance.now(); if (now - lastCtrlTs < 400) { lastCtrlTs = 0; - if (pinnedPopups.length === 0) { - // Aucun popup épinglé : épingler le tooltip live s'il y en a un - if (state.currentTooltipIv) pinTooltip(); - } else if (pinnedPopups.length === 1) { - // 1 popup épinglé : le fermer si la souris n'est pas dessus - const p = pinnedPopups[0]; - if (!p.el.matches(":hover")) { - _closePinnedPopup(p.el); + const iv = state.currentTooltipIv; + if (!iv) return; + const pinState = _getPinStateForIv(iv); + if (pinState.isPinned && pinState.popup) { + // Déjà épinglée : désépingle (ferme le popup correspondant) + const idx = pinnedPopups.findIndex(p => p.el === pinState.popup); + if (idx >= 0) pinnedPopups.splice(idx, 1); + // Fermer aussi la pastille dock si elle existe + if (pinState.popup._linkedPill) { + try { pinState.popup._linkedPill.remove(); } catch (e) {} } + try { pinState.popup.remove(); } catch (e) {} + // Nettoyer dock si vide + const dock = document.getElementById("pinned-popups-dock"); + if (dock && dock.querySelectorAll(".pinned-popup-dock-pill").length === 0) { + dock.classList.remove("visible"); + const closeAllBtn = document.getElementById("pinned-popups-close-all"); + if (closeAllBtn) closeAllBtn.remove(); + } else { + _ensureDockCloseAllBtn(); + } + // Mettre à jour le tooltip hover (📍 → 📌) + const tip = tooltipEl(); + if (tip && tip.classList.contains("visible")) { + tip.innerHTML = buildTooltipHTML(iv); + } + } else { + // Pas encore épinglée : épingle + pinTooltip(); } - // 2+ popups : rien faire (Échap pour tout fermer) } else { lastCtrlTs = now; } @@ -7628,10 +7675,34 @@ function bindTooltipInteractions() { e.preventDefault(); const action = btn.dataset.action; if (action === "pin") { - // v4.3.0 : toujours épingler (le tooltip live clone son contenu en popup - // détaché). Pour désépingler, l'user utilise × sur le popup, ou Échap. + // v2026.5.21 : toggle — si déjà épinglée, désépingle ; sinon épingle if (state.currentTooltipIv) { - pinTooltip(); + const iv = state.currentTooltipIv; + const pinState = _getPinStateForIv(iv); + if (pinState.isPinned && pinState.popup) { + // Désépingle + const idx = pinnedPopups.findIndex(p => p.el === pinState.popup); + if (idx >= 0) pinnedPopups.splice(idx, 1); + if (pinState.popup._linkedPill) { + try { pinState.popup._linkedPill.remove(); } catch (err) {} + } + try { pinState.popup.remove(); } catch (err) {} + const dock = document.getElementById("pinned-popups-dock"); + if (dock && dock.querySelectorAll(".pinned-popup-dock-pill").length === 0) { + dock.classList.remove("visible"); + const closeAllBtn = document.getElementById("pinned-popups-close-all"); + if (closeAllBtn) closeAllBtn.remove(); + } else { + _ensureDockCloseAllBtn(); + } + // Mettre à jour le tooltip (📍 → 📌) + const tip = tooltipEl(); + if (tip && tip.classList.contains("visible")) { + tip.innerHTML = buildTooltipHTML(iv); + } + } else { + pinTooltip(); + } } } else if (action === "reload") { // v4.1.14 : recharger uniquement l'intervention actuellement affichée @@ -7772,12 +7843,25 @@ function buildTooltipHTML(iv) { rows.push(`
Cliquer pour ouvrir la fiche
`); } + // v2026.5.21 : icône épingle adaptative + // 📌 (épingle couchée) = pas encore épinglée, clic pour épingler + // 📍 (épingle plantée, rouge) = déjà épinglée pour cette (ref + date) + // — clic pour désépingler. NB : le popup a sa propre topbar avec un + // bouton 📍 explicite ; celui-ci dans le tooltip hover sert surtout + // à montrer visuellement l'état à l'user au survol. + const _pinState = _getPinStateForIv(iv); + const _pinIcon = _pinState.isPinned ? "📍" : "📌"; + const _pinTitle = _pinState.isPinned + ? "Cette intervention est déjà épinglée pour ce jour. Cliquer pour désépingler." + : "Épingler la bulle (ou double-Ctrl). Cliquer à nouveau pour libérer."; + const _pinClass = _pinState.isPinned ? " tooltip-pinbtn-active" : ""; + if (rows.length === 0) { return `
-
📌
+
${_pinIcon}
Info
Aucun détail disponible
`; } // v4.1.13/14 : boutons d'action en haut à droite (recharger + épingler) @@ -7785,10 +7869,30 @@ function buildTooltipHTML(iv) {
-
📌
+
${_pinIcon}
${rows.join("")}
`; } +/** + * v2026.5.21 : retourne l'état d'épinglage pour une intervention donnée + * (pour la date actuellement affichée). Utilisé pour afficher 📌 vs 📍 + * dans le tooltip hover. + */ +function _getPinStateForIv(iv) { + if (!iv || !iv.actionId) return { isPinned: false }; + const date = state.currentDate || ""; + const key = iv.actionId + "|" + date; + for (const p of pinnedPopups) { + if (!p || !p.el) continue; + const aid = p.el.dataset.actionId || ""; + const d = p.el.dataset.originDate || ""; + if (aid + "|" + d === key) { + return { isPinned: true, popup: p.el }; + } + } + return { isPinned: false }; +} + /** * Met en forme un texte d'action EasyVista en ajoutant des retours à la ligne * avant chaque étiquette connue ("Date :", "Lieu :", "Contact :", etc.).