e11bc091d6678467768adaa1b2f05c0296ae4352 max Wed Aug 12 08:42:06 2026 -0700 hgTracks: add "Delete Custom Track" to the track right-click menu Custom tracks can now be deleted from the track right-click menu, the same way they are deleted by the trash-can icon in the track list. Both paths call the same deleteCustomTrack() helper, so there is one deletion code path. The menu item is shown only for custom tracks (id starts with "ct_"), reuses the trash SVG inlined from printTrashIcon(), and removes the track from the image in place via hideTracks() rather than reloading the page. refs #38087 diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 6de3da06ce7..df8c9deae29 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -3723,30 +3723,35 @@ } $.ajax({ type: "PUT", async: false, url: "../cgi-bin/hgCollection", data: "cmd=addTrack&track=" + id + "&collection=" + collectionName + "&hgsid=" + getHgsid(), trueSuccess: mySuccess, success: catchErrorOrDispatch, error: errorHandler, }); imageV2.fullReload(); } else if (cmd === "hideOthers") { rightClick.hideOthers(id); + } else if (cmd === "deleteCustomTrack") { + deleteCustomTrack(id); + // remove the track from the image in place (no page reload); hideTracks() + // drops its row, updates hgTracks.trackDb and redraws via afterImgChange(). + rightClick.hideTracks([id]); } else if (cmd === "moveTop") { rightClick.moveTo(id, "top"); } else if (cmd === "moveBottom") { rightClick.moveTo(id, "bottom"); } else if ((cmd === 'sortExp') || (cmd === 'sortSim')) { url = "hgTracks?hgsid=" + getHgsid() + "&" + cmd + "="; rec = hgTracks.trackDb[id]; if (tdbHasParent(rec) && tdbIsLeaf(rec)) url += rec.parentTrack; else { // The button already has the ref var link2 = normed($( 'td#td_btn_'+ rightClick.selectedMenuItem.id ).children('a')); if (link2) url = $(link2).attr('href'); else @@ -4630,30 +4635,53 @@ menu.push(o); } } } menu.push($.contextMenu.separator); o = {}; o[rightClick.makeImgTag("hiddenIcon.png") + " Hide all other tracks "] = { onclick: function(menuItemClicked, menuObject) { rightClick.hit(menuItemClicked, menuObject, "hideOthers"); return true; } }; menu.push(o); + // custom tracks (id starts with "ct_", matching isCustomTrack() in the C code) + // can be deleted here, the same as clicking their trash icon in the track list. + var ctId = rightClick.selectedMenuItem.id; + if (ctId && ctId.startsWith("ct_")) { + o = {}; + // trash-can icon, inlined from printTrashIcon() in hgTracks.c so we + // don't pay an extra http round trip for a tiny PNG icon + var trashSvg = "<svg xmlns='http://www.w3.org/2000/svg' " + + "style='height:16px;vertical-align:middle;' viewBox='0 0 448 512'>" + + "<path d='M135.2 17.7C140.6 6.8 151.7 0 163.8 0H284.2c12.1 0 23.2 6.8 28.6 " + + "17.7L320 32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 " + + "32 32 32h96l7.2-14.3zM32 128H416V448c0 35.3-28.7 64-64 64H96c-35.3 0-64-28.7-64-64V128zm96 " + + "64c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16zm96 " + + "0c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16zm96 " + + "0c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16z'/></svg>"; + o[trashSvg + " Delete Custom Track"] = { + onclick: function(menuItemClicked, menuObject) { + rightClick.hit(menuItemClicked, menuObject, "deleteCustomTrack"); + return true; } + }; + menu.push(o); + } + //o = {}; //o[" Float "] = { //onclick: function(menuItemClicked, menuObject) { //rightClick.hit(menuItemClicked, menuObject, "float"); //return true; } //}; //menu.push(o); o = {}; o[rightClick.makeImgTag("ab_up.gif") + " Move to top "] = { onclick: function(menuItemClicked, menuObject) { rightClick.hit(menuItemClicked, menuObject, "moveTop"); return true; } }; menu.push(o); @@ -5504,42 +5532,48 @@ // A function for the keyboard shortcuts "highlight add/clear/new" function highlightCurrentPosition(mode) { var pos = genomePos.get(); if (mode=="new") dragSelect.highlightThisRegion(pos, false); else if (mode=="add") dragSelect.highlightThisRegion(pos, true); else { hgTracks.highlight = ""; var cartSettings = {'highlight': ""}; cart.setVarsObj(cartSettings); imageV2.drawHighlights(); } } -function onTrackDelIconClick (ev) { - /* delete custom track if user clicks its trash icon */ +function deleteCustomTrack (trackName) { + /* Tell hgCustom to delete the given custom track. Shared by the trash icon + * and the right-click context menu so there is only one deletion code path. */ // 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(); + var 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(); +} + +function onTrackDelIconClick (ev) { + /* delete custom track if user clicks its trash icon */ + 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"); + deleteCustomTrack(trackName); 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);