151f0489b9e4fa73bdc22226a7a6354a450fecb2
chmalee
  Wed Feb 4 11:28:20 2026 -0800
Hide species tree by default but put up a link to show it again. Don't put connected assembly hubs into the species tree, instead into their own card section like the recent genomes list, below the recents. refs #34078

diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js
index a9d15f474df..3889fc2d98f 100644
--- src/hg/js/hgGateway.js
+++ src/hg/js/hgGateway.js
@@ -11,30 +11,31 @@
 // speciesTree: module that exports draw() function for drawing a phylogenetic tree
 // (and list of hubs above the tree, if any) in a pre-existing SVG element -- see
 // hg/hgGateway/hgGateway.html.
 
 // rainbow: module that exports draw() function and colors.  draw() adds stripes using
 // a spectrum of colors that are associated to species groups.  The hgGateway view code
 // uses coordinates of stripes within the tree image to create a corresponding "rainbow"
 // slider bar to the left of the phylogenetic tree container.
 
 // hgGateway: module of mostly view/controller code (model state comes directly from server).
 
 // Globals (pragma for jshint):
 /* globals dbDbTree, activeGenomes, surveyLink, surveyLabel, surveyLabelImage, cart */
 /* globals autoCompleteCat */
 /* globals calculateHgTracksWidth */ // function is defined in utils.js
