2f0d3fa3d2bcbf3e23e834ca68565c470f73939d
chmalee
Mon Oct 21 12:32:08 2024 -0700
Back end for auto-generating a hub with every user upload done
diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js
index 28442aa..a9ad6f1 100644
--- src/hg/js/hgMyData.js
+++ src/hg/js/hgMyData.js
@@ -199,30 +199,43 @@
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");
}
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);
};
@@ -288,30 +301,33 @@
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);
let choices = ["Human hg38", "Human T2T", "Human hg19", "Mouse mm39", "Mouse mm10"];
let cartChoice = document.createElement("option");
cartChoice.id = cartDb;
cartChoice.label = cartDb;
cartChoice.value = cartDb.split(" ").slice(-1);
+ 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);
choices.forEach( (e) => {
if (e === cartDb) {return;} // don't print the cart database twice
let choice = document.createElement("option");
choice.id = e;
choice.label = e;
choice.value = e.split(" ")[1];
genomeInp.appendChild(choice);
});
return genomeInp;
}
function makeTypeSelect(fileName) {
let typeInp = document.createElement("select");
@@ -334,57 +350,90 @@
choice.id = e;
choice.label = e;
choice.value = e;
typeInp.appendChild(choice);
});
return typeInp;
}
function createTypeAndDbDropdown(fileName) {
typeInp = makeTypeSelect(fileName);
genomeInp = makeGenomeSelect(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 = "";
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);
- displayList.appendChild(deleteEle.cloneNode(true));
+ 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) {
@@ -411,102 +460,92 @@
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) => {
- i.addEventListener("click", function(e) {
- // the sibling text content is the file name, which we use to
- // find all the other elements to delete
- let trashIconDiv = e.currentTarget;
- fname = trashIconDiv.nextSibling.textContent;
- document.querySelectorAll("[id^='" + fname + "']").forEach( (sib) => {
- sib.remove();
- });
- trashIconDiv.remove();
- delete uiState.toUpload[fname];
- // if there are no rows left 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";
- }
- });
- }
- });
});
}
// 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);
row.remove().draw();
}
- function deleteFile(fname, fileType) {
+ function deleteFile(fname, fileType, hubNameList) {
// 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}`);
cart.setCgi("hgHubConnect");
- cart.send({deleteFile: {fileNameList: [fname, fileType]}});
+ // a little complex, but the format is:
+ // {commandToCgi: {arg: val, ...}
+ // but we make val an object as well, becoming:
+ // {commandToCgi: {fileList: [{propertyName1: propertyVal1, ...}, {propertName2: ...}]}}
+ cart.send({deleteFile: {fileList: [{fileName: fname, fileType: fileType, hubNameList: hubNameList}]}});
cart.flush();
deleteFileFromTable(fname);
}
function deleteFileList() {
}
function addFileToHub(rowData) {
// a file has been uploaded and a hub has been created, present a modal
// to choose which hub to associate this track to
// backend wise: move the file into the hub directory
// update the hubSpace row with the hub name
// frontend wise: move the file row into a 'child' of the hub row
console.log(`sending addToHub req for ${rowData.fileName} to `);
cart.setCgi("hgHubConnect");
cart.send({addToHub: {hubName: "", dataFile: ""}});
cart.flush();
}
- function viewInGenomeBrowser(fname, ftype, genome) {
+ function viewInGenomeBrowser(fname, ftype, genome, hubName) {
// redirect to hgTracks with this track open in the hub
if (typeof uiState.userUrl !== "undefined" && uiState.userUrl.length > 0) {
if (ftype in extensionMap) {
// TODO: tusd should return this location in it's response after
// uploading a file and then we can look it up somehow, the cgi can
// write the links directly into the html directly for prev uploaded files maybe?
- window.location.assign("../cgi-bin/hgTracks?db=" + genome + "&hgt.customText=" + uiState.userUrl + fname);
+ let url = "../cgi-bin/hgTracks?hgsid=" + getHgsid() + "&db=" + genome + "&hubUrl=" + uiState.userUrl + hubName + "/hub.txt";
+ window.location.assign(url);
return false;
}
}
}
function addNewUploadedFileToTable(req) {
// req is an object with properties of an uploaded file, make a new row
// for it in the filesTable
let table = null;
if ($.fn.dataTable.isDataTable("#filesTable")) {
table = $("#filesTable").DataTable();
let newRow = table.row.add(req).order([8, 'asc']).draw().node();
$(newRow).css('color','red').animate({color: 'black'}, 1000);
} else {
showExistingFiles([req]);
@@ -544,90 +583,98 @@
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 = {
- language: {
- emptyTable: "Uploaded files will appear here. Click \"Upload\" to get started",
- },
layout: {
topStart: {
buttons: [
- {text: 'Upload',
- action: startUploadDialog},
+ {
+ text: 'Upload',
+ action: startUploadDialog,
+ enabled: false, // disable by default in case user is not logged in
+ },
]
}
},
columnDefs: [
{
orderable: false, targets: 0,
title: "",
render: function(data, type, row) {
return "";
}
},
{
orderable: false, targets: 1,
data: "action", title: "Action",
render: function(data, type, row) {
/* Return a node for rendering the actions column */
// all of our actions will be buttons in this div:
let container = document.createElement("div");
// click to call hgHubDelete file
let delBtn = document.createElement("button");
delBtn.textContent = "Delete";
delBtn.type = 'button';
delBtn.addEventListener("click", function() {
- deleteFile(row.fileName, row.fileType);
+ deleteFile(row.fileName, row.fileType, row.hub);
});
// click to view hub/file in gb:
let viewBtn = document.createElement("button");
viewBtn.textContent = "View in Genome Browser";
viewBtn.type = 'button';
viewBtn.addEventListener("click", function() {
- viewInGenomeBrowser(row.fileName, row.fileType, row.genome);
+ viewInGenomeBrowser(row.fileName, row.fileType, row.genome, row.hub);
});
// click to rename file or hub:
let renameBtn = document.createElement("button");
renameBtn.textContent = "Rename";
renameBtn.type = 'button';
renameBtn.addEventListener("click", function() {
console.log("rename btn clicked!");
});
// click to associate this track to a hub
let addToHubBtn = document.createElement("button");
addToHubBtn.textContent = "Add to hub";
addToHubBtn.type = 'button';
addToHubBtn.addEventListener("click", function() {
@@ -637,55 +684,71 @@
container.appendChild(delBtn);
container.appendChild(viewBtn);
container.appendChild(renameBtn);
container.appendChild(addToHubBtn);
return container;
}
},
{
targets: 3,
render: function(data, type, row) {
return dataTablePrintSize(data);
}
},
{
+ targets: 5,
+ render: function(data, type, row) {
+ return dataTablePrintGenome(data);
+ }
+ },
+ {
// The upload time column, not visible but we use it to sort on new uploads
targets: 8,
visible: false,
searchable: false
}
],
columns: [
{data: "", },
{data: "", },
{data: "fileName", title: "File name"},
{data: "fileSize", title: "File size", render: dataTablePrintSize},
{data: "fileType", title: "File type"},
- {data: "genome", title: "Genome"},
+ {data: "genome", title: "Genome", render: dataTablePrintGenome},
{data: "hub", title: "Hubs"},
{data: "lastModified", title: "File Last Modified"},
{data: "uploadTime", title: "Upload Time"},
],
order: [[6, 'desc']],
+ drawCallback: function(settings) {
+ if (isLoggedIn) {
+ 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"};
+ }
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,
@@ -762,43 +825,39 @@
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') {
+ if (typeof userFiles !== 'undefined' && Object.keys(userFiles).length > 0) {
uiState.fileList = userFiles.fileList;
uiState.hubList = userFiles.hubList;
uiState.userUrl = userFiles.userUrl;
}
- showExistingFiles(uiState.fileList);
- showExistingHubs(uiState.hubList);
+ showExistingFiles(uiState.fileList.filter((row) => row.fileType !== "hub"));
inputBtn.addEventListener("click", (e) => uiState.input.click());
- //uiState.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
}
$("#newTrackHubDialog").dialog({
modal: true,
autoOpen: false,
title: "Create new track hub",
closeOnEscape: true,
minWidth: 400,
minHeight: 120
});
}
return { init: init,
uiState: uiState,
};
}());