1bafe5a5569d58aa2446c60ef2a42f531d47698f jrobinso Sun Sep 21 21:56:30 2025 -0700 igv dialog position fixes. Misc minor cleanup diff --git src/hg/js/igvFileHelper.js src/hg/js/igvFileHelper.js index a2421d09671..658d8b1c95d 100644 --- src/hg/js/igvFileHelper.js +++ src/hg/js/igvFileHelper.js @@ -1,30 +1,30 @@ // Helper functions for using igv.js with local files in the UCSC genome browser // // The UCSC browser does not use modules, so wrap code in a self-executing function to limit // scope of variables to this file. (function () { const indexExtensions = new Set(['bai', 'csi', 'tbi', 'idx', 'crai']); const requireIndex = new Set(['bam', 'cram']); // File scope variables const IGV_STORAGE_KEY = "igvSession"; let filePicker = null; let isDragging = false; let sessionAutoSaveTimer = null; - window.igvBrowser = undefined; // The gloal igv.js browser object, TODO -- perhaps this should be attached to "igv" + let igvBrowser = null; // Create a BroadcastChannel for communication between the UCSC browser page and the file picker page. const channel = new BroadcastChannel('igv_file_channel'); // Message types for communication between browser page and file picker page const MSG = { SELECTED_FILES: 'selectedFiles', RESTORE_FILES: 'restoreFiles', REMOVED_TRACK: 'removedTrack', LOAD_URL: 'loadURL', FILE_PICKER_READY: 'filePickerReady', PING: 'ping', PONG: 'pong' }; @@ -62,31 +62,32 @@ throw new Error(`Connection to file ${this.name} is not available. Please re-select the file` + ' in the IGV File Manager window.'); } } } /** * Initialize igv.js, creating an igvBrowser if a session is found in local storage. The existence of a * global "igvBrowser" variable indicates igv.js has already been initialized. * * @returns {Promise} */ async function initIgvUcsc() { - if (window.igvBrowser) { + if (igvBrowser) { + // This can happend because initVars(), which calls initIgvUcsc(), is called from multiple places //console.log("igvBrowser already exists"); return; } // Retrieve the igv session for this genome, if any, from local storage. // TODO -- in the future this might come from the UCSC session (cart) const db = getDb(); let sessionString = getSessionStorage()[db]; if (sessionString) { const igvSession = JSON.parse(igv.uncompressSession(`blob:${sessionString}`)); // Reconnect any file-based tracks to the actual File objects. if (igvSession.tracks) { @@ -343,35 +344,38 @@ if (sessionAutoSaveTimer !== null) { clearInterval(sessionAutoSaveTimer); sessionAutoSaveTimer = null; } } window.addEventListener("DOMContentLoaded", async () => { // The "Add IGV track" button handler. The button opens the file picker window, unless // it is already open in which case it brings that window to the front. Tracks are added // from the filePicker page by selecting track files. document.getElementById('hgtIgv').addEventListener('click', async function (e) { e.preventDefault(); // our if (filePicker && !filePicker.closed) { + showDialog("To add tracks please use the IGV File Manager window."); filePicker.focus(); return; - } else { + } + + else { // A file picker may be open from a previous session. First ping it to see if it is still there, - // if it responds the user should be alerted from that window, no need to open a new window. + // if it responds the user should be alerted, if needed, from a failed track load. const responded = await pingFilePicker(); if (!responded) { filePicker = openFilePicker(); } } }); initializeDialog(); }); // Initialize a jQuery UI dialog used for user messages. function initializeDialog() { // Inject a hidden dive for an alert dialog. We use this to report errors. @@ -606,33 +610,33 @@ * a track in the UCSC browser context. This function is called when the user adds the first IGV track, or * the igv session is restored on page reload. * * @param config -- The IGV browser configuration object. Must include a reference genome, but might also include * an initial locus or tracks. * @returns {Promise} */ async function createIGVBrowser(config) { // Override locus in the IGV session with the UCSC locus // TODO -- should we use genomePos here? const ucscImageWidth = document.getElementById("td_data_ruler").clientWidth; const resolution = (hgTracks.winEnd - hgTracks.winStart) / ucscImageWidth; config.locus = {chr: hgTracks.chromName, start: hgTracks.winStart, bpPerPixel: resolution}; - console.log("Creating IGV browser with config: ", config); + //console.log("Creating IGV browser with config: ", config); - let igvRow = document.getElementById("tr_igv") + let igvRow = document.getElementById("tr_igv"); if (!igvRow) { // No existing igv row, insert one igvRow = insertIGVRow(); } // Ammend the igv config to remove most of the IGV widgets. We only want the track display area. Object.assign(config, { showNavigation: false, showIdeogram: false, showRuler: false, //showSequence: false, // Uncomment this to hide igv sequence track showAxis: false, showTrackDragHandles: false, showAxisColumn: false, @@ -668,50 +672,57 @@ // as if the user had dragged a track image igvBrowser.on('trackdrag', e => { isDragging = true; const newStart = igvBrowser.referenceFrameList[0].start; igv.ucscTrackpan(newStart); } ); // Notify UCSC browser that igv.js track panning has ended. igvBrowser.on('trackdragend', () => { isDragging = false; igv.ucscTrackpanEnd(); } ); - window.igvBrowser = igvBrowser; + // Repaint name panels as needed + igvBrowser.on('trackheightchange', () => { + updateTrackNames(); + }) + igvBrowser.on('tracknamechange', () => { + updateTrackNames(); + }) // Start the session auto-save timer. This will periodically save the igv session to local storage. // When / if we can reliably capture IGV state changes we can eliminate this and just save on state change. startSessionAutoSave(); + window.igvBrowser = igvBrowser; // Make available to hgTracks.js code + return igvBrowser; } - // Respond to messages from the filePicker window. channel.onmessage = async function (event) { const msg = event.data; if (!msg || !msg.type) return; switch (msg.type) { case MSG.SELECTED_FILES: - console.log("Received selected files: ", event.data.files); + //console.log("Received selected files: ", event.data.files); const configs = getTrackConfigurations(event.data.files); loadIGVTracks(configs); break; case MSG.LOAD_URL: loadIGVTracks([event.data.config]); break; // NOTE: The file picker ready message does not appear to be used. case MSG.FILE_PICKER_READY: const filesToRestore = JSON.parse(sessionStorage.getItem('filesToRestore')); if (filesToRestore) { channel.postMessage({type: MSG.RESTORE_FILES, files: filesToRestore}); sessionStorage.removeItem('filesToRestore'); } @@ -719,31 +730,31 @@ } }; /** * Load one or more tracks into the igvBrowser. If the igvBrowser does not exist it is created. * * @param configs * @returns {Promise} */ async function loadIGVTracks(configs) { if (configs.length > 0) { // Create igvBrowser if needed -- i.e. this is the first track being added. State needs to be obtained // from the UCSC browser for genome and locus. - if (typeof (window.igvBrowser) === 'undefined' || window.igvBrowser === null) { + if (typeof (igvBrowser) === 'undefined' || igvBrowser === null) { const defaultConfig = { reference: await getMinimalReference(getDb()), // locus: genomePos.get() }; igvBrowser = await createIGVBrowser(defaultConfig); } // First search for existing tracks referencing the same files. This is to handle the situation // of a user closing the file picker window, thus loosing file references, then reopening the file picker // to restore them. const newConfigs = []; for (let config of configs) { const matchingTracks = igvBrowser.findTracks(t => config.id === t.id);