+/* jshint esnext: true */
 
 window.hgsid = '';
 window.activeGenomes = {};
 window.surveyLink=null;
 window.surveyLabel=null;
 window.surveyLabelImage=null;
 
 function setCopyLinks() {
   // add onclick to class 'copyLink' buttons, there could be more than one.
   addOnClick = function(){copyToClipboard(event);};
   copySpan = document.getElementsByClassName('copyLinkSpan');
   for (i = 0; i < copySpan.length; i++) {
     dataTarget = copySpan[i].getAttribute('data-target');
     hostName = window.location.hostname;
     targetSpan = document.getElementById(dataTarget);
@@ -1070,48 +1071,115 @@
                 }
             }
         }
         return hasActiveLeaf;
     }
 
     function drawSpeciesPicker(dbDbTree) {
         // If dbDbTree is nonempty and SVG is supported, draw the tree; if SVG is not supported,
         // use the space to suggest that the user install a better browser.
         // If dbDbTree doesn't exist, leave the "Represented Species" section hidden.
         var svg, spTree, stripeTops;
         if (dbDbTree) {
             if (document.createElementNS) {
                 // Draw the phylogenetic tree and do layout adjustments
                 svg = document.getElementById('speciesTree');
-                spTree = speciesTree.draw(svg, dbDbTree, uiState.hubs,
+                spTree = speciesTree.draw(svg, dbDbTree, null,
                                           { hgHubConnectUrl: 'hgHubConnect?hgsid=' + window.hgsid,
                                             containerWidth: $('#speciesPicker').width()
                                             });
                 setSpeciesPickerSizes(spTree.width, spTree.height);
                 stripeTops = rainbow.draw(svg, dbDbTree,
                                           spTree.yTree, spTree.height, spTree.leafTops);
             } else {
                 $('#speciesTreeContainer').html(getBetterBrowserMessage);
             }
+            let showSpeciesTree = document.getElementById("speciesTreeLink");
+            showSpeciesTree.addEventListener("click", function() {
+                let speciesTreeSection = document.getElementById("speciesGraphic");
+                if (!$(speciesTreeSection).is(":visible")) {
                     $('#representedSpeciesTitle').show();
                     $('#speciesGraphic').show();
                     if (dbDbTree && document.createElementNS) {
                         // These need to be done after things are visible because heights are 0 when hidden.
                         highlightLabelForDb(uiState.db, uiState.taxId);
                         initRainbowSlider(spTree.height, rainbow.colors, stripeTops);
                     }
+                    showSpeciesTree.textContent = "Hide species tree";
+                } else {
+                    $('#representedSpeciesTitle').hide();
+                    $('#speciesGraphic').hide();
+                    showSpeciesTree.textContent = "Show species tree";
+                }
+            });
+        }
+    }
+
+    function addHubsToList(hubList) {
+        // Render connected hubs into the connected hubs section
+        // Only show the section if there are non-curated hubs to display
+        var $title = $('#connectedHubsTitle');
+        var $section = $('#connectedHubsSection');
+        var $panel = $('#connectedHubsList');
+
+        if (!hubList || hubList.length === 0) {
+            // No hubs, hide the section
+            $title.hide();
+            $section.hide();
+            return;
+        }
+
+        // Filter out curated assembly hubs (those in /gbdb but not /gbdb/genark)
+        // and convert hub objects to card-compatible format
+        var displayHubs = [];
+        hubList.forEach(function(hub) {
+            // Skip curated assembly hubs (same logic as drawHubs)
+            if (hub.hubUrl.startsWith("/gbdb") && !hub.hubUrl.startsWith("/gbdb/genark")) {
+                return;
             }
+            // Convert hub object to card-compatible format
+            var hubItem = {
+                label: hub.shortLabel + ' (' + hub.assemblyCount + ' assembl' +
+                       (hub.assemblyCount === 1 ? 'y' : 'ies') + ')',
+                genome: hub.defaultDb,
+                category: 'Assembly Hub',
+                hubUrl: hub.hubUrl,
+                taxId: hub.taxId,
+                db: hub.defaultDb,
+                hubName: hub.name,
+                longLabel: hub.longLabel
+            };
+            displayHubs.push(hubItem);
+        });
+
+        if (displayHubs.length === 0) {
+            // No non-curated hubs, hide the section
+            $title.hide();
+            $section.hide();
+            return;
+        }
+
+        // Show the section and render hub cards
+        $title.show();
+        $section.show();
+
+        // Custom click handler for hub cards
+        var onHubClick = function(item) {
+            setHubDb(item.hubUrl, item.taxId, item.db, item.hubName, null, false);
+        };
+
+        renderGenomeCards(displayHubs, $panel, null, onHubClick);
     }
 
     function addCommasToPosition(pos) {
         // Return seqName:start-end pos with commas inserted in start and end as necessary.
         var posComma = pos;
         var fourDigits = /(^.*:.*[0-9])([0-9]{3}\b.*)/;
         var matches = fourDigits.exec(posComma);
         while (matches) {
             posComma = matches[1] + ',' + matches[2];
             matches = fourDigits.exec(posComma);
         }
         return posComma;
     }
 
     function onSelectGene(item) {
@@ -1371,30 +1439,31 @@
 
     function updateStateAndPage(jsonData) {
         // Update uiState with new values and update the page.
         var hubsChanged = !_.isEqual(jsonData.hubs, uiState.hubs);
         // In rare cases, there can be a genome (e.g. Baboon) with multiple species/taxIds
         // (e.g. Papio anubis for papAnu1 vs. Papio hamadryas for papHam1).  Changing the
         // db can result in changing the taxId too.  In that case, update the highlighted
         // species in the tree image.
         if (jsonData.taxId !== uiState.taxId) {
             highlightLabel('textEl_' + jsonData.taxId, false);
         }
         _.assign(uiState, jsonData);
         updateFindPositionSection(uiState);
         if (hubsChanged) {
             drawSpeciesPicker(prunedDbDbTree);
+            addHubsToList(uiState.hubs);
         }
     }
 
     function handleRefreshState(jsonData) {
         if (checkJsonData(jsonData, 'handleRefreshState')) {
             updateStateAndPage(jsonData);
         }
     }
 
     function isGenomeAtFrontOfRecents(db) {
         // Check if a genome with this db is already at the front of the recents list.
         // This is used to detect if autocompleteCat.js just added it (to avoid double-adding).
         // We compare without hub prefixes since autocompleteCat saves without prefix but
         // server responses may include prefixes like "hub_123_GCA_xxx".
         var stored = window.localStorage.getItem("recentGenomes");
@@ -1731,88 +1800,95 @@
             "Interactive Tutorials</a>";
         $("#help > ul")[0].appendChild(tutorialLinks);
         $("#hgGatewayHelpTutorialLinks").on("click", function () {
             // Check to see if the tutorial popup has been generated already
             var tutorialPopupExists = document.getElementById ("tutorialContainer");
             if (!tutorialPopupExists) {
                 // Create the tutorial popup if it doesn't exist
                 createTutorialPopup();
             } else {
                 //otherwise use jquery-ui to open the popup
                 $("#tutorialContainer").dialog("open");
             }
         });
     }
 
-    // Recent Genomes Panel functions (Option C layout)
+    // Genome Cards Panel functions (shared by recent genomes and connected hubs)
 
-    function renderRecentGenomesPanel(genomes) {
-        // Render recent genomes as vertical scrollable cards
-        var $panel = $('#recentGenomesList');
+    function renderGenomeCards(items, $panel, emptyMessage, onCardClick) {
+        // Render genome/hub items as vertical scrollable cards
+        // items: array of genome or hub objects
+        // $panel: jQuery element to render into
+        // emptyMessage: message to show when no items
+        // onCardClick: optional callback for card clicks (defaults to setDbFromAutocomplete)
         $panel.empty();
 
-        if (!genomes || genomes.length === 0) {
-            $panel.html('<div class="recentGenomesEmpty">Search for a genome above, ' +
-                        'or click a popular species icon</div>');
+        if (!items || items.length === 0) {
+            if (emptyMessage) {
+                $panel.html('<div class="recentGenomesEmpty">' + emptyMessage + '</div>');
+            }
             return;
         }
 
-        // Render each genome as a card (vertical layout)
-        genomes.forEach(function(item) {
+        var clickHandler = onCardClick || setDbFromAutocomplete;
+
+        // Render each item as a card (vertical layout)
+        items.forEach(function(item) {
             var $card = $('<div class="recentGenomeCard"></div>');
-            var label = item.label || item.value || item.genome || item.commonName;
+            var label = item.label || item.shortLabel || item.value || item.genome || item.commonName;
             var genome = item.genome || item.db || '';
 
             $card.append('<div class="recentGenomeLabel">' + escapeHtml(label) + '</div>');
             if (genome && label.indexOf(genome) < 0) {
                 $card.append('<div class="recentGenomeDb">' + escapeHtml(genome) + '</div>');
             }
 
             // Add category as small label
             if (item.category) {
                 var shortCategory = item.category;
                 if (shortCategory.indexOf('UCSC Genome Browser') >= 0) {
                     shortCategory = 'UCSC';
                 } else if (shortCategory.indexOf('GenArk') >= 0) {
                     shortCategory = 'GenArk';
                 } else if (shortCategory.indexOf('Assembly Hub') >= 0) {
                     shortCategory = 'Hub';
                 }
                 $card.append('<div class="recentGenomeCategory">' + escapeHtml(shortCategory) + '</div>');
             }
 
             // Store item data for click handler
             $card.data('item', item);
             $card.on('click', function() {
                 var clickedItem = $(this).data('item');
-                setDbFromAutocomplete(clickedItem);
-                // Highlight selected card
-                $('.recentGenomeCard').removeClass('selected');
+                clickHandler(clickedItem);
+                // Highlight selected card in this panel
+                $panel.find('.recentGenomeCard').removeClass('selected');
                 $(this).addClass('selected');
             });
 
             $panel.append($card);
         });
     }
 
     function displayRecentGenomesInPanel() {
         // Display recent genomes in the panel on page load and after genome selection
         var recentGenomes = getRecentGenomes();
         // Show the section (hidden by default in HTML)
         $('#recentGenomesTitle').show();
         $('#recentGenomesSection').show();
-        renderRecentGenomesPanel(recentGenomes);
+        var emptyMessage = 'Search for a genome above, or click a popular species icon';
+        renderGenomeCards(recentGenomes, $('#recentGenomesList'), emptyMessage);
     }
 
     function escapeHtml(text) {
         // Simple HTML escape for display
         if (!text) return '';
         return String(text)
             .replace(/&/g, '&amp;')
             .replace(/</g, '&lt;')
             .replace(/>/g, '&gt;')
             .replace(/"/g, '&quot;');
     }
 
     function init() {
         // Boot up the page; initialize elements and install event handlers.
         var searchObj = {};