4bb9e8caea515342ba98d3871da76cd4ec69916f
chmalee
  Fri May 1 14:10:00 2026 -0700
Initial myVariants implementation: a form on hgTracks where users can enter item details in one of three ways: hgvs/item search, simple bed form, advanced bed form where additional non-bed fields can dynamically created. Allows changing the color of items, writing descriptions, and editing the items after creation. Show overlaps with hardcoded tracks when hgc page is open (not in the hgc dialog). Next commit has implementation of sharing these tracks with other users

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 291449bd6d5..01dd56c17f4 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -12,30 +12,45 @@
 /* Support these formats for range specifiers.  Note the ()'s around chrom,
  * start and end portions for substring retrieval: */
 // \s = whitespace, \w = azAZ0-9_, 
 var canonicalRangeExp = /^[\s]*([\w._#-]+)[\s]*:[\s]*([-0-9,]+)[\s]*[-_][\s]*([0-9,]+)[\s]*$/;
 var gbrowserRangeExp =  /^[\s]*([\w._#-]+)[\s]*:[\s]*([0-9,]+)[\s]*\.\.[\s]*([0-9,]+)[\s]*$/;
 var lengthRangeExp =    /^[\s]*([\w._#-]+)[\s]*:[\s]*([0-9,]+)[\s]*\+[\s]*([0-9,]+)[\s]*$/;
 var bedRangeExp =       /^[\s]*([\w._#-]+)[\s]+([0-9,]+)[\s]+([0-9,]+)[\s]*$/;
 var sqlRangeExp =       /^[\s]*([\w._#-]+)[\s]*\|[\s]*([0-9,]+)[\s]*\|[\s]*([0-9,]+)[\s]*$/;
 var singleBaseExp =     /^[\s]*([\w._#-]+)[\s]*:[\s]*([0-9,]+)[\s]*$/;
 // also allow gnomad variants, ex: 12-1234-A-C
 var gnomadVarExp = "^(([0-9]+)|(X|Y|M|MT))-([0-9]+)-([A-Za-z]+)-([A-Za-z]+)$";
 
 // allow gnomad ranges, ex: 12-1234-11223344
 var gnomadRangeExp = "^(([0-9]+)|(X|Y|M|MT))-([0-9]+)-([0-9]+)$";
 
+function createInfoIcon(text) {
+    /* Create an info icon (i in circle) with tooltip text.
+     * Returns a span element containing the SVG icon.
+     * Uses addMouseover() for consistent tooltip behavior. */
+    var span = document.createElement("span");
+    span.style.marginLeft = "4px";
+    span.innerHTML = "<svg style='height:1.1em; vertical-align:top' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>" +
+        "<circle cx='12' cy='12' r='10' stroke='#1C274C' stroke-width='1.5'/>" +
+        "<path d='M12 17V11' stroke='#1C274C' stroke-width='1.5' stroke-linecap='round'/>" +
+        "<circle cx='1' cy='1' r='1' transform='matrix(1 0 0 -1 11 9)' fill='#1C274C'/>" +
+        "</svg>";
+    addMouseover(span, text);
+    return span;
+}
+
 function copyToClipboard(ev) {
     /* copy a piece of text to clipboard. event.target is some DIV or SVG that is an icon. 
      * The attribute data-target of this element is the ID of the element that contains the text to copy. 
      * The text is either in the attribute data-copy or the innerText.
      * see C function printCopyToClipboardButton(iconId, targetId);
      * */
      
     ev.preventDefault();
 
     var buttonEl = ev.target.closest("button"); // user can click SVG or BUTTON element
 
     var targetId = buttonEl.getAttribute("data-target");
     if (targetId===null)
         targetId = ev.target.parentNode.getAttribute("data-target");
     var textEl = document.getElementById(targetId);
@@ -4364,30 +4379,35 @@
     for (let sel of sels) {
         sel.addEventListener("focus", hideTooltips);
         for (let opt of sel.options) {
             opt.addEventListener("click", hideTooltips);
         }
     }
 
     /* Make the ESC key hide tooltips */
     document.body.addEventListener("keyup", (ev) => {
         if (ev.keyCode === 27) {
             if (mouseoverContainer) {
                 hideMouseoverText(mouseoverContainer);
             }
         }
     });
+
+    /* Make jquery-ui dialogs hide tooltips */
+    $(document).on("dialogopen", (ev) => {
+        hideMouseoverText(mouseoverContainer);
+    });
 }
 
 function parseUrl(url) {
     // turn a url into some of it's components like server, query-string, etc
     let protocol, serverName, pathInfo, queryString, queryArgs = {};
     let temp;
     temp = url.split("?");
     if (temp.length > 1)
         queryString = temp.slice(1).join("?");
     temp = temp[0].split("/");
     protocol = temp[0]; // "https:"
     serverName = temp[2]; // "genome-test.gi.ucsc.edu"
     pathInfo = temp.slice(3).join("/"); // "cgi-bin/hgTracks"
     cgi = pathInfo.startsWith("cgi-bin") ? pathInfo.split('/')[1] : "";
     let i, s = queryString ? queryString.split('&') : []; // Fix for when there is no query string in the URL