fc3472e7e9978eb81e3ac14e985bf27e5196ab73
kate
  Fri Jul 24 10:58:01 2020 -0700
Recommended track sets feature. refs #25601

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 3ea5eb8..31e9c32 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -772,30 +772,47 @@
             return postTheForm($(thisForm).attr('name'),cart.addUpdatesToUrl(obj.href));
         }
         return true;
     }
 };
 
 /////////////////////////
 //// cart updating /////
 ///////////////////////
 var cart = {
     // Controls queuing and ultimately updating cart variables vis ajax or submit. Queued vars
     // are held in an object with unique keys preventing duplicate updates and ensuring last update 
     // takes precedence.  WARNING: be careful creating an object with variables on the fly:
     // cart.setVarsObj({track: vis}) is invalid but cart.setVarsObj({'knownGene': vis}) is ok! 
 
+    updateSessionPanel: function()
+    {
+    // change color of text
+    $('span.gbSessionChangeIndicator').addClass('gbSessionChanged');
+
+    // change mouseover on the panel.  A bit fragile here inserting text in the mouseover specified in
+    // hgTracks.js, so depends on match with text there, and should present same message as C code
+    // (Perhaps this could be added as a script tag, so not duplicated)
+    var txt = $('span.gbSessionLabelPanel').attr('title');
+    if (!txt.match(/with changes/)) {
+        $('span.gbSessionLabelPanel').attr('title', txt.replace(
+                                   "track set", 
+                                   "track set, with changes (added or removed tracks) you have requested"));
+        }
+    return true;
+    },
+
     updateQueue: {},
     
     updatesWaiting: function ()
     {   // returns TRUE if updates are waiting.
         return objNotEmpty(cart.updateQueue);
     },
     
     addUpdatesToUrl: function (url)
     {   // adds any outstanding cart updates to the url, then clears the queue
         if (cart.updatesWaiting()) {
             //console.log('cart.addUpdatesToUrl: '+objKeyCount(cart.updateQueue)+' vars');
             var updates = cart.varsToUrlData(); // clears the queue
             if (!url || url.length === 0)
                 return updates;
 
@@ -835,30 +852,31 @@
     setVarsObj: function (varsObj, errFunc, async)
     {   // Set all vars in a var hash, appending any queued updates
         //console.log('cart.setVarsObj: were:'+objKeyCount(cart.updateQueue) + 
         //            ' new:'+objKeyCount(varsObj);
         cart.queueVarsObj(varsObj); // lay ontop of queue, to give new values precedence
         
         // Now ajax update all in queue and clear queue
         if (cart.updatesWaiting()) {
             setVarsFromHash(cart.updateQueue, errFunc, async);
             cart.updateQueue = {};
         }
     },
     
     setVars: function (names, values, errFunc, async)
     {   // ajax updates the cart, and includes any queued updates.
+        cart.updateSessionPanel();      // handles hide from left minibutton
         cart.setVarsObj(arysToObj(names, values), errFunc, async);
     },
 
     queueVarsObj: function (varsObj)
     {   // Add object worth of cart updates to the 'to be updated' queue, so they can be sent to
         // the server later. Note: hash allows overwriting previous updates to the same variable.
         if (typeof varsObj !== 'undefined' && objNotEmpty(varsObj)) {
             //console.log('cart.queueVarsObj: were:'+objKeyCount(cart.updateQueue) + 
             //            ' new:'+objKeyCount(varsObj));
             for (var name in varsObj) {
                 cart.updateQueue[name] = varsObj[name];
                 
                 // Could update in background, however, failing to hit "refresh" is user choice
                 // first in queue, schedule background update
                 if (objKeyCount(cart.updateQueue) === 1) {
@@ -944,30 +962,31 @@
         return false;
     },
     
     initForAjax: function()
     {   // To better support the back-button, it is good to eliminate extraneous form puts
         // Towards that end, we support visBoxes making ajax calls to update cart.
         var sels = $('select.normalText,select.hiddenText');
         $(sels).change(function() {
             var track = $(this).attr('name');
             if ($(this).val() === 'hide') {
                 var rec = hgTracks.trackDb[track];
                 if (rec)
                     rec.visibility = 0;
                 // else Would be nice to hide subtracks as well but that may be overkill
                 $(document.getElementById('tr_' + track)).remove();
+                cart.updateSessionPanel();
                 imageV2.highlightRegion();
                 $(this).attr('class', 'hiddenText');
             } else
                 $(this).attr('class', 'normalText');
             
             cart.addVarsToQueue([track], [$(this).val()]);
             imageV2.markAsDirtyPage();
             return false;
         });
         // Now we can rid the submt of the burden of all those vis boxes
         var form = $('form#TrackForm');
         $(form).submit(function () {
             $('select.normalText,select.hiddenText').attr('disabled',true);
         });
         $(form).attr('method','get');
@@ -2528,31 +2547,31 @@
             cart.setVarsObj({'highlight' : hgTracks.highlight});
             imageV2.highlightRegion();
         } else if (cmd === 'toggleMerge') {
             // toggle both the cart (if the user goes to trackUi)
             // and toggle args[key], if the user doesn't leave hgTracks
             var key = id + ".doMergeItems";
             var updateObj = {};
             if (args[key] === 1) {
                 args[key] = 0;
                 updateObj[key] = 0;
                 cart.setVarsObj(updateObj,null,false);
                 imageV2.requestImgUpdate(id, id + ".doMergeItems=0");
             } else {
                 args[key] = 1;
                 updateObj[key] = 1;
-                cart.setVars(updateObj,null,false);
+                cart.setVarsObj(updateObj,null,false);
                 imageV2.requestImgUpdate(id, id + ".doMergeItems=1");
             }
         } else {   // if ( cmd in 'hide','dense','squish','pack','full','show' )
             // Change visibility settings:
             //
             // First change the select on our form:
             rec = hgTracks.trackDb[id];
             selectUpdated = vis.update(id, cmd);
 
             // Now change the track image
             if (imageV2.enabled && cmd === 'hide') {
                 // Hide local display of this track and update server side cart.
                 // Subtracks controlled by 2 settings so del vis and set sel=0.
                 if (tdbIsSubtrack(rec)) {
                     // Remove subtrack level vis and explicitly uncheck.
@@ -3245,30 +3264,45 @@
         autocompleteCat.init($('#singleAltHaploId'),
                              { baseUrl: 'hgSuggest?db=' + getDb() + '&type=altOrPatch&prefix=',
                                enterSelectsIdentical: true });
         // Make option inputs select their associated radio buttons
         $('input[name="emPadding"]').keyup(function() {
             $('#virtModeType[value="exonMostly"]').attr('checked', true); });
         $('input[name="gmPadding"]').keyup(function() {
             $('#virtModeType[value="geneMostly"]').attr('checked', true); });
         $('#multiRegionsBedInput').keyup(function() {
             $('#virtModeType[value="customUrl"]').attr('checked', true); });
         $('#singleAltHaploId').keyup(function() {
             $('#virtModeType[value="singleAltHaplo"]').attr('checked', true); });
     }
 };
 
+// Show the recommended track sets popup
+function showRecTrackSetsPopup() {
+    // Populate links with position
+    $('a.recTrackSetLink').each(function() {
+        var $this = $(this);
+        var _href = $this.attr("href");
+        $this.attr("href", _href + genomePos.original);
+    });
+    $('#recTrackSetsPopup').dialog({width:'650'});
+}
+
+function removeSessionPanel() {
+    $('#recTrackSetsPanel').remove();
+}
+
 // A function to show the keyboard help dialog box, bound to ? and called from the menu bar
 function showHotkeyHelp() {
     $("#hotkeyHelp").dialog({width:'600'});
 }
 
 // A function to add an entry for the keyboard help dialog box to the menubar 
 // and add text that indicates the shortcuts to many static menubar items as suggested by good old IBM CUA/SAA
 function addKeyboardHelpEntries() {
     var html = '<li><a id="keybShorts" title="List all possible keyboard shortcuts" href="#">Keyboard Shortcuts</a><span class="shortcut">?</span></li>';
     $('#help .last').before(html);
     $("#keybShorts").click( function(){showHotkeyHelp();} );
 
     html = '<span class="shortcut">s s</span>';
     $('#sessionsMenuLink').after(html);
 
@@ -3430,30 +3464,31 @@
         //debugDumpFormCollection("allVars", allVars);
         //debugDumpFormCollection("changedVars", changedVars);
         var newVis = changedVars[trackName];
         // subtracks do not have "hide", thus '[]'
         var hide = (newVis && (newVis === 'hide' || newVis === '[]'));  
         if ( ! normed($('#imgTbl')) ) { // On findTracks or config page
             if (objNotEmpty(changedVars))
                 cart.setVarsObj(changedVars);
         }
         else {  // On image page
             if (hide) {
                 if (objNotEmpty(changedVars))
                     cart.setVarsObj(changedVars);
                 $(document.getElementById('tr_' + trackName)).remove();
                 imageV2.afterImgChange(true);
+                cart.updateSessionPanel();
             } else {
                 // Keep local state in sync if user changed visibility
                 if (newVis) {
                     vis.update(trackName, newVis);
                 }
                 if (objNotEmpty(changedVars)) {
                     var urlData = cart.varsToUrlData(changedVars);
                     if (imageV2.mapIsUpdateable) {
                         imageV2.requestImgUpdate(trackName,urlData,"");
                     } else {
                         window.location = "../cgi-bin/hgTracks?" + urlData + "&hgsid=" + getHgsid();
                     }
                 }
             }
         }
@@ -3790,32 +3825,35 @@
             var oldJsonRec = oldJson.trackDb[id];
             
             if (newJsonRec.visibility === 0)  // hidden 'ruler' is in newJson.trackDb!
                 continue;
             if (newJsonRec.type === "remote")
                 continue;
             if (oldJsonRec &&  oldJsonRec.visibility !== 0 && $('tr#tr_' + id).length === 1) {
                 // New track replacing old:
                 if (!imageV2.updateImgForId(response, id, true, newJsonRec))
                     warn("Couldn't parse out new image for id: " + id);
             } else { //if (!oldJsonRec || oldJsonRec.visibility === 0)
                 // New track seen for the first time
                 if (imageV2.backSupport) {
                     $(imgTbl).append("<tr id='tr_" + id + "' abbr='0'" + // abbr gets filled in
                                         " class='imgOrd trDraggable'></tr>");
-                    if (!imageV2.updateImgForId(response, id, true, newJsonRec))
+                    if (!imageV2.updateImgForId(response, id, true, newJsonRec)) {
                         warn("Couldn't insert new image for id: " + id);
+                    } else {
+                        cart.updateSessionPanel();
+                    }
                 }
             }
         }
         if (imageV2.backSupport) {
             // Removes OLD: those in oldJson but not in newJson
             for (id in oldJson.trackDb) {
                 if ( ! newJson.trackDb[id] )
                     $(document.getElementById('tr_' + id)).remove();
             }
             
             // Need to reorder the rows based upon abbr
             dragReorder.sort($(imgTbl));
         }
     },
     
@@ -3942,30 +3980,32 @@
                 warn("hgTracks object is missing from the response");
         } else {
             if (this.id) {
                 if (newJson.trackDb[this.id]) {
                     var visibility = vis.enumOrder[newJson.trackDb[this.id].visibility];
                     var limitedVis;
                     if (newJson.trackDb[this.id].limitedVis)
                         limitedVis = vis.enumOrder[newJson.trackDb[this.id].limitedVis];
                     if (this.newVisibility && limitedVis && this.newVisibility !== limitedVis)
                         // see redmine 1333#note-9
                         alert("There are too many items to display the track in " +
                                 this.newVisibility + " mode.");
                     var rec = oldJson.trackDb[this.id];
                     rec.limitedVis = newJson.trackDb[this.id].limitedVis;
                     vis.update(this.id, visibility);
+                    if (visibility === "hide")
+                        cart.updateSessionPanel(); // notify when vis change to hide track
                     valid = true;
                 } else {
                     // what got returned from the AJAX request was a different
                     // set of tracks.  Let's do a reload and hope for the best
                     imageV2.fullReload();
                 }
             } else {
                 valid = true;
             }
         }
         if (valid) {
             if (imageV2.enabled
             && this.id
             && this.cmd
             && this.cmd !== 'wholeImage'