f4a41965d1f5c26bcae644bba6777e141d485a95 max Wed Sep 8 01:56:09 2021 -0700 merging highlight-color-sticking into master, refs #28098 Squashed commit of the following: commit f09bd0dcc1c23a91aae3c07610066b28e5177d82 Author: Max Date: Tue Sep 7 03:02:46 2021 -0700 Adding another anonymous function, to make sure that the page load does not crash because one of the .js files is not loaded yet, refs #28090 commit 5934e12f75e610bb8ff699dc17be5743a4b17c37 Author: Max Date: Tue Sep 7 02:36:40 2021 -0700 Fixing a super annoying CSS bug in our dialog box where the buttons moved on hover, and adding a 'save color' button to the highlight dialogbox. Not a perfect solution, but adresses the missing save button problem at least for now, refs #28090 commit 1d6a87e7303297c61637591807b338294cc1dcec Author: Max Date: Thu Sep 2 08:23:00 2021 -0700 updated shift-drag dialog, refs #28090 commit 4e078c875f10d5b23d9540ec8dd3efba7b3af4c9 Author: Max Date: Thu Sep 2 06:35:36 2021 -0700 saving previous picked highlight color to the cart, refs #28090 diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 585daca..0f5a650 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -11,30 +11,32 @@ /* Data passed in from CGI via the hgTracks object: * * string cgiVersion // CGI_VERSION * string chromName // current chromosome * int winStart // genomic start coordinate (0-based, half-open) * int winEnd // genomic end coordinate * int newWinWidth // new width (in bps) if user clicks on the top ruler * boolean revCmplDisp // true if we are in reverse display * int insideX // width of side-bar (in pixels) * int rulerClickHeight // height of ruler (in pixels) - zero if ruler is hidden * boolean inPlaceUpdate // true if in-place-update is turned on * int imgBox* // various drag-scroll values * boolean measureTiming // true if measureTiming is on * Object trackDb // hash of trackDb entries for tracks which are visible on current page + * string highlight // highlight string, in format chrom#start#end#color|chrom2#start2#end2#color2|... + * string prevHlColor // the last highlight color that the user picked */ /* IE11 compatibility - IE doesn't have string startsWith and never will */ if (!String.prototype.startsWith) { String.prototype.startsWith = function(searchString, position) { position = position || 0; return this.indexOf(searchString, position) === position; }; } function initVars() { // There are various entry points, so we call initVars in several places to make sure all is well if (typeof(hgTracks) !== "undefined" && !genomePos.original) { // remember initial position and size so we can restore it if user cancels genomePos.original = genomePos.getOriginalPos(); @@ -959,31 +961,31 @@ }, initForAjax: function() { // To better support the back-button, it is good to eliminate extraneous form puts // Towards that end, we support visBoxes making ajax calls to update cart. var sels = $('select.normalText,select.hiddenText'); $(sels).change(function() { var track = $(this).attr('name'); if ($(this).val() === 'hide') { var rec = hgTracks.trackDb[track]; if (rec) rec.visibility = 0; // else Would be nice to hide subtracks as well but that may be overkill $(document.getElementById('tr_' + track)).remove(); cart.updateSessionPanel(); - imageV2.highlightRegion(); + imageV2.drawHighlights(); $(this).attr('class', 'hiddenText'); } else $(this).attr('class', 'normalText'); cart.setVars([track], [$(this).val()]); imageV2.markAsDirtyPage(); return false; }); // Now we can rid the submt of the burden of all those vis boxes var form = $('form#TrackForm'); $(form).submit(function () { $('select.normalText,select.hiddenText').attr('disabled',true); }); $(form).attr('method','get'); }, @@ -1022,59 +1024,85 @@ selectChange: function (img, selection) { if (selection.x1 !== selection.x2) { if (genomePos.check(img, selection)) { genomePos.update(img, selection, false); jQuery('body').css('cursor', dragSelect.originalCursor); } else { jQuery('body').css('cursor', 'not-allowed'); } } return true; }, findHighlightIdxForPos : function(findPos) { // return the index of the highlight string e.g. hg19.chrom1:123-345#AABBDCC that includes a chrom range findPos - // mostly copied from highlightRegion() + // mostly copied from drawHighlights() var currDb = getDb(); if (hgTracks.highlight) { var hlArray = hgTracks.highlight.split("|"); // support multiple highlight items for (var i = 0; i < hlArray.length; i++) { hlString = hlArray[i]; pos = parsePositionWithDb(hlString); imageV2.undisguiseHighlight(pos); if (!pos) continue; // ignore invalid position strings pos.start--; if (pos.chrom === hgTracks.chromName && pos.db === currDb && pos.start <= findPos.chromStart && pos.end >= findPos.chromEnd) { return i; } } } return null; }, + saveHlColor : function (hlColor) + // save the current hlColor to the object and also the cart variable hlColor and return it. + // hlColor is a 6-character hex string prefixed by # + { + dragSelect.hlColor = hlColor; + cart.setVars(["prevHlColor"], [dragSelect.hlColor]); + hgTracks.prevHlColor = hlColor; // cart.setVars does not update the hgTracks-variables. The cart-variable system is a problem. + return hlColor; + }, + + loadHlColor : function () + // load hlColor from prevHlColor in the cart, or use default color, set and return it + // color is a 6-char hex string prefixed by # + { + if (hgTracks.prevHlColor) + dragSelect.hlColor = hgTracks.prevHlColor; + else + dragSelect.hlColor = dragSelect.hlColorDefault; + return dragSelect.hlColor; + }, + highlightThisRegion: function(newPosition, doAdd, hlColor) // set highlighting newPosition in server-side cart and apply the highlighting in local UI. + // hlColor can be undefined, in which case it defaults to the last used color or the default light blue + // if hlColor is set, it is also saved into the cart. + // if doAdd is true, the highlight is added to the current list. If it is false, all old highlights are deleted. { newPosition.replace("virt:", "multi:"); - var hlColorName = hlColor; // js convention: do not assign to argument variables + var hlColorName = null; if (hlColor==="" || hlColor===null || hlColor===undefined) - hlColorName = dragSelect.hlColor; + hlColorName = dragSelect.loadHlColor(); + else + hlColorName = dragSelect.saveHlColor( hlColor ); var pos = parsePosition(newPosition); var start = pos.start; var end = pos.end; var newHighlight = makeHighlightString(getDb(), pos.chrom, start, end, hlColorName); newHighlight = imageV2.disguiseHighlight(newHighlight); var oldHighlight = hgTracks.highlight; if (oldHighlight===undefined || doAdd===undefined || doAdd===false || oldHighlight==="") { // just set/overwrite the old highlight position, this used to be the default hgTracks.highlight = newHighlight; } else { // add to the end of a |-separated list hgTracks.highlight = oldHighlight+"|"+newHighlight; } @@ -1102,64 +1130,67 @@ if (w.chromName === nonVirtChrom) { nonVirtEnd = Math.max(ce, nonVirtEnd); } else { break; } } } } if (nonVirtChrom !== "") cartSettings.nonVirtHighlight = makeHighlightString(getDb(), nonVirtChrom, nonVirtStart, (nonVirtEnd+1), hlColorName); } else if (hgTracks.windows && hgTracks.virtualSingleChrom) { cartSettings.nonVirtHighlight = hgTracks.highlight; } // TODO if not virt, do we need to erase cart nonVirtHighlight ? cart.setVarsObj(cartSettings); - imageV2.highlightRegion(); + imageV2.drawHighlights(); }, selectionEndDialog: function (newPosition) // Let user choose between zoom-in and highlighting. { newPosition.replace("virt:", "multi:"); // if the user hit Escape just before, do not show this dialo if (dragSelect.startTime===null) return; var dragSelectDialog = $("#dragSelectDialog")[0]; if (!dragSelectDialog) { $("body").append("
" + "

    "+ "
  • Hold Shift+drag to show this dialog" + - "
  • Hold Alt+drag to add a highlight" + + "
  • Hold Alt+drag (Windows) or Option+drag (Mac) to add a highlight" + "
  • Hold Ctrl+drag (Windows) or Cmd+drag (Mac) to zoom" + - "
  • To cancel, press Esc anytime or drag mouse outside image" + - "
  • Highlight the current position with h then m" + + "
  • To cancel, press Esc anytime during the drag" + + "
  • Using the keyboard, highlight the current position with h then m" + "
  • Clear all highlights with View - Clear Highlights or h then c" + + "
  • To merely save the color for the next keyboard or right-click > Highlight operations, click 'Save Color' below" + "

