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