Skip to content

Commit

Permalink
changing weighted gene cart previews
Browse files Browse the repository at this point in the history
  • Loading branch information
adkinsrs committed Nov 29, 2023
1 parent 9690b38 commit a196dd9
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 25 deletions.
3 changes: 3 additions & 0 deletions www/cgi/get_weighted_gene_cart_preview.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def main():
# Reset index so that all column levels are the same. Numerical index is later dropped for displaying.
df = pd.concat([adata.var, df1], axis=1).reset_index()[:ROWS_TO_SHOW]

result["num_genes"] = len(adata.var)
result["weights"] = adata.obs.index.tolist()

result['preview_json'] = df.to_html(classes=['weighted-list'], index=False)
result['success'] = 1
print(json.dumps(result))
Expand Down
4 changes: 4 additions & 0 deletions www/cgi/save_new_genecart_form.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ def main():
except Exception as e:
print(str(e))
sys.exit(1)
elif upload_type == "labeled-list":
raise("Not implemented")
else:
raise Exception("Invalid upload type: {0}".format(upload_type))

gc.save()

Expand Down
3 changes: 3 additions & 0 deletions www/cgi/save_new_genecart_json.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def main():
print(str(e))
sys.exit(1)

elif gc.gctype == 'labeled-list':
raise("Not implemented")

try:
gc.save()
except Exception as e:
Expand Down
7 changes: 7 additions & 0 deletions www/css/gene_collection_manager.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ hr.gc-list-element-divider {
border-radius: 4px;
}

/* Toast-style notifications for actual alerts */
.notification.js-toast {
position: fixed;
top: 30px;
right: 10px;
z-index:999;
}

/* CSS for Floating UI components */

Expand Down
3 changes: 2 additions & 1 deletion www/gene_collection_manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,13 @@
</header>
<div class="card-content is-flex-grow-1">
<div class="content has-text-dark">
<p class="is-subtitle has-text-weight-semibold">COMING SOON</p>
Upload a file containing a list of genes and associated labels, for example marker genes for cell types
</div>
</div>
<footer class="card-footer">
<div class="buttons card-footer-item">
<button class="button is-primary js-upload-gc-btn" id="btn_gc_upload_labeled_list">Upload file</button>
<button class="button is-primary js-upload-gc-btn" id="btn_gc_upload_labeled_list" disabled>Upload file</button>
</div>
</footer>
</div>
Expand Down
8 changes: 8 additions & 0 deletions www/js/classes/genecart.v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ class WeightedGeneCart extends GeneCart {
}
}

class LabeledGeneCart extends GeneCart {
constructor ({...args} = {}, labels) {
super(args);
this.labels = labels || [];
this.genecarts = [];
}
}

