v2026.5.21 — Polish positionnement popups

This commit is contained in:
Quentin Rouiller
2026-04-23 15:03:54 +02:00
parent 3d5bdbab3d
commit f6dc9eaf7b
3 changed files with 145 additions and 27 deletions
+1 -1
View File
@@ -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": [
+22 -8
View File
@@ -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;
}
+122 -18
View File
@@ -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(`<dt></dt><dd style="color:var(--text-faint);font-size:11px">Cliquer pour ouvrir la fiche</dd>`);
}
// 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 `<div class="tooltip-actions">
<div class="tooltip-actionbtn" data-action="reload" title="Recharger uniquement cette intervention">
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M2 8a6 6 0 1 0 1.76-4.24M2 3v3h3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="tooltip-actionbtn tooltip-pinbtn" data-action="pin" title="Épingler la bulle (ou double-Ctrl). Cliquer à nouveau pour libérer.">📌</div>
<div class="tooltip-actionbtn tooltip-pinbtn${_pinClass}" data-action="pin" title="${_pinTitle}">${_pinIcon}</div>
</div><dl><dt>Info</dt><dd>Aucun détail disponible</dd></dl>`;
}
// v4.1.13/14 : boutons d'action en haut à droite (recharger + épingler)
@@ -7785,10 +7869,30 @@ function buildTooltipHTML(iv) {
<div class="tooltip-actionbtn" data-action="reload" title="Recharger uniquement cette intervention">
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M2 8a6 6 0 1 0 1.76-4.24M2 3v3h3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="tooltip-actionbtn tooltip-pinbtn" data-action="pin" title="Épingler la bulle (ou double-Ctrl). Cliquer à nouveau pour libérer.">📌</div>
<div class="tooltip-actionbtn tooltip-pinbtn${_pinClass}" data-action="pin" title="${_pinTitle}">${_pinIcon}</div>
</div><dl>${rows.join("")}</dl>`;
}
/**
* 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.).