c35f61249daf6b5f463754b180c3fe74ef42d39d
max
  Tue Oct 15 06:13:50 2024 -0700
when hiding tracks on right-click, hide parents if possible, rather than their children, refs #27890

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 1b57db8..8998d26 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -457,30 +457,77 @@
 // json help routines
 function tdbGetJsonRecord(trackName)  { return hgTracks.trackDb[trackName]; }
 // NOTE: These must jive with tdbKindOfParent() and tdbKindOfChild() in trackDb.h
 function tdbIsFolder(tdb)             { return (tdb.kindOfParent === 1); } 
 function tdbIsComposite(tdb)          { return (tdb.kindOfParent === 2); }
 function tdbIsMultiTrack(tdb)         { return (tdb.kindOfParent === 3); }
 function tdbIsView(tdb)               { return (tdb.kindOfParent === 4); } // Don't expect to use
 function tdbIsContainer(tdb)          { return (tdb.kindOfParent === 2 || tdb.kindOfParent === 3); }
 function tdbIsLeaf(tdb)               { return (tdb.kindOfParent === 0); }
 function tdbIsFolderContent(tdb)      { return (tdb.kindOfChild  === 1); }
 function tdbIsCompositeSubtrack(tdb)  { return (tdb.kindOfChild  === 2); }
 function tdbIsMultiTrackSubtrack(tdb) { return (tdb.kindOfChild  === 3); }
 function tdbIsSubtrack(tdb)           { return (tdb.kindOfChild  === 2 || tdb.kindOfChild === 3); }
 function tdbHasParent(tdb)            { return (tdb.kindOfChild  !== 0 && tdb.parentTrack); }
 
+function tdbFindChildless(trackDb, delTracks) {
+    /* Find parents that have no children left anymore in hgTracks.trackDb if you remove delTracks.
+     * return obj with o.loneParents as array of [parent, array of children] , and o.others as an array of all other tracks*/
+    others = [];
+
+    var familySize = {};
+    var families = {};
+    // sort trackDb into object topParent -> count of children
+    for (var trackName of Object.keys(hgTracks.trackDb)) {
+        var rec = hgTracks.trackDb[trackName];
+        if (rec.topParent===undefined) {
+            //others.push(trackName);
+            continue; // ignore top-level tracks
+        }
+
+        var topParent = rec.topParent;
+        if (!familySize.hasOwnProperty(topParent)) {
+            familySize[topParent] = 0;
+            families[topParent] = [];
+        }
+        familySize[topParent]++;
+        families[topParent].push(trackName);
+    }
+
+    // decrease the parent's count for each track to delete
+    for (var delTrack of delTracks) {
+        var tdbRec = hgTracks.trackDb[delTrack];
+        if (tdbRec.topParent)
+            familySize[tdbRec.topParent]--;
+    }
+
+    // for the parents with a count of 0, create an array of [parentName, children]
+    loneParents = [];
+    for (var parentName of Object.keys(familySize)) {
+        if (familySize[parentName]===0)
+            loneParents.push([parentName, families[parentName]]);
+        else
+            for (var child of families[parentName])
+                others.push(child);
+    }
+
+    o = {};
+    o.loneParents = loneParents;
+    o.others = others;
+    return o;
+}
+
 function aryFind(ary,val)
 {// returns the index of a value on the array or -1;
     for (var ix=0; ix < ary.length; ix++) {
         if (ary[ix] === val) {
             return ix;
         }
     }
     return -1;
 }
 
 function aryRemove(ary,vals)
 { // removes one or more variables that are found in the array
     for (var vIx=0; vIx < vals.length; vIx++) {
         var ix = aryFind(ary,vals[vIx]);
         if (ix !== -1)