5ef3bb90e839f6d218c6ce5988adb111d600d3df
chmalee
  Mon Apr 22 16:34:02 2024 -0700
Make some special tooltips have a longer delay than normal item
tooltips. This includes the track title tooltips.

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 20152b3..9c7e6f4 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -4016,87 +4016,104 @@
     /* Called after 500ms of the mouse being stationary, show a new tooltip
      * if we are over a new mouseover-able element */
     e = triggeringMouseMoveEv;
     if (mousedNewItem && !(mouseIsOverPopup(e, currTooltip, 0))) {
         mousemoveController.abort();
         hideMouseoverText(currTooltip);
         showMouseoverText(triggeringMouseMoveEv);
     }
 }
 
 function mousemoveHelper(e) {
     /* Helper function for deciding whether to keep a tooltip visible upon a mousemove event */
     if (mousemoveTimer) {
         clearTimeout(mousemoveTimer);
     }
+    let isDelayedTooltip = lastMouseoverEle.getAttribute("tooltipDelay");
+    if (isDelayedTooltip !== null && isDelayedTooltip === "delayed") {
+        mousemoveTimer = setTimeout(mousemoveTimerHelper, 3000, e, this);
+        mousedNewItem = true;
+        mousemoveController.abort();
+        hideMouseoverText(this);
+    } else {
         mousemoveTimer = setTimeout(mousemoveTimerHelper, 500, e, this);
         // we are moving the mouse away, hide the tooltip regardless how much time has passed
         if (!(mouseIsOverPopup(e, this) || mouseIsOverItem(e, this))) {
             mousemoveController.abort();
             hideMouseoverText(this);
             return;
         }
     }
+}
 
 function showMouseoverText(ev) {
     /* If a tooltip is not visible, show the tooltip text right away. If a tooltip
      * is visble, do nothing as the mousemove event helper will re-call us
      * after hiding the tooltip that is shown */
     ev.preventDefault();
     let referenceElement = lastMouseoverEle;
 
     if (!tooltipIsVisible()) {
         let tooltipDivId = "#" + referenceElement.getAttribute("mouseoverid");
         let tooltipDiv = $(tooltipDivId)[0];
         if (!tooltipDiv) {
             return;
         }
         mouseoverContainer.replaceChildren();
         let divCpy = tooltipDiv.cloneNode(true);
         divCpy.childNodes.forEach(function(n) {
             mouseoverContainer.appendChild(n);
         });
         positionMouseover(ev, referenceElement, mouseoverContainer, ev.pageX, ev.pageY);
         mouseoverContainer.classList.add("isShown");
         mouseoverContainer.style.opacity = "1";
         mouseoverContainer.style.visibility = "visible";
         mouseoverContainer.setAttribute("origItemMouseoverId", referenceElement.getAttribute("mouseoverid"));
+
+        // some tooltips are special and have a long delay, make sure the container knows
+        // that too
+        let isDelayed = referenceElement.getAttribute("tooltipDelay");
+        if (isDelayed !== null && isDelayed === "delayed") {
+            mouseoverContainer.setAttribute("isDelayedTooltip", "delayed");
+        } else {
+            mouseoverContainer.setAttribute("isDelayedTooltip", "normal");
+        }
         // Events all get their own unique id but they are tough to keep track of if we
         // want to remove one. We can use the AbortController interface to let the
         // web browser automatically raise a signal when the event is fired and remove
         // appropriate event
         mousemoveController = new AbortController();
         let callback = mousemoveHelper.bind(mouseoverContainer);
 
         mousedNewItem = false;
         clearTimeout(mouseoverTimer);
         mouseoverTimer = undefined;
         // allow the user to mouse over the mouse over, (eg. clicking a link or selecting text)
         document.addEventListener("mousemove", callback, {signal: mousemoveController.signal});
         document.addEventListener("scroll", callback, {signal: mousemoveController.signal});
     }
 }
 
 function showMouseover(e) {
     /* Helper function for showing a mouseover. Uses a timeout function to allow
      * user to not immediately see all available tooltips. */
     e.preventDefault();
 
     // make the mouseover div:
     let ele1 = e.currentTarget;
     let text = ele1.getAttribute("mouseoverText");
-    if (ele1.getAttribute("mouseoverId") === null) {
+    if (ele1.getAttribute("mouseoverid") === null) {
         if (text.length > 0) {
             let newEl = document.createElement("span");
             newEl.style = "max-width: 400px"; // max width of the mouseover text
             newEl.innerHTML = text;
 
             let newDiv = document.createElement("div");
             newDiv.className = "tooltip";
             newDiv.style.position = "fixed";
             newDiv.style.display = "inline-block";
             if (ele1.title) {
                 newDiv.id = replaceReserved(ele1.title);
                 ele1.setAttribute("originalTitle", ele1.title);
                 ele1.title = "";
             } else {
                 newDiv.id = replaceReserved(text);
@@ -4120,33 +4137,39 @@
     // this element on their way to mousing over the shown mouseover
     mousedNewItem = true;
     lastMouseoverEle = ele1;
     if (mouseoverTimer) {
         // user is moving their mouse around, make sure where they stop is what we show
         clearTimeout(mouseoverTimer);
     }
     if (mousemoveTimer) {
         // user is moving their mouse around and has triggered a potentially triggered
         // a new pop up, clear the move timeout
         clearTimeout(mousemoveTimer);
     }
     // If there is no tooltip present, we want a small but noticeable delay
     // before showing a tooltip
     if (canShowNewMouseover) {
+        // some tooltips are special and have a longer delay
+        let isDelayedTooltip = ele1.getAttribute("tooltipDelay");
+        if (isDelayedTooltip !== null && isDelayedTooltip === "delayed") {
+            mouseoverTimer = setTimeout(showMouseoverText, 3000, e);
+        } else {
             mouseoverTimer = setTimeout(showMouseoverText, 500, e);
         }
     }
+}
 
 function addMouseover(ele1, text = null, ele2 = null) {
     /* Adds wrapper elements to control various mouseover events */
     if (!mouseoverContainer) {
         mouseoverContainer = document.createElement("div");
         mouseoverContainer.className = "tooltip";
         mouseoverContainer.style.position = "fixed";
         mouseoverContainer.style.display = "inline-block";
         mouseoverContainer.style.visibility = "hidden";
         mouseoverContainer.style.opacity = "0";
         mouseoverContainer.id = "mouseoverContainer";
         let tooltipTextSize = localStorage.getItem("tooltipTextSize");
         if (tooltipTextSize === null) {tooltipTextSize = window.browserTextSize;}
         mouseoverContainer.style.fontSize =  tooltipTextSize + "px";
         document.body.append(mouseoverContainer);
@@ -4155,33 +4178,38 @@
     if (ele1) {
         ele1.setAttribute("mouseoverText", text);
         ele1.addEventListener("mouseover", showMouseover, {capture: true});
     }
 }
 
 function titleTagToMouseover(mapEl) {
     /* for a given area tag, extract the title text into a div that can be positioned
     * like a standard tooltip mouseover next to the item */
     addMouseover(mapEl, mapEl.title);
 }
 
 function convertTitleTagsToMouseovers() {
     /* make all the title tags in the document have mouseovers */
     $("[title]").each(function(i, a) {
-        if (a.title.startsWith("click & drag to scroll"))
+        if (a.title !== undefined && a.title.startsWith("click & drag to scroll"))
             a.title = "";
         else if (a.title !== undefined && a.title.length > 0) {
+            if (a.title.startsWith("Click to alter the display density") ||
+                    a.title.startsWith("drag select or click to zoom")) {
+                // these tooltips have a longer delay:
+                a.setAttribute("tooltipDelay", "delayed");
+            }
             titleTagToMouseover(a);
         }
     });
 
     /* Mouseover should clear if you leave the document window altogether */
     document.body.addEventListener("mouseleave", (ev) => {
         clearTimeout(mouseoverTimer);
         if (mousemoveController) { mousemoveController.abort(); }
         hideMouseoverText(mouseoverContainer);
         canShowNewMouseover = false;
         // let mouseovers show up again upon moving back in to the window
         // but only need the event once
         // use capture: true to force this event to happen
         // before the regular mouseover event
         document.body.addEventListener("mouseover", (evt) => {