142 changes: 118 additions & 24 deletions www/js/gene_collection_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const addGeneCollectionEventListeners = () => {
button.classList.remove("is-outlined");

// If the preview table already exists, just show it
if (document.querySelector(`#gc_${gcId}_gene_table > table`)) {
if (document.querySelector(`#gc_${gcId}_gene_info > .js-info-container`)) {
geneCollectionIdContainer.classList.remove("is-hidden"); // TODO: add animate CSS with fade in

button.querySelector("i").classList.add("mdi-eye-off");
Expand All @@ -80,10 +80,25 @@ const addGeneCollectionEventListeners = () => {
'share_id': shareId
}));

// This creates a table with classes dataframe and weighted-list
document.getElementById(`gc_${gcId}_gene_table`).innerHTML = data['preview_json'];
// Add Bulma table class
document.querySelector(`#gc_${gcId}_gene_table > table`).classList.add("table");
// List the number of genes as well as the names of weights in the gene collection
const geneCount = data['num_genes'];
const weights = data['weights'];

const infoContainer = document.createElement("div");
infoContainer.classList.add("js-info-container");
document.getElementById(`gc_${gcId}_gene_info`).appendChild(infoContainer);

const geneCountElt = document.createElement("p");
geneCountElt.innerHTML = `<span class="has-text-weight-semibold">Genes:</span> ${geneCount}`;
infoContainer.appendChild(geneCountElt);
const numWeightsElt = document.createElement("p");
numWeightsElt.innerHTML = `<span class="has-text-weight-semibold">Weights:</span> ${weights.length}`;
infoContainer.appendChild(numWeightsElt);

const weightsElt = document.createElement("p");
weightsElt.innerHTML = `<span class="has-text-weight-semibold">Weight names:</span> ${weights.join(", ")}`;
infoContainer.appendChild(weightsElt);

geneCollectionIdContainer.classList.remove("is-hidden"); // TODO: add animate CSS with fade in

button.querySelector("i").classList.add("mdi-eye-off");
Expand All @@ -92,7 +107,7 @@ const addGeneCollectionEventListeners = () => {

} catch (error) {
logErrorInConsole(error);
// TODO: Display error notification
createToast("Failed to load gene collection preview")
} finally {
button.classList.remove("is-loading");
}
Expand Down Expand Up @@ -137,7 +152,7 @@ const addGeneCollectionEventListeners = () => {

if (gctype == "unweighted-list") {
// Take the list elements, separate them, and put them in a text file
const geneListElt = document.getElementById(`gc_${gcId}_gene_tags`);
const geneListElt = document.getElementById(`gc_${gcId}_gene_info`);
const fileContents = Array.from(geneListElt.children).map(tag => tag.innerText).join("\n");

const element = document.createElement("a");
Expand Down Expand Up @@ -272,9 +287,11 @@ const addGeneCollectionEventListeners = () => {
// Put interface back to view mode.
toggleEditableMode(true, selectorBase);

createToast("Gene collection changes saved", "is-success");

} catch (error) {
logErrorInConsole(error);
// TODO: Display error notification
createToast("Failed to save gene collection changes");
}
});
}
Expand All @@ -290,6 +307,12 @@ const addGeneCollectionEventListeners = () => {
const editableOrganismIdElt = document.querySelector(`${selectorBase}_editable_organism_id`);
editableOrganismIdElt.innerHTML = document.getElementById("new_collection_organism_id").innerHTML;

// Add default "select an organism" option
const defaultOption = document.createElement("option");
defaultOption.value = "";
defaultOption.innerText = "Select an organism";
editableOrganismIdElt.prepend(defaultOption);

// set the current value as selected
editableOrganismIdElt.value = editableOrganismIdElt.dataset.originalVal;

Expand Down Expand Up @@ -335,21 +358,21 @@ const addGeneListToGeneCollection = (geneCollectionId, gctype, genes) => {
const geneCollectionIdContainer = document.getElementById(`${geneCollectionId}_gene_container`);
if (gctype == "weighted-list") {
const weightedGeneListContainer = document.createElement("div");
weightedGeneListContainer.id = `gc_${geneCollectionId}_gene_table`;
weightedGeneListContainer.id = `gc_${geneCollectionId}_gene_info`;
geneCollectionIdContainer.appendChild(weightedGeneListContainer);
return;
} else if (gctype == "labeled-list") {
return;
};
const geneListContainer = document.createElement("div");
geneListContainer.id = `gc_${geneCollectionId}_gene_tags`;
geneListContainer.classList.add("tags", "are-medium");
geneListContainer.id = `gc_${geneCollectionId}_gene_info`;
geneListContainer.classList.add("tags");
geneCollectionIdContainer.appendChild(geneListContainer);
// append genes to gene collection
const geneCollectionIdGeneUlElt = document.getElementById(geneListContainer.id);
for (const gene of genes) {
const tag = document.createElement("span");
tag.classList.add("tag", "is-dark");
tag.classList.add("tag", "is-gear-bg-secondary");
tag.innerText = gene;
geneCollectionIdGeneUlElt.appendChild(tag);
}
Expand All @@ -375,7 +398,7 @@ const addPreviewGenesToGeneCollection = (geneCollectionId, gctype, shareId, gene
button.classList.add("js-gc-weighted-gene-list-toggle");
const elt = document.createElement("span");
elt.id = `btn_gc_${geneCollectionId}_text`;
elt.innerText = `Preview`;
elt.innerText = `Info`;
elt.dataset.offState = elt.textContent
button.append(elt);
} else if (gctype === "unweighted-list") {
Expand Down Expand Up @@ -593,6 +616,8 @@ const createDeleteConfirmationPopover = () => {
resultElement.style.opacity = 0;
resultElement.remove();

createToast("Gene collection deleted", "is-success");

// This can affect page counts, so we need to re-run the search
submitSearch();

Expand All @@ -601,7 +626,7 @@ const createDeleteConfirmationPopover = () => {
}
} catch (error) {
logErrorInConsole(error);
// TODO: Display error notification
createToast("Failed to delete gene collection");
} finally {
popoverContent.remove();
}
Expand Down Expand Up @@ -665,13 +690,64 @@ const createActionTooltips = (referenceElement) => {
// Callbacks after attempting to save a gene collection
const geneCollectionFailure = (gc, message) => {
logErrorInConsole(message);
createToast("Failed to save gene collection");
}

const geneCollectionSaved = (gc) => {
document.getElementById("create_new_gene_collection").click(); // resets form also
createToast("Gene collection saved", "is-success");
submitSearch();
}

/**
* Creates a toast notification with the given message and level class.
* @param {string} msg - The message to display in the toast notification.
* @param {string} [levelClass="is-danger"] - The level class for the toast notification. Defaults to "is-danger".
*/
const createToast = (msg, levelClass="is-danger") => {
const template = `
<div class="notification js-toast ${levelClass} animate__animated animate__fadeInUp animate__faster">
<button class="delete"></button>
${msg}
</div>
`
const html = generateElements(template);

const numToasts = document.querySelectorAll(".js-toast.notification").length;

if (document.querySelector(".js-toast.notification")) {
// If .js-toast notifications are present, append under final notification
// This is to prevent overlapping toast notifications
document.querySelector(".js-toast.notification:last-of-type").insertAdjacentElement("afterend", html);
// Position new toast under previous toast with CSS
html.style.setProperty("top", `${(numToasts * 70) + 30}px`);
} else {
// Otherwise prepend to top of main content
document.getElementById("main_c").prepend(html);
}

// This should get the newly added notification since it is now the first
html.querySelector(".js-toast.notification .delete").addEventListener("click", (event) => {
const notification = event.target.closest(".js-toast.notification");
notification.remove(notification);
});

// For a success message, remove it after 3 seconds
if (levelClass === "is-success") {
const notification = document.querySelector(".js-toast.notification:last-of-type");
notification.classList.remove("animate__fadeInUp");
notification.classList.remove("animate__faster");
notification.classList.add("animate__fadeOutDown");
notification.classList.add("animate__slower");
}

// remove the toast
html.addEventListener("animationend", (event) => {
if (event.animationName === "fadeOutDown") {
event.target.remove();
}
});
}

/**
* Loads the list of organisms from the server and populates the organism choices and new cart organism ID select elements.
Expand All @@ -698,7 +774,7 @@ const loadOrganismList = async () => {
}
} catch (error) {
logErrorInConsole(error);
// TODO: Display error notification
createToast("Failed to load organism list");
}
}

Expand Down Expand Up @@ -1017,6 +1093,10 @@ const resetAddForm = () => {
document.getElementById("new_collection_pasted_genes_c").classList.add("is-hidden");
document.getElementById("new_collection_file_name").classList.add("is-hidden");

for (const classElt of document.getElementsByClassName("js-validation-help")) {
classElt.remove();
}

isAddFormOpen = false;
}

Expand Down Expand Up @@ -1144,7 +1224,7 @@ const submitSearch = async (page) => {
setupPagination(data.pagination);
} catch (error) {
logErrorInConsole(error);
// TODO: Display error notification
createToast("Failed to search gene collections");
}
}

Expand Down Expand Up @@ -1237,7 +1317,7 @@ const handlePageSpecificLoginUIUpdates = async (event) => {

// validate that #new_collection_label input has a value
document.getElementById("new_collection_label").addEventListener("blur", (e) => {
e.target.classList.remove("is-danger-dark");
e.target.classList.remove("is-danger");
// Remove small helper text under input
const helperText = e.target.parentElement.querySelector("p.help");
if (helperText) {
Expand All @@ -1247,10 +1327,10 @@ document.getElementById("new_collection_label").addEventListener("blur", (e) =>
if (e.target.value) {
return;
}
e.target.classList.add("is-danger-dark");
e.target.classList.add("is-danger");
// Add small helper text under input
const newHelperElt = document.createElement("p");
newHelperElt.classList.add("help", "is-danger-dark");
newHelperElt.classList.add("help", "has-text-danger-dark", "js-validation-help");
newHelperElt.innerText = "Please enter a value";
e.target.parentElement.appendChild(newHelperElt);
});
Expand Down Expand Up @@ -1385,18 +1465,32 @@ btnNewCartSave.addEventListener("click", (e) => {
// check required fields
const newCartLabel = document.getElementById("new_collection_label");
if (! newCartLabel.value) {
newCartLabel.classList.add("is-danger-dark");
newCartLabel.classList.add("is-danger");
// Add small helper text under input
const newHelperElt = document.createElement("p");
newHelperElt.classList.add("help", "is-danger-dark");
newHelperElt.classList.add("help", "has-text-danger-dark", "js-validation-help");
newHelperElt.innerText = "Please enter a value";
newCartLabel.parentElement.appendChild(newHelperElt);

btnNewCartSave.classList.remove("is-loading");
return;
}

newCartLabel.classList.remove("is-danger-dark");
const newCartOrganism = document.getElementById("new_collection_organism_id");
if (! newCartOrganism.value) {
newCartOrganism.classList.add("is-danger");
// Add small helper text under input
const newHelperElt = document.createElement("p");
newHelperElt.classList.add("help", "has-text-danger-dark", "js-validation-help");
newHelperElt.innerText = "Please select an organism";
newCartOrganism.parentElement.appendChild(newHelperElt);

btnNewCartSave.classList.remove("is-loading");
return;
}

newCartLabel.classList.remove("is-danger");
newCartOrganism.classList.remove("is-danger");
// Remove small helper text under input
const helperText = newCartLabel.parentElement.querySelector("p.help");
if (helperText) {
Expand All @@ -1407,8 +1501,8 @@ btnNewCartSave.addEventListener("click", (e) => {
const isPublic = document.getElementById("new_collection_visibility").checked ? 1 : 0;

const payload = {
'new_cart_label': document.getElementById("new_collection_label").value
, 'new_cart_organism_id': document.getElementById("new_collection_organism_id").value
'new_cart_label': newCartLabel.value
, 'new_cart_organism_id': newCartOrganism.value
, 'new_cart_ldesc': document.getElementById("new_collection_ldesc").value
, 'is_public': isPublic
, 'session_id': CURRENT_USER.session_id
Expand Down

0 comments on commit a196dd9

Please sign in to comment.