2dfd61094ec3e548f25f56e83379f85ec22cfab5
chmalee
  Thu Oct 31 16:48:26 2024 -0700
Start using Uppy instead of my own UI

diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js
index a9ad6f1..0e0b485 100644
--- src/hg/js/hgMyData.js
+++ src/hg/js/hgMyData.js
@@ -1,29 +1,47 @@
 /* jshint esnext: true */
-debugCartJson = true;
+var debugCartJson = true;
 
 function prettyFileSize(num) {
     if (!num) {return "n/a";}
     if (num < (1000 * 1024)) {
         return `${(num/1000).toFixed(1)}kb`;
     } else if (num < (1000 * 1000 * 1024)) {
         return `${((num/1000)/1000).toFixed(1)}mb`;
     } else {
         return `${(((num/1000)/1000)/1000).toFixed(1)}gb`;
     }
 }
 
+// make our Uppy instance:
+const uppy = new Uppy.Uppy({
+    debug: true,
+    onBeforeUpload: (files) => {
+        // set all the fileTypes and genomes from their selects
+        for (let [key, file] of Object.entries(files)) {
+            if (!file.meta.genome || !file.meta.fileType) {
+                uppy.getPlugin("Dashboard").info("error!");
+            }
+            uppy.setFileMeta(file.id, {
+                fileName: file.name,
+                fileSize: file.size,
+                lastModified: file.data.lastModified,
+            });
+        }
+    },
+});
+
 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;
@@ -151,48 +169,44 @@
         "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"],
     };
 
     function detectFileType(fileName) {
-        let selectedType = document.getElementById(`${file.name}#typeInput`).selectedOptions[0].value;
-        if (selectedType === "Auto-detect from extension") {
         let fileLower = fileName.toLowerCase();
         for (let fileType in extensionMap) {
             for (let extIx in extensionMap[fileType]) {
                 let ext = extensionMap[fileType][extIx];
                 if (fileLower.endsWith(ext)) {
                     return fileType;
                 }
             }
         }
         //TODO: raise an error
         alert(`file extension for ${fileName} not found, please explicitly select it`);
-        } else {
-            return selectedType;
-        }
     }
 
