4eaf56ff3eb90ea8ebcf1697ac3830b8a7b5465f
jrobinso
  Tue Sep 16 09:32:52 2025 -0700
Handle file picker edge case - picker is open but doesn't have handles for requested files.   Also more comments and reformatting.

diff --git src/hg/htdocs/admin/filePicker.html src/hg/htdocs/admin/filePicker.html
index 7ea150cb67e..dade2d2adf4 100644
--- src/hg/htdocs/admin/filePicker.html
+++ src/hg/htdocs/admin/filePicker.html
@@ -1,195 +1,201 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>File Picker</title>
 
     <link rel="stylesheet" href="../../style/HGStyle.css" type="text/css">
     <style>
 
         .section-group {
             border: 2px solid #ccc;
             /*border-radius: 8px; */
             padding: 16px;
             margin-bottom: 24px;
             background: #fafafa;
         }
 
         .input-row {
             display: flex;
             align-items: center;
             margin-top: 8px;
             gap: 16px;
         }
 
         .input-row label {
             min-width: 100px;
         }
 
         .input-row input[type="url"] {
             width: 500px;
         }
     </style>
 
 </head>
 <body>
 
 <section class="section-group" style="text-align: center; margin-bottom: 32px; background: #e3f2fd;">
     <h2 style="margin: 0; font-size: 2em;">File Manager</h2>
     <p style="font-size: 1.1em;">Please do not close this window, it is necessary to get access to the files that you open below. If you close this window, the Genome Browser's IGV track loses access to the files.</p>
 </section>
 
 <section class="section-group">
     <div id="message" style="color: red; font-weight: bold; display: none;">
         You can open files from your local harddisk using the 'Choose Files' button. The Genome Browser does not have access to 
         any other files on your harddisk, since it is a website. Data of these files is not sent to UCSC, but it
         remains on your local computer. As far as we know, the files can include identifying health information,
         since the data does not leave your computer. If you make a session shortlink on the Genome Browser, then these
         files cannot be loaded by the person that opens the session link, because this exact window must be open to load
         the files. As soon as the window is closed, file access is lost and you must re-open the files in this window again.
         <p>
         <ul id="restoreList"></ul>
         </p>
     </div>
     <h3>Local Files</h3>
     <p>Click 'Chose Files' to open files from your local harddisk. For indexed formats both data and index files must be selected.</p>
     <p>
         The Genome Browser webserver does not have access to any files on your
         harddisk, since it is a website. Data of the files opened below is not
         sent to UCSC, but it remains on your local computer. As far as we know,
         it is OK if these files include identifying health information, since the data
         does not leave your computer. If you make a session shortlink on the
         Genome Browser, then these files cannot be loaded by the person that
         opens the session link.
         As soon as the window is closed, file access is lost and you must re-open the files in this window again.
      </p>
 
     <input type="file" id="fileInput" multiple>
     <p>
     <h4>Selected local files:</h4>
     <ul id="fileList"></ul>
     </p>
 </section>
 
 <section class="section-group">
     <h3>Local URL</h3>
     Enter a local URL on your intranet to a track file to load. For indexed
     formats the index file is assumed to be at the same location with an added
     extension if not specified. It is OK if the UCSC webserver cannot access this file, 
     as all display is handled on your local computer. If this is a publicly accessible
     URL and public data, it is usually easier to load the file as a 'custom track' or 'track hub'.
 
     <div class="input-row">
         <label for="urlInput">URL</label>
         <input type="url" id="urlInput">
     </div>
     <div class="input-row">
         <label for="indexURLInput">Index URL</label>
         <input type="url" id="indexURLInput">
     </div>
     <p>
         <button id="urlButton">Load</button>
     </p>
 </section>
 
 
 <script>
     document.addEventListener('DOMContentLoaded', () => {
+
+        window.focus()
         const channel = new BroadcastChannel('igv_file_channel')
         channel.postMessage({type: 'filePickerReady'})
         const fileCache = new Map()
         const restoreCache = new Map()
 
         document.getElementById('fileInput').addEventListener('change', function (event) {
             const files = []
             for (let file of event.target.files) {
                 const id = `${file.name}_${file.size}`
                 files.push({id, file}) //value)
                 fileCache.set(id, file)
                 restoreCache.delete(id)
                 refreshFileList()
             }
             event.target.value = ""
 
             channel.postMessage({type: 'selectedFiles', files: files})
         })
 
         document.getElementById("urlButton").addEventListener('click', event => {
             const url = document.getElementById("urlInput").value
             if (url) {
                 let indexURL = document.getElementById("indexURLInput").value
                 if (!indexURL) {
                     if (url.endsWith(".bam")) {
                         indexURL = url + ".bai"
                     } else if (url.endsWith(".cram")) {
                         indexURL = url + ".crai"
                     } else if (url.endsWith(".vcf.gz")) {
                         indexURL = url + ".tbi"
                     }
                 }
                 channel.postMessage({type: 'loadURL', config: {id: url, url, indexURL}})
             }
         })
 
         // Respond to messages from the browser page
         channel.onmessage = function (event) {
             const {id, type} = event.data
             if (type === 'getFile') {
                 const selectedFile = fileCache.get(id)
 
                 if(selectedFile) {
                 channel.postMessage({type: 'file', data: selectedFile})   // TODO -- what if selectedFile is undefined?  Handle here or in browser?
                 } else {
                     // Don't respond if we don't have the file.  Its possible multiple file pickers are open.
                     console.info(`No file found for id: ${id}`)
                 }
             } else if (type === 'removeFile') {
                 fileCache.delete(id)
                 refreshFileList()
             } else if (type === 'restoreFiles') {
+
+                // Process a request to restore a list of files, which means prompt the user to select them
+
                 const failed = event.data.files
                 for (const f of failed) {
                     restoreCache.set(f.id, f.name)
                 }
+                window.focus()
                 refreshFileList()
             } else if (type === 'removedTrack') {
                 // TODO -- might want to remove from restoreCache too
                 const config = event.data.config
                 if (config.url && config.url.id) {
                     fileCache.delete(config.url.id)
                     restoreCache.delete(config.url.id)
                 }
                 if (config.indexURL && config.indexURL.id) {
                     fileCache.delete(config.indexURL.id)
                     restoreCache.delete(config.indexURL.id)
                 }
                 refreshFileList()
             } else if (type === 'ping') {
                 window.focus()
                 channel.postMessage({type: 'pong', id: window.name})
             }
         }
 
         function refreshFileList() {
 
             if (restoreCache.size > 0) {
                 const ul = document.getElementById("restoreList")
                 ul.innerHTML = ""
                 for (let name of restoreCache.values()) {
                     ul.innerHTML += `<li>${name}</li>`
                 }
                 document.getElementById("message").style.display = "block"
             } else {
                 document.getElementById("message").style.display = "none"
             }
 
             const ul = document.getElementById("fileList")
             ul.innerHTML = ""
             for (let file of fileCache.values()) {
                 ul.innerHTML += `<li>${file.name}</li>`
             }
         }
     })
 </script>
 </body>
 </html>