Skip to content

Commit

Permalink
wip: UI updates for content selector and entity overview
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobiClark committed Feb 11, 2025
1 parent 9bfcf78 commit 20f5e9b
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 87 deletions.
193 changes: 108 additions & 85 deletions src/renderer/src/components/shared/DatasetContentSelector/index.jsx
Original file line number Diff line number Diff line change
@@ -1,147 +1,170 @@
import { Card, Stack, Text, Group, Tooltip, Checkbox } from "@mantine/core";
import { useState, useCallback } from "react";
import { Stack, Text, Group, Tooltip, Checkbox, Collapse, ActionIcon } from "@mantine/core";
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
import FullWidthContainer from "../../containers/FullWidthContainer";
import useGlobalStore from "../../../stores/globalStore";
import { toggleEntitySelection } from "../../../stores/slices/datasetContentSelectorSlice";

const contentOptionsMap = {
subjects: {
label: "I collected data from subjects",
description:
"Subjects are individual entities, such as humans, animals, or other biological specimens from which data was collected during the study.",
ml: "0px",
description: "Subjects are humans, animals, or other biological specimens.",
ml: 0,
},
samples: {
label: "I collected data from samples",
label: "I collected samples from my subjects",
description:
"Samples are biological or physical specimens collected from subjects, such as tissue samples, blood samples, or other biological materials.",
"Samples are biological or physical specimens like tissue or blood taken from subjects",
dependsOn: ["subjects"],
ml: "20px",
dependsOnNotSatiatedMessage: "You must indicate that you collected data from subjects first.",
ml: 20,
},
sites: {
label: "I collected data from multiple distinct physical sites on subjects or samples.",
description:
"For example, if you collected data from multiple brain regions, different sections of a tissue sample, or distinct parts of an organ.",
dependsOn: ["subjects"],
ml: "20px",
dependsOnNotSatiatedMessage: "You must indicate that you collected data from subjects first.",
ml: 20,
},
"subject-sites": {
label: "I collected data from distinct physical sites of subjects.",
label: "I collected data from distinct physical sites on subjects.",
description:
"Select this option if the sites where data was collected correspond to specific locations or regions within the subjects, such as different anatomical regions or organs.",
dependsOn: ["subjects", "samples", "sites"],
ml: "40px",
dependsOnNotSatiatedMessage:
"You must indicate that you collected data from subjects, samples, and sites first.",
ml: 40,
},
"sample-sites": {
label: "I collected data from distinct physical sites of samples.",
label: "I collected data from distinct physical sites on samples.",
description:
"Select this option if the sites where data was collected correspond to specific regions within the samples, such as different sections of tissue or other biological materials.",
dependsOn: ["subjects", "samples", "sites"],
ml: "40px",
dependsOnNotSatiatedMessage:
"You must indicate that you collected data from subjects, samples, and sites first.",
ml: 40,
},
performances: {
label: "I collected data from multiple performances of the same protocol.",
description:
"Performances refer to the repeated execution of the same protocol or procedure during the study. This can involve tasks or experiments conducted multiple times on the subjects or samples.",
"Select this option if you repeated the same protocol or procedure multiple times (such as running repeated tests or experiments) and collected data from each repetition.",
dependsOn: ["subjects"],
ml: "20px",
dependsOnNotSatiatedMessage: "You must indicate that you collected data from subjects first.",
ml: 20,
},
"performances-on-subjects": {
label: "The protocol performances were run on the subjects.",
description:
"Select this option if the protocol performances (such as tasks, tests, or procedures) were conducted on subjects, such as humans or animals, and data was collected during those performances.",
"Choose this if tasks, tests, or procedures were performed directly on subjects (e.g., humans or animals) and data was collected during these sessions.",
dependsOn: ["subjects", "samples", "performances"],
ml: "40px",
dependsOnNotSatiatedMessage:
"You must indicate that you collected data from subjects, samples, and performances first.",
ml: 40,
},
"performances-on-samples": {
label: "The protocol performances were run on the samples.",
description:
"Select this option if the protocol performances (such as processing or testing procedures) were conducted on samples, like tissue or biological specimens, and data was collected during those procedures.",
"Choose this if tasks, tests, or procedures were performed directly on samples (e.g., tissues or blood) and data was collected during these sessions.",
dependsOn: ["subjects", "samples", "performances"],
ml: "40px",
dependsOnNotSatiatedMessage:
"You must indicate that you collected data from subjects, samples, and performances first.",
ml: 40,
},
code: {
label: "I used code to generate or analyze the collected data",
description:
"Code includes scripts, computational models, analysis pipelines, or other software used to generate, process, or analyze the data.",
ml: "0px",
ml: 0,
},
};

