6f87a0fb5dffd9f4e814e0bfe0d7b5d7693a7a0e chmalee Mon Nov 18 15:52:15 2024 -0800 Remove a bunch of now dead code now that we are using uppy. display the quota next to the upload button diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js index 61d16a5..11c269e 100644 --- src/hg/js/hgMyData.js +++ src/hg/js/hgMyData.js @@ -30,162 +30,43 @@ doUpload = false; continue; } uppy.setFileMeta(file.id, { fileName: file.name, fileSize: file.size, lastModified: file.data.lastModified, }); } return doUpload; }, }); var hubCreate = (function() { let uiState = { // our object for keeping track of the current UI and what to do - toUpload: {}, // set of file objects keyed by name - input: null, // the hidden input element - pickedList: null, // the <div> for displaying files in toUpload - pendingQueue: [], // our queue of pending [tus.Upload, file], kind of like the toUpload object - fileList: [], // the files this user has uploaded, initially populated by the server - // on page load, but gets updated as the user uploades/deletes files - hubList: [], // the hubs this user has created/uploaded, initially populated by server - // on page load, but gets updated as the user creates/deletes hubs userUrl: "", // the web accesible path where the uploads are stored for this user }; // We can use XMLHttpRequest if necessary or a mirror can't use tus var useTus = tus.isSupported && true; function getTusdEndpoint() { // return the port and basepath of the tusd server // NOTE: the port and basepath are specified in hg.conf //let currUrl = parseUrl(window.location.href); return "https://hgwdev-hubspace.gi.ucsc.edu/files"; } - function liForFile(file) { - let liId = `${file.name}#li`; - let li = document.getElementById(liId); - return li; - } - - function newButton(text) { - /* Creates a new button with some text as the label */ - let newBtn = document.createElement("label"); - newBtn.classList.add("button"); - newBtn.textContent = text; - return newBtn; - } - - function createInput() { - /* Create a new input element for a file picker */ - let input = document.createElement("input"); - input.multiple = true; - input.type = "file"; - input.id = "hiddenFileInput"; - input.style = "display: none"; - input.addEventListener("change", listPickedFiles); - return input; - } - - function requestsPending() { - /* Return true if requests are still pending, which means it needs to - * have been sent(). aborted requests are considered done for this purpose */ - for (let [req, f] of uiState.pendingQueue) { - if (req._req !== null) { - xreq = req._req._xhr; - if (xreq.readyState != XMLHttpRequest.DONE && xreq.readyState != XMLHttpRequest.UNSENT) { - return true; - } - } - } - return false; - } - - function addCancelButton(file, req) { - /* Add a button that cancels the request req */ - let li = liForFile(file); - let newBtn = newButton("Cancel upload"); - newBtn.addEventListener("click", (e) => { - req.abort(); - li.removeChild(newBtn); - // TODO: make this remove the cancel all button if it's the last pending - // request - stillPending = requestsPending(); - if (!stillPending) { - let btnRow = document.getElementById("chooseAndSendFilesRow"); - cAllBtn = btnRow.lastChild; - btnRow.removeChild(cAllBtn); - } - }); - li.append(newBtn); - } - - function removeCancelAllButton() { - let btnRow = document.getElementById("chooseAndSendFilesRow"); - if (btnRow.lastChild.textContent === "Cancel all") { - btnRow.removeChild(btnRow.lastChild); - } - } - - function addCancelAllButton() { - let btnRow = document.getElementById("chooseAndSendFilesRow"); - let newBtn = newButton("Cancel all"); - newBtn.addEventListener("click", (e) => { - while (uiState.pendingQueue.length > 0) { - let [req, f] = uiState.pendingQueue.pop(); - // we only need to abort requests that haven't finished yet - if (req._req !== null) { - if (req._req._xhr.readyState != XMLHttpRequest.DONE) { - req.abort(true); - } - } - let li = liForFile(f); - if (li !== null) { - // the xhr event handlers should handle this but just in case - li.removeChild(li.lastChild); - } - } - }); - btnRow.appendChild(newBtn); - } - - function makeNewProgMeter(fileName) { - // create a progress meter for this filename - const progMeterWidth = 128; - const progMeterHeight = 12; - const progMeter = document.createElement("canvas"); - progMeter.classList.add("upload-progress"); - progMeter.setAttribute("width", progMeterWidth); - progMeter.setAttribute("height", progMeterHeight); - idStr = `${fileName}#fileName`; - ele = document.getElementById(idStr); - ele.appendChild(progMeter); - progMeter.ctx = progMeter.getContext('2d'); - progMeter.ctx.fillStyle = 'orange'; - progMeter.updateProgress = (percent) => { - // update the progress meter for this elem - if (percent === 100) { - progMeter.ctx.fillStyle = 'green'; - } - progMeter.ctx.fillRect(0, 0, (progMeterWidth * percent) / 100, progMeterHeight); - }; - progMeter.updateProgress(0); - return progMeter; - } - let extensionMap = { "bigBed": [".bb", ".bigbed"], "bam": [".bam"], "vcf": [".vcf"], "vcfTabix": [".vcf.gz", "vcf.bgz"], "bigWig": [".bw", ".bigwig"], "hic": [".hic"], "cram": [".cram"], "bigBarChart": [".bigbarchart"], "bigGenePred": [".bgp", ".biggenepred"], "bigMaf": [".bigmaf"], "bigInteract": [".biginteract"], "bigPsl": [".bigpsl"], "bigChain": [".bigchain"], "bamIndex": [".bam.bai", ".bai"], @@ -197,139 +78,30 @@ for (let fileType in extensionMap) { for (let extIx in extensionMap[fileType]) { let ext = extensionMap[fileType][extIx]; if (fileLower.endsWith(ext)) { return fileType; } } } //we could alert here but instead just explicitly set the value to null //and let the backend reject it instead, forcing the user to rename their //file //alert(`file extension for ${fileName} not found, please explicitly select it`); return null; } - /* - function submitPickedFiles() { - - let tusdServer = getTusdEndpoint(); - - let onBeforeRequest = function(req) { - let xhr = req.getUnderlyingObject(req); - xhr.withCredentials = true; - }; - - let onSuccess = function(req, metadata) { - // remove the selected file from the input element and the ul list - // FileList is a read only setting, so we have to make - // a new one without this req - delete uiState.toUpload[req.name]; - let i, newReqObj = {}; - for (i = 0; i < uiState.pendingQueue.length; i++) { - fname = uiState.pendingQueue[i][1].name; - if (fname === req.name) { - // remove the successful tusUpload off - uiState.pendingQueue.splice(i, 1); - } - } - // remove the file from the list the user can see - let rowDivs = document.querySelectorAll("[id^='" + req.name+"']"); - let delIcon = rowDivs[0].previousElementSibling; - rowDivs.forEach((div) => {div.remove();}); - delIcon.remove(); - - // if nothing else we can close the dialog - if (uiState.pendingQueue.length === 0) { - // first remove the grid headers - let headerEle = document.querySelectorAll(".fileListHeader"); - headerEle.forEach( (header) => { - if (header.style.display !== "none") { - header.style.display = "none"; - } - }); - //check if we can remove the batch change selects - if (uiState.pendingQueue.length > 1) { - document.querySelectorAll("[id^=batchChangeSelect]").forEach( (select) => { - select.remove(); - }); - } - $("#filePickerModal").dialog("close"); - } - }; - - let onError = function(metadata, err) { - console.log("failing metadata:"); - console.log(metadata); - removeCancelAllButton(); - if (err.originalResponse !== null) { - alert(err.originalResponse._xhr.responseText); - } else { - alert(err); - } - }; - - let onProgress = function(bytesSent, bytesTotal) { - this.updateProgress((bytesSent / bytesTotal) * 100); - }; - - for (let f in uiState.toUpload) { - file = uiState.toUpload[f]; - if (useTus) { - let progMeter = makeNewProgMeter(file.name); - let metadata = { - fileName: file.name, - fileSize: file.size, - fileType: detectFileType(file.name), - genome: document.getElementById(`${file.name}#genomeInput`).selectedOptions[0].value, - lastModified: file.lastModified, - }; - let tusOptions = { - endpoint: tusdServer, - metadata: metadata, - onProgress: onProgress.bind(progMeter), - onBeforeRequest: onBeforeRequest, - onSuccess: onSuccess.bind(null, file, metadata), - onError: onError.bind(null, metadata), - retryDelays: null, - }; - // TODO: get the uploadUrl from the tusd server - // use a pre-create hook to validate the user - // and get an uploadUrl - let tusUpload = new tus.Upload(file, tusOptions); - uiState.pendingQueue.push([tusUpload, file]); - tusUpload.start(); - } else { - // make a new XMLHttpRequest for each file, if tusd-tusclient not supported - new sendFile(file); - } - } - addCancelAllButton(); - return; - } - */ - - /* - function clearPickedFiles() { - while (uiState.pickedList.firstChild) { - uiState.pickedList.removeChild(uiState.pickedList.firstChild); - } - //uiState.input = createInput(); - //uiState.toUpload = {}; - } - */ - function defaultFileType(file) { return detectFileType(file); } function defaultDb() { return cartDb.split(" ").slice(-1)[0]; } function makeGenomeSelectOptions() { // Returns an array of options for genomes let ret = []; let choices = ["Human hg38", "Human T2T", "Human hg19", "Mouse mm39", "Mouse mm10"]; let cartChoice = {}; cartChoice.id = cartDb; cartChoice.label = cartDb; @@ -356,155 +128,30 @@ autoChoice.label = "Auto-detect from extension"; autoChoice.value = "Auto-detect from extension"; autoChoice.selected = true; ret.push(autoChoice); let choices = ["bigBed", "bam", "vcf", "vcf (bgzip or gzip compressed)", "bigWig", "hic", "cram", "bigBarChart", "bigGenePred", "bigMaf", "bigInteract", "bigPsl", "bigChain"]; choices.forEach( (e) => { let choice = {}; choice.id = e; choice.label = e; choice.value = e; ret.push(choice); }); return ret; } - - function createTypeAndDbDropdown(fileName) { - typeInp = makeTypeSelect(fileName); - genomeInp = makeGenomeSelectOptions(fileName); - return [typeInp, genomeInp]; - } - - /* - function deletePickedFile(eventInst) { - // called when the trash icon has been clicked to remove a file - // the sibling text content is the file name, which we use to - // find all the other elements to delete - let trashIconDiv = eventInst.currentTarget; - fname = trashIconDiv.nextSibling.textContent; - document.querySelectorAll("[id^='" + fname + "']").forEach( (sib) => { - sib.remove(); - }); - trashIconDiv.remove(); - delete uiState.toUpload[fname]; - // if there are no file rows left (1 row for the header) in the picker hide the headers: - let container = document.getElementById("fileList"); - if (getComputedStyle(container).getPropertyValue("grid-template-rows").split(" ").length === 1) { - let headerEle = document.querySelectorAll(".fileListHeader"); - headerEle.forEach( (header) => { - if (header.style.display !== "none") { - header.style.display = "none"; - } - }); - } - // if there is only one file picked hide the batch change selects, we may or may not have - // hidden the headers yet, so we should have 3 or less rows - if (getComputedStyle(container).getPropertyValue("grid-template-rows").split(" ").length <= 3) { - let batchChange = document.querySelectorAll("[id^=batchChangeSelect]"); - batchChange.forEach( (select) => { - select.remove(); - }); - } - } - */ - - function listPickedFiles() { - // displays the users chosen files in a grid: - if (uiState.input.files.length === 0) { - console.log("not input"); - return; - } else { - let displayList = document.getElementById("fileList"); - let deleteEle = document.createElement("div"); - deleteEle.classList.add("deleteFileIcon"); - deleteEle.innerHTML = "<svg xmlns='http://www.w3.org/2000/svg' height='0.8em' viewBox='0 0 448 512'><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><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>"; - for (let file of uiState.input.files ) { - if (file.name in uiState.toUpload) { - continue; - } - // create a list for the user to see - let nameCell = document.createElement("div"); - nameCell.classList.add("pickedFile"); - nameCell.id = `${file.name}#fileName`; - nameCell.textContent = `${file.name}`; - // Add the form controls for this file: - let [typeCell, dbCell] = createTypeAndDbDropdown(file.name); - let sizeCell = document.createElement("div"); - sizeCell.classList.add("pickedFile"); - sizeCell.id = `${file.name}#fileSize`; - sizeCell.textContent = prettyFileSize(file.size); - - newDelIcon = deleteEle.cloneNode(true); - newDelIcon.addEventListener("click", deletePickedFile); - displayList.appendChild(newDelIcon); - displayList.appendChild(nameCell); - displayList.appendChild(typeCell); - displayList.appendChild(dbCell); - displayList.appendChild(sizeCell); - - // finally add it for us - uiState.toUpload[file.name] = file; - } - let headerEle = document.querySelectorAll(".fileListHeader"); - headerEle.forEach( (header) => { - if (header.style.display !== "block") { - header.style.display = "block"; - } - }); - if (Object.keys(uiState.toUpload).length > 1) { - // put up inputs to batch change all file inputs and dbs - let batchType = document.createElement("div"); - batchType = makeTypeSelect("batchChangeSelectType"); - let batchDb = document.createElement("div"); - batchDb = makeGenomeSelectOptions("batchChangeSelectDb"); - - // place into the grid in the right spot: - batchType.classList.add('batchTypeSelect'); - batchDb.classList.add('batchDbSelect'); - - // update each files select on change - batchType.addEventListener("change", function(e) { - let newVal = e.currentTarget.selectedOptions[0].value; - document.querySelectorAll("[id$=typeInput]").forEach( (i) => { - if (i === e.currentTarget) { - return; - } - i.value = newVal; - }); - }); - batchDb.addEventListener("change", function(e) { - let newVal = e.currentTarget.selectedOptions[0].value; - document.querySelectorAll("[id$=genomeInput]").forEach( (i) => { - if (i === e.currentTarget) { - return; - } - i.value = newVal; - }); - }); - - // append to the document - displayList.appendChild(batchType); - displayList.appendChild(batchDb); - } - document.querySelectorAll(".deleteFileIcon").forEach( (i) => { - }); - } - // always clear the input element - uiState.input = createInput(); - } - function dataTablePrintSize(data, type, row, meta) { return prettyFileSize(data); } function dataTablePrintGenome(data, type, row, meta) { if (data.startsWith("hub_")) return data.split("_").slice(2).join("_"); return data; } function deleteFileFromTable(fname) { // req is an object with properties of an uploaded file, make a new row // for it in the filesTable let table = $("#filesTable").DataTable(); let row = table.row((idx, data) => data.fileName === fname); @@ -575,90 +222,42 @@ fileType: "hub", fileName: jqXhr.hubName, genome: jqXhr.db, fileSize: null, hub: jqXhr.hubName }); } function createHub(db, hubName) { // send a request to hgHubConnect to create a hub for this user cart.setCgi("hgHubConnect"); cart.send({createHub: {db: db, name: hubName}}, createHubSuccess, null); cart.flush(); } - /* - function startUploadDialog() { - // put up a dialog to walk a user through uploading data files and setting up a track hub - console.log("create a hub button clicked!"); - hubUploadButtons = { - "Start": function() { - submitPickedFiles(); - let currBtns = $("#filePickerModal").dialog("option", "buttons"); - // add a cancel button to stop current uploads - if (!("Cancel" in currBtns)) { - currBtns.Cancel = function() { - clearPickedFiles(); - $(this).dialog("close"); - }; - $("#filePickerModal").dialog("option", "buttons", currBtns); - } - }, - "Cancel": function() { - clearPickedFiles(); - $(this).dialog("close"); - }, - "Close": function() { - // delete everything that isn't the headers, which we set to hide: - let fileList = document.getElementById("fileList"); - let headers = document.querySelectorAll(".fileListHeader"); - fileList.replaceChildren(...headers); - headers.forEach( (header) => { - header.style.display = "none"; - }); - uiState.input = createInput(); - $(this).dialog("close"); - } - }; - $("#filePickerModal").dialog({ - modal: true, - //buttons: hubUploadButtons, - minWidth: $("#filePickerModal").width(), - width: (window.innerWidth * 0.8), - height: (window.innerHeight * 0.55), - title: "Upload track data", - open: function(e, ui) { - $(e.target).parent().css("position", "fixed"); - $(e.target).parent().css("top", "10%"); - }, - }); - $("#filePickerModal").dialog("open"); - } - */ - let tableInitOptions = { layout: { topStart: { buttons: [ { text: 'Upload', action: function() {return;}, className: 'uploadButton', enabled: false, // disable by default in case user is not logged in }, - ] + ], + quota: null, } }, columnDefs: [ { orderable: false, targets: 0, title: "<input type=\"checkbox\"></input>", render: function(data, type, row) { return "<input type=\"checkbox\"></input>"; } }, { orderable: false, targets: 1, data: "action", title: "Action", render: function(data, type, row) { /* Return a node for rendering the actions column */ @@ -723,30 +322,38 @@ settings.api.buttons(0).enable(); } } }; function showExistingFiles(d) { // Make the DataTable for each file // make buttons have the same style as other buttons $.fn.dataTable.Buttons.defaults.dom.button.className = 'button'; tableInitOptions.data = d; if (isLoggedIn) { tableInitOptions.language = {emptyTable: "Uploaded files will appear here. Click \"Upload\" to get started"}; } else { tableInitOptions.language = {emptyTable: "You are not logged in, please navigate to \"My Data\" > \"My Sessions\" and log in or create an account to begin uploading files"}; } + DataTable.feature.register('quota', function(settings, opts) { + let options = Object.assign({option1: false, option2: false}, opts); + let container = document.createElement("div"); + if (isLoggedIn) { + container.textContent = `Using ${prettyFileSize(userQuota)} of ${prettyFileSize(maxQuota)}`; + } + return container; + }); let table = new DataTable("#filesTable", tableInitOptions); } function showExistingHubs(d) { // Add the hubs to the files table if (!d) {return;} let table = $("#filesTable").DataTable(); d.forEach((hub) => { let hubName = hub.hubName; let db = hub.genome; let data = { fileName: hubName, fileSize: null, fileType: "hub", genome: db, @@ -797,65 +404,58 @@ } let pickedFiles = document.getElementById("fileList"); let inputBtn = document.getElementById("btnForInput"); if (pickedFiles !== null) { // this element should be an empty div upon loading the page uiState.pickedList = pickedFiles; if (pickedFiles.children.length === 0) { let para = document.createElement("p"); para.textContent = "No files chosen yet"; para.classList.add("noFiles"); pickedFiles.parentNode.appendChild(para); } } else { // TODO: graceful handle of leaving the page and coming back? } - /* - let parent = document.getElementById("chooseAndSendFilesRow"); - let input = createInput(); - uiState.input = input; - inputBtn.parentNode.appendChild(input); - */ if (typeof cartJson !== "undefined") { if (typeof cartJson.warning !== "undefined") { alert("Warning: " + cartJson.warning); } var urlParts = {}; if (debugCartJson) { console.log('from server:\n', cartJson); } _.assign(uiState,cartJson); saveHistory(cartJson, urlParts, true); } else { // no cartJson object means we are coming to the page for the first time: //cart.send({ getUiState: {} }, handleRefreshState); //cart.flush(); // TODO: initialize buttons, check if there are already files // TODO: write functions for // after picking files // choosing file types // creating default trackDbs // editing trackDbs let fileDiv = document.getElementById('filesDiv'); if (typeof userFiles !== 'undefined' && Object.keys(userFiles).length > 0) { uiState.fileList = userFiles.fileList; uiState.hubList = userFiles.hubList; uiState.userUrl = userFiles.userUrl; } showExistingFiles(uiState.fileList.filter((row) => row.fileType !== "hub")); - //inputBtn.addEventListener("click", (e) => uiState.input.click()); // TODO: add event handlers for editing defaults, grouping into hub // TODO: display quota somewhere } $("#newTrackHubDialog").dialog({ modal: true, autoOpen: false, title: "Create new track hub", closeOnEscape: true, minWidth: 400, minHeight: 120 }); // create a custom uppy plugin to batch change the type and db fields class BatchChangePlugin extends Uppy.BasePlugin { constructor(uppy, opts) { super(uppy, opts); @@ -877,40 +477,30 @@ addSelectsForFile(file) { /* create two selects for the file object, to include the db and type */ const id = "uppy_" + file.id; let fileDiv = document.getElementById(id); // this might not exist yet depending on where we are in the render cycle if (fileDiv) { let dbSelectId = "db_select_" + file.id; if (!document.getElementById(dbSelectId)) { let dbSelect = document.createElement("select"); dbSelect.id = dbSelectId; let dbOpts = makeGenomeSelectOptions(); this.createOptsForSelect(dbSelect, dbOpts); fileDiv.appendChild(dbSelect); } - /* - let typeSelectId = "type_select_" + file.id; - if (!document.getElementById(typeSelectId)) { - let typeSelect = document.createElement("select"); - typeSelect.id = typeSelectId; - let typeOpts = makeTypeSelectOptions(); - this.createOptsForSelect(typeSelect, typeOpts); - fileDiv.appendChild(typeSelect); - } - */ } } removeBatchSelectsFromDashboard() { let batchSelectDiv = document.getElementById("batch-selector-div"); if (batchSelectDiv) { batchSelectDiv.remove(); } } addBatchSelectsToDashboard() { if (!document.getElementById("batch-selector-div")) { let batchSelectDiv = document.createElement("div"); batchSelectDiv.id = "batch-selector-div"; batchSelectDiv.style.display = "grid";