d121edc0bd2809f1d6de6185a497e6a288958479 braney Tue May 12 09:18:29 2026 -0700 hgConvert quickLift: skip pre-lifted tracks, append-and-merge hub file, per-track remove UI, refs #37535 In hgConvert / trackHubBuild: - Skip tracks that already came from a quickLift hub (quickLiftUrl / quickLifted setting) so they don't get re-lifted to a new destination. - Append new track stanzas to an existing per-source hub file instead of overwriting it; new stanzas get priorities after the existing max; duplicate track names are skipped. Also avoids re-emitting a parent supertrack that's already in the file. New public quickLiftHubRemoveTrack(cart, sourceDb, trackName) in trackHub.c rewrites the per-source hub file with the named stanza removed plus all descendant stanzas (parent reference cascade, transitive). hgTrackUi: adds a "Remove from QuickLift" link next to "Duplicate track" for any tdb carrying a quickLiftDb setting. The link hits hgTrackUi_op=quickLiftRemove which calls quickLiftHubRemoveTrack, hides the track in the cart, and 302s to hgTracks. The op argument cart var is qlSourceDb (renamed from quickLiftSourceDb to avoid colliding with the quickLift.* prefix used elsewhere; values cloned out of the cart hash before cartRemove so the helper doesn't see freed strings). hgTracks: adds a small "x" icon (printQuickLiftDelIcon) on tracks in a quickLift group, suppressed on the synthetic bigQuickLiftChain track. JS onQuickLiftDelIconClick fires the same hgTrackUi_op endpoint via synchronous XHR and removes every TD whose icon matches the deleted data-track, so the row goes away in both the QuickLift group and the Visible Tracks group. hubConnect cart handling fixes shaken out by the above: - hubConnectRemakeTrackHubVar's cart-var prefix is now "quickLift." (with the trailing dot) instead of "quickLift", so unrelated keys like qlSourceDb no longer get parsed as hubId/db and crash cart loading on every CGI. Also skips entries whose hubStatus lookup returned NULL. - hubConnectStatusListFromCart no longer calls removeQuickListReference when the current db isn't the lift's destination. A side trip to another assembly between two lifts to the same destination was deleting the earlier attachment's cart var; just skip attaching this load and leave the cart alone so the lift survives the round trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index cd10f06316e..20bf397876c 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -4912,30 +4912,53 @@ function onTrackDelIconClick (ev) { /* delete custom track if user clicks its trash icon */ // https://genome.ucsc.edu/cgi-bin/hgCustom?hgsid=1645697744_i0Yp2Di71NytSDdb6r0vUbupIvKO&hgct_do_delete=delete&hgct_del_ct_UserTrack_3545=on var divEl = ev.target.closest("div"); // must use .closest(), as user can click on either the SVG or the DIV space. var trackName = divEl.getAttribute("data-track"); var hgsid = getHgsid(); var url = 'hgCustom?hgsid='+hgsid+'&hgct_do_delete=delete&hgct_del_'+trackName+'=on'; xhttp = new XMLHttpRequest(); // this cannot be asyncronous, as users can click quickly here and the hgCustom calls above cannot run in parallel // since we store custom tracks as a text file, not mysql tables xhttp.open("GET", url, false); xhttp.send(); divEl.closest("td").remove(); } +function onQuickLiftDelIconClick (ev) { + /* Remove this track's stanza from the quickLift hub in trash, and + * remove all rows referencing it from the page (it can appear both in + * its hub group and in the Visible Tracks group). */ + var divEl = ev.target.closest("div"); + var trackName = divEl.getAttribute("data-track"); + var sourceDb = divEl.getAttribute("data-sourcedb"); + var hgsid = getHgsid(); + var url = 'hgTrackUi?hgsid=' + hgsid + + '&hgTrackUi_op=quickLiftRemove' + + '&g=' + encodeURIComponent(trackName) + + '&qlSourceDb=' + encodeURIComponent(sourceDb); + var xhttp = new XMLHttpRequest(); + // synchronous: the hub file is a shared text file, parallel rewrites would race + xhttp.open("GET", url, false); + xhttp.send(); + var selector = 'div.quickLiftDelIcon[data-track="' + trackName + '"]'; + document.querySelectorAll(selector).forEach(function(d) { + var td = d.closest("td"); + if (td) td.remove(); + }); +} + ////////////////////////////////// //// popup (aka modal dialog) //// ////////////////////////////////// var popUp = { trackName: "", trackDescriptionOnly: false, saveAllVars: null, cleanup: function () { // Clean out the popup box on close if ($('#hgTrackUiDialog').html().length > 0 ) { // clear out html after close to prevent problems caused by duplicate html elements $('#hgTrackUiDialog').html(""); @@ -6885,30 +6908,33 @@ }; /////////////// //// READY //// /////////////// $(document).ready(function() { imageV2.moveTiming(); // hg.conf will turn this on 2020-10 - Hiram if (window.mouseOverEnabled) { mouseOver.addListener(); } // custom tracks get little trash icons $("div.trackDeleteIcon").on("click", onTrackDelIconClick ); + // quickLift tracks get a little 'x' icon + $("div.quickLiftDelIcon").on("click", onQuickLiftDelIconClick ); + // on Safari the back button doesn't call the ready function. Reload the page if // the back button was pressed. $(window).on("pageshow", function(event) { if (event.originalEvent.persisted) { window.location.reload() ; } }); // The page may be reached via browser history (back button) // If so, then this code should detect if the image has been changed via js/ajax // and will reload the image if necessary. // NOTE: this is needed for IE but other browsers can detect the dirty page much earlier if (!imageV2.backSupport) { if (imageV2.isDirtyPage()) { // mark as non dirty to avoid infinite loop in chrome.