+    /*
     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 = {};
@@ -214,41 +228,30 @@
                 // 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");
             }
-            const d = new Date(req.lastModified);
-            newReqObj = {
-                "uploadTime": Date.now(),
-                "lastModified": d.toJSON(),
-                "fileName": metadata.fileName,
-                "fileSize": metadata.fileSize,
-                "fileType": metadata.fileType,
-                "genome": metadata.genome,
-                "hub": ""
-            };
-            addNewUploadedFileToTable(newReqObj);
         };
 
         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);
@@ -276,134 +279,131 @@
                 };
                 // 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 makeGenomeSelect(fileName) {
-        let genomeInp = document.createElement("select");
-        genomeInp.classList.add("genomePicker");
-        genomeInp.name = `${fileName}#genomeInput`;
-        genomeInp.id = `${fileName}#genomeInput`;
-        let labelChoice = document.createElement("option");
-        labelChoice.label = "Choose Genome";
-        labelChoice.value = "Choose Genome";
-        labelChoice.selected = true;
-        labelChoice.disabled = true;
-        genomeInp.appendChild(labelChoice);
+        //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 = document.createElement("option");
+        let cartChoice = {};
         cartChoice.id = cartDb;
         cartChoice.label = cartDb;
-        cartChoice.value = cartDb.split(" ").slice(-1);
+        cartChoice.value = cartDb.split(" ").slice(-1)[0];
         if (cartChoice.value.startsWith("hub_")) {
             cartChoice.label = cartDb.split(" ").slice(0,-1).join(" "); // take off the actual db value
         }
         cartChoice.selected = true;
-        genomeInp.appendChild(cartChoice);
+        ret.push(cartChoice);
         choices.forEach( (e) =>  {
             if (e === cartDb) {return;} // don't print the cart database twice
-            let choice = document.createElement("option");
+            let choice = {};
             choice.id = e;
             choice.label = e;
             choice.value = e.split(" ")[1];
-            genomeInp.appendChild(choice);
-        });
-        return genomeInp;
-    }
-
-    function makeTypeSelect(fileName) {
-        let typeInp = document.createElement("select");
-        typeInp.classList.add("typePicker");
-        typeInp.name = `${fileName}#typeInput`;
-        typeInp.id = `${fileName}#typeInput`;
-        let labelChoice = document.createElement("option");
-        labelChoice.label = "Choose File Type";
-        labelChoice.value = "Choose File Type";
-        labelChoice.disabled = true;
-        typeInp.appendChild(labelChoice);
-        let autoChoice = document.createElement("option");
+            ret.push(choice);
+        });
+        return ret;
+    }
+
+    function makeTypeSelectOptions() {
+        let ret = [];
+        let autoChoice = {};
         autoChoice.label = "Auto-detect from extension";
         autoChoice.value = "Auto-detect from extension";
         autoChoice.selected = true;
-        typeInp.appendChild(autoChoice);
+        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 = document.createElement("option");
+            let choice = {};
             choice.id = e;
             choice.label = e;
             choice.value = e;
-            typeInp.appendChild(choice);
+            ret.push(choice);
         });
-        return typeInp;
+        return ret;
     }
 
 
     function createTypeAndDbDropdown(fileName) {
         typeInp = makeTypeSelect(fileName);
-        genomeInp = makeGenomeSelect(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;
                 }
@@ -429,31 +429,31 @@
 
                 // 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 = makeGenomeSelect("batchChangeSelectDb");
+                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;
                     });
                 });
@@ -560,86 +560,87 @@
             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,
+            //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: startUploadDialog,
+                        action: function() {return;},
+                        className: 'uploadButton',
                         enabled: false, // disable by default in case user is not logged in
                     },
                 ]
             }
         },
         columnDefs: [
             {
                 orderable: false, targets: 0,
                 title: "<input type=\"checkbox\"></input>",
                 render: function(data, type, row) {
                     return "<input type=\"checkbox\"></input>";
                 }
             },
             {
                 orderable: false, targets: 1,
@@ -799,73 +800,258 @@
         }
         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());
+            //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);
+                this.id = "BatchChangePlugin";
+                this.type = "progressindicator";
+                this.opts = opts;
+            }
+
+            createOptsForSelect(select, opts) {
+                opts.forEach( (opt) => {
+                    let option = document.createElement("option");
+                    option.value = opt.value;
+                    option.label = opt.label;
+                    option.id = opt.id;
+                    option.selected = typeof opt.selected !== 'undefined' ? opt.selected : false;
+                    select.appendChild(option);
+                });
+            }
+
+            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";
+                    let batchDbSelect = document.createElement("select");
+                    let batchTypeSelect = document.createElement("select");
+                    this.createOptsForSelect(batchDbSelect, [{id: "batchChangeDb", name: "batchChangeDb"}]);
+                    this.createOptsForSelect(batchTypeSelect, [{id: "batchChangeType", name: "batchChangeType"}]);
+                    batchSelectDiv.textContent = "Change options for all files";
+                    batchSelectDiv.appendChild(batchDbSelect);
+                    batchSelectDiv.appendChild(batchTypeSelect);
+                    batchSelectDiv.style.display = "flex";
+                    batchSelectDiv.style.justifyContent = "center";
+                    let titleBarText = document.querySelector(".uppy-DashboardContent-title");
+                    if (titleBarText) {
+                        batchSelectDiv.style.color = getComputedStyle(titleBarText).color;
+                    }
+                    // append the batch changes to the bottom of the file list, for some reason
+                    // I can't append to the actual Dashboard-files, it must be getting emptied
+                    // and re-rendered or something
+                    let uppyFilesDiv = document.querySelector(".uppy-Dashboard-progressindicators");
+                    if (uppyFilesDiv) {
+                        uppyFilesDiv.insertBefore(batchSelectDiv, uppyFilesDiv.firstChild);
+                    }
+                }
+            }
+
+            install() {
+                this.uppy.on("file-added", (file) => {
+                    // add default meta data for genome and fileType
+                    console.log("file-added");
+                    this.uppy.setFileMeta(file.id, {"genome": defaultDb(), "fileType": defaultFileType(file.name)});
+                    if (this.uppy.getFiles().length > 1) {
+                        this.addBatchSelectsToDashboard()
+                    }
+                });
+                this.uppy.on("file-removed", (file) => {
+                    // remove the batch change selects if now <2 files present
+                    if (this.uppy.getFiles().length < 2) {
+                        this.removeBatchSelectsFromDashboard();
+                    }
+                });
+
+                this.uppy.on("dashboard:modal-open", () => {
+                    // check if there were already files chosen from before:
+                    if (this.uppy.getFiles().length > 2) {
+                        this.addBatchSelectsToDashboard();
+                    }
+                    if (this.uppy.getFiles().length < 2) {
+                        this.removeBatchSelectsFromDashboard();
+                    }
+                });
+                this.uppy.on("dashboard:modal-close", () => {
+                    if (this.uppy.getFiles().length < 2) {
+                        this.removeBatchSelectsFromDashboard();
+                    }
+                });
+            }
+            uninstall() {
+                // not really used because we aren't ever uninstalling the uppy instance
+                this.uppy.off("file-added");
+            }
+        }
+        let uppyOptions = {
+            //target: "#filePickerModal", // this seems nice but then the jquery css interferes with
+                                          // the uppy css
+            trigger: ".uploadButton",
+            showProgressDetails: true,
+            note: "Example text in the note field",
+            meta: {"genome": null, "fileType": null},
+            metaFields: (file) => {
+                const fields = [{id: 'name', name: 'File name'}];
+                fields.push({
+                    id: 'genome',
+                    name: 'Genome',
+                    render: ({value, onChange}, h) => {
+                        return h('select',
+                            {onChange: e => onChange(e.target.value)}, 
+                            makeGenomeSelectOptions().map( (genomeObj) => {
+                                return h('option', genomeObj, genomeObj.label);
+                            })
+                        );
+                    },
+                });
+                fields.push({
+                    id: 'fileType',
+                    name: 'File Type',
+                    render: ({value, onChange}, h) => {
+                        return h( 'select',
+                            {onChange: e => {
+                                if (e.target.value === "Auto-detect from extension") {
+                                    onChange(detectFileType(file.name));
+                                } else {
+                                    onChange(e.target.value);
+                                }
+                            }},
+                            makeTypeSelectOptions().map( (typeObj) => {
+                                return h('option', typeObj, typeObj.label);
+                            })
+                        );
+                    },
+                });
+                return fields;
+            },
+            restricted: {requiredMetaFields: ["genome", "fileType"]},
+            closeModalOnClickOutside: true,
+            closeAfterFinish: true,
+            theme: 'auto',
+        };
+        let tusOptions = {
+            endpoint: getTusdEndpoint(),
+            withCredentials: true,
+            retryDelays: null,
+        };
+        uppy.use(Uppy.Dashboard, uppyOptions);
+        uppy.use(Uppy.Tus, tusOptions);
+        uppy.use(BatchChangePlugin, {target: Uppy.Dashboard});
+        uppy.on('upload-success', (file, response) => {
+            const metadata = file.meta;
+            const d = new Date(metadata.lastModified);
+            newReqObj = {
+                "uploadTime": Date.now(),
+                "lastModified": d.toJSON(),
+                "fileName": metadata.fileName,
+                "fileSize": metadata.fileSize,
+                "fileType": metadata.fileType,
+                "genome": metadata.genome,
+                "hub": ""
+            };
+            addNewUploadedFileToTable(newReqObj);
+        });
     }
     return { init: init,
              uiState: uiState,
            };
-
 }());
 
 
+
 // when a user reaches this page from the back button we can display our saved state
 // instead of sending another network request
 window.onpopstate = function(event) {
     event.preventDefault();
     hubCreate.updateStateAndPage(event.state, false);
 };