2f73d7595b0e37b159e9c9355f8e99512bf37e81 max Wed Sep 10 08:33:03 2025 -0700 Merging Jim Robinsons code into the kent tree. Due to whitespace changes in his IDE, I'm merging this manually. The original code is at https://github.com/igvteam/ucsc_dev/. changes up to 48816bc are included. Also adding an API call to get the 2bit file and code to use it. diff --git src/hg/htdocs/admin/filePicker.html src/hg/htdocs/admin/filePicker.html new file mode 100644 index 00000000000..302ab7a196b --- /dev/null +++ src/hg/htdocs/admin/filePicker.html @@ -0,0 +1,188 @@ +<!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', () => { + const channel = new BroadcastChannel('igv_file_channel') + 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: {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) + channel.postMessage({type: 'file', data: selectedFile}) // TODO -- what if selectedFile is undefined? Handle here or in browser? + } else if (type === 'removeFile') { + fileCache.delete(id) + refreshFileList() + } else if (type === 'restoreFiles') { + const failed = event.data.files + for (const f of failed) { + restoreCache.set(f.id, f.name) + } + 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>