8f2e6dbc0e250e934382477cea73e60624ccfe32 chmalee Fri Feb 16 11:45:42 2024 -0800 Start of populating a list of files to the UI diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js index 3fb8d9f..969c25a 100644 --- src/hg/js/hgMyData.js +++ src/hg/js/hgMyData.js @@ -23,30 +23,31 @@ 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 togglePickStateMessage(showMsg = false) { if (showMsg) { let para = document.createElement("p"); para.textContent = "No files selected for upload"; para.classList.add("noFiles"); uiState.pickedList.prepend(para); + removeClearSubmitButtons(); } else { let msg = document.querySelector(".noFiles"); if (msg) { msg.parentNode.removeChild(msg); } } } function liForFile(file) { let liId = `${file.name}#li`; let li = document.getElementById(liId); return li; } function newButton(text) { @@ -127,114 +128,76 @@ 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; } - - function sendFile(file) { - // this function can go away once tus is implemented - // this is mostly adapted from the mdn example: - // https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#example_uploading_a_user-selected_file - // we will still need all the event handlers though - const xhr = new XMLHttpRequest(); - this.progMeter = makeNewProgMeter(file.name); - this.xhr = xhr; - const endpoint = "../cgi-bin/hgHubConnect"; - const self = this; // why do we need this? - const fd = new FormData(); - - this.xhr.upload.addEventListener("progress", (e) => { - if (e.lengthComputable) { - const pct = Math.round(e.loaded * 100) / e.total; - self.progMeter.updateProgress(pct); - } - }, false); - - // loadend handles abort/error or load events - this.xhr.upload.addEventListener("loadend", (e) => { - this.progMeter.updateProgress(); - const canvas = self.progMeter.ctx.canvas; - canvas.parentNode.removeChild(canvas); - delete uiState.toUpload[file.name]; - let li = liForFile(file); - li.parentNode.removeChild(li); - if (Object.keys(uiState.toUpload).length === 0) { - removeCancelAllButton(); - } - }, false); - - // for now just treat it like an abort/error - this.xhr.upload.addEventListener("timeout", (e) => { - progMeter.updateProgress(); - const canvas = self.progMeter.ctx.canvas; - canvas.parentNode.removeChild(canvas); - }); - - this.xhr.upload.addEventListener("load", (e) => { - // TODO: on load populate the uploaded files side - let uploadSection = document.getElementById("uploadedFilesSection"); - if (uploadSection.style.display === "none") { - uploadSection.style.display = ""; - } - }); - - // on error keep the file name present and show the error somehow - this.xhr.upload.addEventListener("error", (e) => { - }); - this.xhr.upload.addEventListener("abort", (e) => { - console.log("request aborted"); - }); - this.xhr.upload.addEventListener("timeout", (e) => { - }); - - // finally we can send the request - this.xhr.open("POST", endpoint, true); - fd.set("createHub", 1); - fd.set("userFile", file); - this.xhr.send(fd); - uiState.pendingQueue.push([this.xhr,file]); - - addCancelButton(file, this.xhr); - } - function submitPickedFiles() { let tusdServer = getTusdEndpoint(); + let onBeforeRequest = function(req) { let xhr = req.getUnderlyingObject(req); xhr.withCredentials = true; }; + + let onSuccess = function(reqFname) { + // 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 reqFname + let i; + let newFileList = []; + for (i = 0; i < uiState.input.files.length; i++) { + fname = uiState.input.files[i].name; + if (fname !== reqFname) { + newFileList.unshift(uiState.input.files[0]); + } + } + newFileList.reverse(); + //uiState.input.files = newFileList; + // remove the file from the list the user can see + let li = document.getElementById(reqFname+"#li"); + li.parentNode.removeChild(li); + // add the file to the data table somehow? + removeCancelAllButton(); + }; + + let onError = function(err) { + alert(err); + }; + for (let f in uiState.toUpload) { file = uiState.toUpload[f]; if (useTus) { let tusOptions = { endpoint: tusdServer, metadata: { filename: file.name, fileType: file.type, fileSize: file.size }, onBeforeRequest: onBeforeRequest, + onSuccess: onSuccess(file.name), + onError: onError, retryDelays: [1000], }; // 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); tusUpload.start(); } else { // make a new XMLHttpRequest for each file, if tusd-tusclient not supported new sendFile(file); } } addCancelAllButton(); return; } @@ -289,30 +252,85 @@ // create a list for the user to see let li = document.createElement("li"); li.classList.add("pickedFile"); li.id = `${file.name}#li`; li.textContent = `File name: ${file.name}, file size: ${prettyFileSize(file.size)}`; displayList.appendChild(li); // finally add it for us uiState.toUpload[file.name] = file; } togglePickStateMessage(false); addClearSubmitButtons(); } } + function dataTablePrintSize(data, type, row, meta) { + return prettyFileSize(data); + } + + function deleteFile(fname) { + // Send an async request to hgHubConnect to delete the file + // Note that repeated requests, like from a bot, will return 404 as a correct response + console.log(`sending delete req for ${fname}`); + const xhr = new XMLHttpRequest(); + const endpoint = "../cgi-bin/hgHubConnect?deleteFile=" + fname; + this.xhr = xhr; + this.xhr.open("DELETE", endpoint, true); + this.xhr.send(); + // TODO: on the correct return code, delete the row + } + + function showExistingFiles(fileList) { + // Make the DataTable for each file + //$(document).on("draw.dt", function() {alert("table redrawn");}); + let table = $("#filesTable").DataTable({ + data: fileList, + columnDefs: [{orderable: false, targets: [0,1]}], + columns: [ + {data: "", title: "<input type=\"checkbox\"></input>", + render: function(data, type, row) { + return "<input type=\"checkbox\"></input>"; + } + }, + {data: "action", title: "Action", + render: function(data, type, row) { + // TODO: add event handler on delete button + // click to call hgHubDelete file + return "<button class='deleteFileBtn'>Delete</button"; + } + }, + {data: "name", title: "File name"}, + {data: "size", title: "File size", render: dataTablePrintSize}, + {data: "createTime", title: "Creation Time"}, + ], + order: [[4, 'desc']], + initComplete: function(settings, json) { + let btns = document.querySelectorAll('.deleteFileBtn'); + let i; + for (i = 0; i < btns.length; i++) { + let fnameNode = btns[i].parentNode.nextElementSibling.childNodes[0]; + if (fnameNode.nodeName !== "#text") {continue;} + let fname = fnameNode.nodeValue; + btns[i].addEventListener("click", (e) => { + deleteFile(fname); + }); + } + } + }); + } + function checkJsonData(jsonData, callerName) { // Return true if jsonData isn't empty and doesn't contain an error; // otherwise complain on behalf of caller. if (! jsonData) { alert(callerName + ': empty response from server'); } else if (jsonData.error) { console.error(jsonData.error); alert(callerName + ': error from server: ' + jsonData.error); } else if (jsonData.warning) { alert("Warning: " + jsonData.warning); return true; } else { if (debugCartJson) { console.log('from server:\n', jsonData); } @@ -391,30 +409,38 @@ } 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 // TODO: make hgHubConnect respond to requests // TODO: initialize tus-client // TODO: get user name // TODO: send a request with username // TODO: have tusd respond on server + let uploadSection = document.getElementById("uploadedFilesSection"); + if (uploadSection.style.display === "none") { + uploadSection.style.display = ""; + } + if (typeof userFiles !== 'undefined' && typeof userFiles.fileList !== 'undefined' && + userFiles.fileList.length > 0) { + showExistingFiles(userFiles.fileList); + } input.addEventListener("change", listPickedFiles); // TODO: add event handler for when file is succesful upload // TODO: add event handlers for editing defaults, grouping into hub // TODO: display quota somewhere // TODO: customize the li to remove the picked file } } return { init: init, uiState: uiState, }; }()); // when a user reaches this page from the back button we can display our saved state