const DatasetContentSelector = () => {
const selectedEntities = useGlobalStore((state) => state.selectedEntities);

const handleEntitySelection = (value) => {
const isSelected = selectedEntities.includes(value);
// State to track which option descriptions are expanded.
const [expanded, setExpanded] = useState({});

const toggleExpanded = (key) => {
setExpanded((prev) => ({ ...prev, [key]: !prev[key] }));
};

const handleEntitySelection = useCallback(
(value) => {
const isSelected = selectedEntities.includes(value);

if (isSelected) {
Object.keys(contentOptionsMap).forEach((key) => {
if (contentOptionsMap[key].dependsOn?.includes(value) && selectedEntities.includes(key)) {
toggleEntitySelection(key);
}
});
}
if (isSelected) {
Object.entries(contentOptionsMap).forEach(([key, option]) => {
if (option.dependsOn?.includes(value) && selectedEntities.includes(key)) {
toggleEntitySelection(key);
}
});
}

toggleEntitySelection(value);
toggleEntitySelection(value);
},
[selectedEntities]
);

const renderOption = (key) => {
const option = contentOptionsMap[key];
const isDisabled = option.dependsOn?.some((dep) => !selectedEntities.includes(dep));
const isSelected = selectedEntities.includes(key) && !isDisabled;

return (
<Tooltip
key={key}
label={isDisabled ? option.dependsOnNotSatiatedMessage : ""}
disabled={!isDisabled}
zIndex={2999}
>
<div
style={{
display: "flex",
flexDirection: "column",
marginLeft: option.ml,
borderRadius: "4px",
border: "1px solid #ddd",
padding: "8px",
}}
>
<Group position="apart" align="center">
<Group position="left">
<Checkbox
checked={isSelected}
disabled={isDisabled}
onChange={() => !isDisabled && handleEntitySelection(key)}
/>
<Text fw={600} size="md" style={{ color: isDisabled ? "#aaa" : "inherit" }}>
{option.label}
</Text>
</Group>
{option.description && (
<Tooltip label={expanded[key] ? "Hide details" : "Read more"}>
<ActionIcon
onClick={() => toggleExpanded(key)}
aria-label={expanded[key] ? "Hide details" : "Read more"}
>
{expanded[key] ? <IconChevronUp size={16} /> : <IconChevronDown size={16} />}
</ActionIcon>
</Tooltip>
)}
</Group>
{option.description && (
<Collapse in={expanded[key]}>
<Text size="sm" color="dimmed" mt="xs">
{option.description}
</Text>
</Collapse>
)}
</div>
</Tooltip>
);
};

return (
<FullWidthContainer>
<Stack spacing="md">
{Object.keys(contentOptionsMap).map((key) => {
const option = contentOptionsMap[key];
const isDisabled = option.dependsOn?.some((dep) => !selectedEntities.includes(dep));
const isSelected = selectedEntities.includes(key) && !isDisabled;

return (
<Tooltip
key={key}
label={
isDisabled
? `${option.dependsOn
.map((dep) => contentOptionsMap[dep].label)
.join(" and ")} must be selected first.`
: ""
}
disabled={!isDisabled}
zIndex={2999}
>
<Card
withBorder
shadow="sm"
padding="lg"
ml={option.ml}
style={{
display: isDisabled ? "none" : "block",
opacity: isDisabled ? 0.6 : 1,
cursor: isDisabled ? "not-allowed" : "pointer",
backgroundColor: isSelected ? "#e8f5e9" : "white",
borderColor: isSelected ? "var(--color-light-green)" : "#e0e0e0",
borderWidth: isSelected ? 2 : 1,
borderStyle: "solid",
}}
onClick={() => !isDisabled && handleEntitySelection(key)}
>
<Group position="apart" align="center">
<Checkbox
checked={isSelected}
onChange={() => !isDisabled && handleEntitySelection(key)}
style={{ cursor: isDisabled ? "not-allowed" : "pointer" }}
onClick={(event) => event.stopPropagation()}
/>
<Text fw={600} size="lg">
{option.label}
</Text>
</Group>
{option.description && (
<Text size="sm" mt="xs">
{option.description}
</Text>
)}
</Card>
</Tooltip>
);
})}
</Stack>
<Stack spacing="sm">{Object.keys(contentOptionsMap).map((key) => renderOption(key))}</Stack>
</FullWidthContainer>
);
};
Expand Down
53 changes: 52 additions & 1 deletion src/renderer/src/scripts/guided-mode/guided-curate-dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -2661,12 +2661,13 @@ const guidedTransitionToHome = () => {
}
window.CURRENT_PAGE = undefined;

document.getElementById("guided-header-div").classList.add("hidden");
document.getElementById("guided-footer-div").classList.add("hidden");
};

