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
+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.).