4f03efa12fa7a52cad6b78f24d295ff5d80405c0 chmalee Thu Apr 23 12:14:00 2026 -0700 Try to make it more obvious that clicking the 'view' button next to a track file in hubspace connects the whole hub. Add a banner above the table indicating this and with a link that connects the entire hub, refs Max/Baihe email diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js index d7fae2f2bcc..143b8b3f6be 100644 --- src/hg/js/hgMyData.js +++ src/hg/js/hgMyData.js @@ -1011,30 +1011,56 @@ // UCSC db - hgTracks needs 'genome=' (resolves via the hub) // rather than 'db=' (looks up a native assembly). let dbParam = hubType === "assemblyHub" ? "genome" : "db"; let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid() + "&" + dbParam + "=" + genome + "&hubUrl=" + encodeURIComponent(hubUrl) + "&" + trackHubFixName(fname) + "=pack"; window.location.assign(url); return false; } } } function trackHubFixName(trackName) { // replace everything but alphanumeric and underscore with underscore return encodeURIComponent(trackName.replaceAll(fileNameFixRegex, "_")); } + function viewHubInGenomeBrowser(hubName) { + // connect the whole hub in hgTracks, without pack'ing any specific track + if (typeof uiState.userUrl === "undefined" || uiState.userUrl.length === 0) { + return; + } + let dirRow = uiState.filesHash[hubName]; + if (!dirRow) return; + let hubUrl = uiState.userUrl + cgiEncode(hubTxtPathForHub(hubName)); + let dbParam = dirRow.hubType === "assemblyHub" ? "genome" : "db"; + let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid() + "&" + dbParam + "=" + dirRow.genome + "&hubUrl=" + encodeURIComponent(hubUrl); + window.location.assign(url); + } + + function showHubBanner(hubName) { + let banner = document.getElementById("hubBanner"); + let nameSpan = document.getElementById("hubBannerName"); + if (!banner || !nameSpan) return; + nameSpan.textContent = hubName; + banner.style.display = ""; + } + + function hideHubBanner() { + let banner = document.getElementById("hubBanner"); + if (banner) banner.style.display = "none"; + } + // helper object so we don't need to use an AbortController to update // the data this function is using let selectedData = {}; // track which items the user directly selected (vs children of selected directories) let directlySelected = {}; function viewAllInGenomeBrowser(ev) { // redirect to hgTracks with these tracks/hubs open let data = selectedData; if (typeof uiState.userUrl !== "undefined" && uiState.userUrl.length > 0) { let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid(); let genome; // may be multiple genomes in list, just redirect to the first one // TODO: this should probably raise an alert to click through let hubsAdded = {}; _.forEach(data, (d) => { if (!genome) { @@ -1214,56 +1240,58 @@ // clear any fixed searches so we can apply a new one let currSearches = table.search.fixed().toArray(); currSearches.forEach((name) => table.search.fixed(name, null)); } function dataTableShowTopLevel(table) { // show all the "root" files, which are files (probably mostly directories) // with no parentDir clearSearch(table); // deselect any selected rows like Finder et al when moving into/upto a directory table.rows({selected: true}).deselect(); table.search.fixed("showRoot", function(searchStr, rowData, rowIx) { return !rowData.parentDir; }); uiState.currentHub = ""; + hideHubBanner(); } function dataTableShowDir(table, dirName, dirFullPath) { // show the directory and all immediate children of the directory clearSearch(table); // deselect any selected rows like Finder et al when moving into/upto a directory table.rows({selected: true}).deselect(); table.draw(); // NOTE that the below does not actually render until the next table.draw() call table.search.fixed("oneHub", function(searchStr, rowData, rowIx) { // calculate the fullPath of this rows parentDir in case the dirName passed // to this function has the same name as a parentDir further up in the // listing. For example, consider a test/test/tmp.txt layout, where "test" // is the parentDir of tmp.txt and the test subdirectory let parentDirFull = rowData.fullPath.split("/").slice(0,-1).join("/"); if (rowData.parentDir === dirName && parentDirFull === dirFullPath) { return true; } else if (rowData.fullPath === dirFullPath) { // also return the directory itself return true; } else { return false; } }); uiState.currentHub = dirName; dataTableCreateBreadcrumb(table, dirName, dirFullPath); + showHubBanner(dirName); } // when we move into a new directory, we remove the row from the table // and add it's html into the header, keep the row object around so // we can add it back in later let oldRowData = null; function dataTableCustomOrder(table, dirData) { // figure out the order the rows of the table should be in // if dirData is null, sort on uploadTime first // if dirData exists, that is the first row, followed by everything else // in uploadTime order if (!dirData) { // make sure the old row can show up again in the table let thead = document.querySelector(".dt-scroll-headInner > table:nth-child(1) > thead:nth-child(1)"); if (thead.childNodes.length > 1) { @@ -1715,30 +1743,36 @@ } return container; }); let table = new DataTable("#filesTable", tableInitOptions); if (uiState.isLoggedIn) { table.buttons(".uploadButton").enable(); document.getElementById("rootBreadcrumb").addEventListener("click", function(e) { dataTableShowTopLevel(table); dataTableCustomOrder(table); dataTableEmptyBreadcrumb(table); table.draw(); }); } else { table.buttons(".uploadButton").disable(); } + let hubBannerBtn = document.getElementById("hubBannerViewBtn"); + if (hubBannerBtn) { + hubBannerBtn.addEventListener("click", function(e) { + viewHubInGenomeBrowser(uiState.currentHub); + }); + } table.on("select", function(e, dt, type, indexes) { indexes.forEach(function(i) { doRowSelect(e.type, dt, i); }); }); table.on("deselect", function(e, dt, type, indexes) { indexes.forEach(function(i) { doRowSelect(e.type, dt, i); }); }); table.on("click", function(e) { let copyIcon = e.target.closest ? e.target.closest(".copyLinkIcon") : null; if (copyIcon) { e.stopPropagation(); e.preventDefault();