const guidedSaveProgress = async () => {
const guidedProgressFileName = window.sodaJSONObj?.["digital-metadata"]?.["name"];
//return if guidedProgressFileName is not a strnig greater than 0
//return if guidedProgressFileName is not a string greater than 0
if (
!guidedProgressFileName ||
typeof guidedProgressFileName !== "string" ||
Expand Down Expand Up @@ -4638,6 +4639,56 @@ window.openPage = async (targetPageID) => {
}

if (targetPageID === "guided-unstructured-data-import-tab") {
if (0 === 1) {
const responseObject = await client.get(`manage_datasets/bf_dataset_account`, {
params: {
selected_account: window.defaultBfAccount,
},
});
const datasets = responseObject.data.datasets;
console.log("Got datasets", datasets);

let fieldEntries = [];
for (const field of $("#guided-form-add-a-subject")
.children()
.find(".subjects-form-entry")) {
fieldEntries.push(field.name.toLowerCase());
}

const foundIDs = [];

for (const dataset of datasets) {
try {
const subjectsMetadataResponse = await client.get(
`/prepare_metadata/import_metadata_file`,
{
params: {
selected_account: window.defaultBfAccount,
selected_dataset: dataset.id,
file_type: "subjects.xlsx",
ui_fields: fieldEntries.toString(),
},
}
);
const subjectFileRows = subjectsMetadataResponse.data.subject_file_rows;
// Get the first element of each row and add it to a new row
const newRows = [];
for (const row of subjectFileRows) {
newRows.push(row[0]);
}
foundIDs.push({
dataset: dataset,
rows: newRows,
});

console.log("subjectsMetadataResponse", subjectsMetadataResponse);
} catch (error) {
console.error("Error fetching subjects metadata", error);
}
}
console.log("Found IDs", foundIDs);
}

guidedUpdateFolderStructureUI("data/");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,8 @@ <h2 class="text-sub-step-title mb-3 step-before-spreadsheet-path-declared">
<h1 class="text-sub-step-title">Describe the content of your dataset</h1>
<div class="guided--section">
<p class="help-text">
Select all of the following that apply to the data you collected:
Check the boxes that apply to the data collected or generated during your study in
order to help SODA determine the optimal workflow to organize your dataset.
</p>
</div>
<div data-component-type="dataset-content-selector" class="guided--section"></div>
Expand Down

0 comments on commit 20f5e9b

Please sign in to comment.