" + - "

Highlight color: " + + "

Highlight color: " + //"" + "  " + "  Reset

" + "" + "Don't show this again and always zoom with shift.
" + "Re-enable via 'View - Configure Browser' (c then f)

"+ "Selected chromosome position: "); dragSelectDialog = $("#dragSelectDialog")[0]; // reset value $('#hlReset').click(function() { - $('#hlColorInput').val(dragSelect.hlColorDefault); - $("#hlColorPicker").spectrum("set", dragSelect.hlColorDefault); + var hlDefault = dragSelect.hlColorDefault; + $('#hlColorInput').val(hlDefault); + $("#hlColorPicker").spectrum("set", hlDefault); + dragSelect.saveHlColor(hlDefault); }); // allow to click checkbox by clicking on the label $('#hlNotShowAgainMsg').click(function() { $('#disableDragHighlight').click();}); // click "add highlight" when enter is pressed in color input box $("#hlColorInput").keyup(function(event){ if(event.keyCode == 13){ $(".ui-dialog-buttonset button:nth-child(3)").click(); } }); // activate the color picker var opt = { hideAfterPaletteSelect : true, color : $('#hlColorInput').val(), showPalette: true, showInput: true, @@ -1201,68 +1232,73 @@ str2 += "\n"; if (!(hgTracks.virtualSingleChrom && (selectedRegions === 1))) { str += str2; } $("#dragSelectPosition").html(str); } else { $("#dragSelectPosition").html(newPosition); } $(dragSelectDialog).dialog({ modal: true, title: "Drag-and-select", closeOnEscape: true, resizable: false, autoOpen: false, revertToOriginalPos: true, - minWidth: 500, + minWidth: 550, buttons: { "Zoom In": function() { // Zoom to selection $(this).dialog("option", "revertToOriginalPos", false); if ($("#disableDragHighlight").attr('checked')) hgTracks.enableHighlightingDialog = false; if (imageV2.inPlaceUpdate) { if (hgTracks.virtualSingleChrom && (newPosition.search("multi:")===0)) { newPosition = genomePos.disguisePosition(newPosition); // DISGUISE } var params = "position=" + newPosition; if (!hgTracks.enableHighlightingDialog) params += "&enableHighlightingDialog=0"; imageV2.navigateInPlace(params, null, true); } else { $('body').css('cursor', 'wait'); if (!hgTracks.enableHighlightingDialog) cart.setVarsObj({'enableHighlightingDialog': 0 },null,false); // async=false document.TrackHeaderForm.submit(); } $(this).dialog("close"); }, "Single Highlight": function() { // Clear old highlight and Highlight selection $(imageV2.imgTbl).imgAreaSelect({hide:true}); if ($("#disableDragHighlight").attr('checked')) hgTracks.enableHighlightingDialog = false; - dragSelect.hlColor = $("#hlColorInput").val(); - dragSelect.highlightThisRegion(newPosition, false); + var hlColor = $("#hlColorInput").val(); + dragSelect.highlightThisRegion(newPosition, false, hlColor); $(this).dialog("close"); }, "Add Highlight": function() { // Highlight selection if ($("#disableDragHighlight").attr('checked')) hgTracks.enableHighlightingDialog = false; - dragSelect.hlColor = $("#hlColorInput").val(); - dragSelect.highlightThisRegion(newPosition, true); + var hlColor = $("#hlColorInput").val(); + dragSelect.highlightThisRegion(newPosition, true, hlColor); + $(this).dialog("close"); + }, + "Save Color": function() { + var hlColor = $("#hlColorInput").val(); + dragSelect.saveHlColor( hlColor ); $(this).dialog("close"); }, "Cancel": function() { $(this).dialog("close"); } }, open: function () { // Make zoom the focus/default action $(this).parents('.ui-dialog-buttonpane button:eq(0)').focus(); }, close: function() { // All exits to dialog should go through this $(imageV2.imgTbl).imgAreaSelect({hide:true}); if ($(this).dialog("option", "revertToOriginalPos")) @@ -1372,31 +1408,31 @@ clickClipHeight: heights })); // remove any ongoing drag-selects when the esc key is pressed anywhere for this document // This allows to abort zooming / highlighting $(document).keyup(function(e){ if(e.keyCode === 27) { $(imageV2.imgTbl).imgAreaSelect({hide:true}); dragSelect.escPressed = true; } }); // hide and redraw all current highlights when the browser window is resized $(window).resize(function() { $(imageV2.imgTbl).imgAreaSelect({hide:true}); - imageV2.highlightRegion(); + imageV2.drawHighlights(); }); } } }; ///////////////////////////////////// //// Chrom Drag/Zoom/Expand code //// ///////////////////////////////////// jQuery.fn.chromDrag = function(){ this.each(function(){ // Plan: // mouseDown: determine where in map: convert to img location: pxDown // mouseMove: flag drag // mouseUp: if no drag, then create href centered on bpDown loc with current span @@ -1869,31 +1905,31 @@ // Outside image? Then abandon. var curY = e.pageY; var imgTbl = $('#imgTbl'); var north = $(imgTbl).offset().top; var south = north + $(imgTbl).height(); if (curY < north || curY > south) { atEdge = false; beyondImage = false; if (savedPosition) genomePos.set(savedPosition); var oldPos = prevX.toString() + "px"; $(".panImg").css( {'left': oldPos }); $('.tdData').css( {'backgroundPosition': oldPos } ); if (highlightAreas) - imageV2.highlightRegion(); + imageV2.drawHighlights(); return true; } // Do we need to fetch anything? if (beyondImage) { if (imageV2.inPlaceUpdate) { var pos = parsePosition(genomePos.get()); imageV2.navigateInPlace("position=" + encodeURIComponent(pos.chrom + ":" + pos.start + "-" + pos.end), null, true); } else { document.TrackHeaderForm.submit(); } return true; // Make sure the setTimeout below is not called. } @@ -2253,40 +2289,40 @@ url += "&l=" + (chromStart - 1) + "&r=" + chromEnd; url += "&db=" + getDb() + "&hgsid=" + getHgsid(); if ( ! window.open(url) ) { rightClick.windowOpenFailedMsg(); } } else if (cmd === 'highlightItem') { if (hgTracks.windows && !hgTracks.virtualSingleChrom) { // orig way only worked if the entire item was visible in the windows. //var result = genomePos.chromToVirtChrom(chrom, parseInt(chromStart-1), parseInt(chromEnd)); var result = genomePos.convertChromPosToVirtCoords(chrom, parseInt(chromStart-1), parseInt(chromEnd)); if (result.chromStart != -1) { var newPos2 = hgTracks.chromName+":"+(result.chromStart+1)+"-"+result.chromEnd; - dragSelect.highlightThisRegion(newPos2, true, dragSelect.hlColorDefault); + dragSelect.highlightThisRegion(newPos2, true); } } else { var newChrom = hgTracks.chromName; if (hgTracks.windows && hgTracks.virtualSingleChrom) { newChrom = hgTracks.windows[0].chromName; } var newPos3 = newChrom+":"+(parseInt(chromStart))+"-"+parseInt(chromEnd); - dragSelect.highlightThisRegion(newPos3, true, dragSelect.hlColorDefault); + dragSelect.highlightThisRegion(newPos3, true); } } else { var newPosition = genomePos.setByCoordinates(chrom, chromStart, chromEnd); var reg = new RegExp("hgg_gene=([^&]+)"); var b = reg.exec(href); var name; // pull item name out of the url so we can set hgFind.matches (redmine 3062) if (b && b[1]) { name = b[1]; } else { reg = new RegExp("[&?]i=([^&]+)"); b = reg.exec(href); if (b && b[1]) { name = b[1]; } @@ -2533,31 +2569,31 @@ imageV2.navigateInPlace(params, null, true); } else { genomePos.setByCoordinates(newPos.chrom, newPos.start, newPos.end); jQuery('body').css('cursor', 'wait'); document.TrackHeaderForm.submit(); } } } } else if (cmd === 'removeHighlight') { var highlights = hgTracks.highlight.split("|"); highlights.splice(rightClick.clickedHighlightIdx, 1); // splice = remove element from array hgTracks.highlight = highlights.join("|"); cart.setVarsObj({'highlight' : hgTracks.highlight}); - imageV2.highlightRegion(); + imageV2.drawHighlights(); } else if (cmd === 'toggleMerge') { // toggle both the cart (if the user goes to trackUi) // and toggle args[key], if the user doesn't leave hgTracks var key = id + ".doMergeItems"; var updateObj = {}; if (args[key] === 1) { args[key] = 0; updateObj[key] = 0; cart.setVarsObj(updateObj,null,false); imageV2.requestImgUpdate(id, id + ".doMergeItems=0"); } else { args[key] = 1; updateObj[key] = 1; cart.setVarsObj(updateObj,null,false); imageV2.requestImgUpdate(id, id + ".doMergeItems=1"); @@ -3412,31 +3448,31 @@ newPos = genomePos.disguisePosition(newPosition); // DISGUISE? imageV2.navigateInPlace("position="+newPos, null, true); } // 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.highlightRegion(); + imageV2.drawHighlights(); } } ////////////////////////////////// //// 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 ) { @@ -3772,60 +3808,60 @@ } $("#goButton").click(); }, function (position) { genomePos.set(position, getSizeFromCoordinates(position)); } ); } } }, afterImgChange: function (dirty) { // Standard things to do when manipulations change image without ajax update. dragReorder.init(); dragSelect.load(false); - imageV2.highlightRegion(); + imageV2.drawHighlights(); if (dirty) imageV2.markAsDirtyPage(); }, afterReload: function (id) { // Reload various UI widgets after updating imgTbl map. dragReorder.init(); dragSelect.load(false); // Do NOT reload context menu (otherwise we get the "context menu sticks" problem). // rightClick.load($('#tr_' + id)); if (imageV2.imgTbl.tableDnDUpdate) imageV2.imgTbl.tableDnDUpdate(); rightClick.reloadFloatingItem(); // Turn on drag scrolling. if (hgTracks.imgBoxPortal) { $("div.scroller").panImages(); } if (imageV2.backSupport) { if (id) { // The remainder is only needed for full reload imageV2.markAsDirtyPage(); // vis of cfg change - imageV2.highlightRegion(); + imageV2.drawHighlights(); return; } } imageV2.loadRemoteTracks(); makeItemsByDrag.load(); imageV2.loadSuggestBox(); - imageV2.highlightRegion(); + imageV2.drawHighlights(); if (imageV2.backSupport) { imageV2.setInHistory(false); // Set this new position into History stack } else { imageV2.markAsDirtyPage(); } }, updateImgForId: function (html, id, fullImageReload, newJsonRec) { // update row in imgTbl for given id. // return true if we successfully pull slice for id and update it in imgTrack. var newTr = $(html).find("tr[id='tr_" + id + "']"); if (newTr.length > 0) { var tr = $(document.getElementById("tr_" + id)); if (tr.length > 0) { @@ -4308,35 +4344,40 @@ // undisguise highlight pos { // UN-DISGUISE if (hgTracks.virtualSingleChrom && (pos.chrom.search("multi") !== 0)) { var position = pos.chrom+":"+pos.start+"-"+pos.end; var newPosition = genomePos.undisguisePosition(position); var newPos = parsePosition(newPosition); if (newPos) { pos.chrom = newPos.chrom; pos.start = newPos.start; pos.end = newPos.end; } } }, - highlightRegion: function() + drawHighlights: function() // highlight vertical region in imgTbl based on hgTracks.highlight (#709). + // For PDF/hgRenderTracks output, the highlights are drawn by hgTracks.c:drawHighlights() { var pos; var hexColor = dragSelect.hlColorDefault; + // if possible, re-use the color that the user picked last time + if (hgTracks.prevHlColor) + hexColor = hgTracks.prevHlColor; + $('.highlightItem').remove(); if (hgTracks.highlight) { var hlArray = hgTracks.highlight.split("|"); // support multiple highlight items for (var i = 0; i < hlArray.length; i++) { hlString = hlArray[i]; pos = parsePositionWithDb(hlString); // UN-DISGUISE imageV2.undisguiseHighlight(pos); if (pos) { pos.start--; // make start 0-based to match hgTracks.winStart if (pos.color) hexColor = pos.color; } if (pos && pos.chrom === hgTracks.chromName && pos.db === getDb() @@ -5108,38 +5149,38 @@ $("div.scroller").panImages(); } // Retrieve tracks via AJAX that may take too long to draw initialliy (i.e. a remote bigWig) var retrievables = $('#imgTbl').find("tr.mustRetrieve"); if ($(retrievables).length > 0) { $(retrievables).each( function (i) { var trackName = $(this).attr('id').substring(3); imageV2.requestImgUpdate(trackName,"",""); }); } imageV2.loadRemoteTracks(); makeItemsByDrag.load(); // Any highlighted region must be shown and warnBox must play nice with it. - imageV2.highlightRegion(); + imageV2.drawHighlights(); // When warnBox is dismissed, any image highlight needs to be redrawn. - $('#warnOK').click(function (e) { imageV2.highlightRegion();}); + $('#warnOK').click(function (e) { imageV2.drawHighlights();}); // Also extend the function that shows the warn box so that it too redraws the highlight. showWarnBox = (function (oldShowWarnBox) { function newShowWarnBox() { oldShowWarnBox.apply(); - imageV2.highlightRegion(); + imageV2.drawHighlights(); } return newShowWarnBox; })(showWarnBox); } // Drag select in chromIdeogram if ($('img#chrom').length === 1) { if ($('area.cytoBand').length >= 1) { $('img#chrom').chromDrag(); } } // Track search uses tabs trackSearch.init();