Skip to content

Commit

Permalink
Cele-32 feat: WIP - Add open group functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
afonsobspinto committed Aug 13, 2024
1 parent 998a071 commit 4ae26f2
Show file tree
Hide file tree
Showing 8 changed files with 744 additions and 1,576 deletions.
Original file line number Diff line number Diff line change
@@ -1,151 +1,179 @@
import AddIcon from "@mui/icons-material/Add";
import { Box, IconButton, Stack, Typography } from "@mui/material";
import {Box, IconButton, Stack, Typography} from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import { debounce } from "lodash";
import { useCallback, useState } from "react";
import { useGlobalContext } from "../../contexts/GlobalContext.tsx";
import type { Neuron } from "../../rest";
import { NeuronsService } from "../../rest";
import { vars } from "../../theme/variables.ts";
import {debounce} from "lodash";
import {useCallback, useState} from "react";
import {useGlobalContext} from "../../contexts/GlobalContext.tsx";
import type {Neuron} from "../../rest";
import {NeuronsService} from "../../rest";
import {vars} from "../../theme/variables.ts";
import CustomEntitiesDropdown from "./CustomEntitiesDropdown.tsx";
import CustomListItem from "./CustomListItem.tsx";
import type { EnhancedNeuron } from "../../models/models.ts";
import type {EnhancedNeuron} from "../../models/models.ts";

const { gray900, gray500 } = vars;
const {gray900, gray500} = vars;
const mapNeuronsToListItem = (neuron: string, isActive: boolean) => ({
id: neuron,
label: neuron,
checked: isActive,
id: neuron,
label: neuron,
checked: isActive,
});
const mapNeuronsAvailableNeuronsToOptions = (neuron: Neuron) => ({
id: neuron.name,
label: neuron.name,
content: [],
id: neuron.name,
label: neuron.name,
content: [],
});

const Neurons = ({ children }) => {
const { workspaces, datasets, currentWorkspaceId } = useGlobalContext();
const currentWorkspace = workspaces[currentWorkspaceId];
const activeNeurons = currentWorkspace.activeNeurons;
const recentNeurons = Object.values(currentWorkspace.availableNeurons).filter((neuron) => neuron.isInteractant);
const availableNeurons = currentWorkspace.availableNeurons;
const Neurons = ({children}) => {
const {workspaces, datasets, currentWorkspaceId} = useGlobalContext();
const currentWorkspace = workspaces[currentWorkspaceId];
const activeNeurons = currentWorkspace.activeNeurons;
const recentNeurons = Object.values(currentWorkspace.availableNeurons).filter((neuron) => neuron.isInteractant);
const availableNeurons = currentWorkspace.availableNeurons;
const groups = currentWorkspace.neuronGroups;

const [neurons, setNeurons] = useState(availableNeurons);
const [neurons, setNeurons] = useState(availableNeurons);

const handleSwitchChange = async (neuronId: string, checked: boolean) => {
const neuron = availableNeurons[neuronId];
const handleSwitchChange = async (neuronId: string, checked: boolean) => {
const neuron = availableNeurons[neuronId];

if (!neuron) return;
if (checked) {
await currentWorkspace.activateNeuron(neuron);
} else {
await currentWorkspace.deactivateNeuron(neuronId);
}
};
if (!neuron) return;
if (checked) {
await currentWorkspace.activateNeuron(neuron);
} else {
await currentWorkspace.deactivateNeuron(neuronId);
}
};

const onNeuronClick = (option) => {
const neuron = availableNeurons[option.id];
if (neuron && !activeNeurons.has(option.id)) {
currentWorkspace.activateNeuron(neuron);
} else {
currentWorkspace.deleteNeuron(option.id);
}
};
const handleDeleteNeuron = (neuronId: string) => {
currentWorkspace.deleteNeuron(neuronId);
};
const onNeuronClick = (option) => {
const neuron = availableNeurons[option.id];
if (neuron && !activeNeurons.has(option.id)) {
currentWorkspace.activateNeuron(neuron);
} else {
currentWorkspace.deleteNeuron(option.id);
}
};
const handleDeleteNeuron = (neuronId: string) => {
currentWorkspace.deleteNeuron(neuronId);
};

const fetchNeurons = async (name: string, datasetsIds: { id: string }[]) => {
try {
const ids = datasetsIds.map((dataset) => dataset.id);
const response = await NeuronsService.searchCells({ name: name, datasetIds: ids });
const fetchNeurons = async (name: string, datasetsIds: { id: string }[]) => {
try {
const ids = datasetsIds.map((dataset) => dataset.id);
const response = await NeuronsService.searchCells({name: name, datasetIds: ids});

// Convert the object to a Record<string, Neuron>
const neuronsRecord = Object.entries(response).reduce((acc: Record<string, EnhancedNeuron>, [_, neuron]: [string, EnhancedNeuron]) => {
acc[neuron.name] = neuron;
return acc;
}, {});
// Convert the object to a Record<string, Neuron>
const neuronsRecord = Object.entries(response).reduce((acc: Record<string, EnhancedNeuron>, [_, neuron]: [string, EnhancedNeuron]) => {
acc[neuron.name] = neuron;
return acc;
}, {});

setNeurons(neuronsRecord);
} catch (error) {
console.error("Failed to fetch datasets", error);
}
};
setNeurons(neuronsRecord);
} catch (error) {
console.error("Failed to fetch datasets", error);
}
};

const debouncedFetchNeurons = useCallback(debounce(fetchNeurons, 300), []);
const debouncedFetchNeurons = useCallback(debounce(fetchNeurons, 300), []);

const onSearchNeurons = (value) => {
const datasetsIds = Object.keys(datasets);
debouncedFetchNeurons(value, datasetsIds);
};
const onSearchNeurons = (value) => {
const datasetsIds = Object.keys(datasets);
debouncedFetchNeurons(value, datasetsIds);
};

const autoCompleteOptions = Object.values(neurons).map((neuron: Neuron) => mapNeuronsAvailableNeuronsToOptions(neuron));
const autoCompleteOptions = Object.values(neurons).map((neuron: Neuron) => mapNeuronsAvailableNeuronsToOptions(neuron));

return (
<Box
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
>
<Stack spacing=".25rem" p=".75rem" mb="1.5rem" pb="0">
<Typography variant="body1" component="p" color={gray900} fontWeight={500}>
Neurons
</Typography>
return (
<Box
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
>
<Stack spacing=".25rem" p=".75rem" mb="1.5rem" pb="0">
<Typography variant="body1" component="p" color={gray900} fontWeight={500}>
Neurons
</Typography>

<Typography variant="body1" component="p" color={gray500}>
Search for the neurons and add it to your workspace. This will affect all viewers.
</Typography>
</Stack>
{children}
<CustomEntitiesDropdown
options={autoCompleteOptions}
activeNeurons={activeNeurons}
onNeuronClick={onNeuronClick}
onSearchNeurons={onSearchNeurons}
setNeurons={setNeurons}
availableNeurons={availableNeurons}
/>
<Box
sx={{
height: "100%",
overflow: "auto",
flex: 1,
}}
>
<Stack spacing=".5rem" p="0 .25rem" mt=".75rem">
<Box display="flex" alignItems="center" justifyContent="space-between" padding=".25rem .5rem">
<Typography color={gray500} variant="subtitle1">
All Neurons
</Typography>
<Tooltip title="Create new group">
<IconButton
<Typography variant="body1" component="p" color={gray500}>
Search for the neurons and add it to your workspace. This will affect all viewers.
</Typography>
</Stack>
{children}
<CustomEntitiesDropdown
options={autoCompleteOptions}
activeNeurons={activeNeurons}
onNeuronClick={onNeuronClick}
onSearchNeurons={onSearchNeurons}
setNeurons={setNeurons}
availableNeurons={availableNeurons}
/>
<Box
sx={{
padding: ".25rem",
borderRadius: ".25rem",
height: "100%",
overflow: "auto",
flex: 1,
}}
>
<AddIcon fontSize="medium" />
</IconButton>
</Tooltip>
</Box>
{Array.from(recentNeurons).map((neuron) => (
<CustomListItem
key={neuron.name}
data={mapNeuronsToListItem(neuron.name, activeNeurons.has(neuron.name))}
showTooltip={false}
showExtraActions={true}
listType="neurons"
onSwitchChange={handleSwitchChange}
onDelete={handleDeleteNeuron}
deleteTooltipTitle="Remove neuron from the workspace"
/>
))}
</Stack>
</Box>
</Box>
);
>
<Stack spacing=".5rem" p="0 .25rem" mt=".75rem">
<Box display="flex" alignItems="center" justifyContent="space-between" padding=".25rem .5rem">
<Typography color={gray500} variant="subtitle1">
All Neurons
</Typography>
<Tooltip title="Create new group">
<IconButton
sx={{
padding: ".25rem",
borderRadius: ".25rem",
}}
>
<AddIcon fontSize="medium"/>
</IconButton>
</Tooltip>
</Box>
{Array.from(recentNeurons).map((neuron) => (
<CustomListItem
key={neuron.name}
data={mapNeuronsToListItem(neuron.name, activeNeurons.has(neuron.name))}
showTooltip={false}
showExtraActions={true}
listType="neurons"
onSwitchChange={handleSwitchChange}
onDelete={handleDeleteNeuron}
deleteTooltipTitle="Remove neuron from the workspace"
/>
))}
<Box display="flex" alignItems="center" justifyContent="space-between" padding=".25rem .5rem">
<Typography color={gray500} variant="subtitle1">
All Groups
</Typography>
<Tooltip title="Create new group">
<IconButton
sx={{
padding: ".25rem",
borderRadius: ".25rem",
}}
>
<AddIcon fontSize="medium"/>
</IconButton>
</Tooltip>
</Box>
{Array.from(Object.keys(groups)).map((groupId) => (
<CustomListItem
key={groupId}
data={mapNeuronsToListItem(groupId, activeNeurons.has(groupId))}
showTooltip={false}
showExtraActions={true}
listType="groups"
onSwitchChange={() => console.log("switch")}
onDelete={() => console.log("delete")}
deleteTooltipTitle="Remove group from the workspace"
/>
))}
</Stack>
</Box>
</Box>
);
};

export default Neurons;
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
};

const handleUngroup = () => {
const groupsToRemoveFromOpen = new Set<string>();

workspace.customUpdate((draft) => {
const nextSelected = new Set<string>();
for (const elementId of draft.selectedNeurons) {
Expand All @@ -132,10 +134,20 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
nextSelected.add(groupedNeuronId);
}
delete draft.neuronGroups[elementId];
if (openGroups.has(elementId)) {
groupsToRemoveFromOpen.add(elementId);
}
}
}
draft.selectedNeurons = nextSelected;
});

// Remove groups from the openGroups set
setOpenGroups((prevOpenGroups: Set<string>) => {
const updatedOpenGroups = new Set<string>(prevOpenGroups);
groupsToRemoveFromOpen.forEach((groupId) => updatedOpenGroups.delete(groupId));
return updatedOpenGroups;
});
onClose();
};

Expand Down Expand Up @@ -280,7 +292,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
workspace.selectedNeurons.forEach((neuronId) => {
if (workspace.neuronGroups[neuronId] && !openGroups.has(neuronId)) {
// Mark the group as open
setOpenGroups((prevOpenGroups: Set<string>) => {
setOpenGroups((prevOpenGroups: Set<string>) => {
const updatedOpenGroups = new Set<string>(prevOpenGroups);
updatedOpenGroups.add(neuronId);
return updatedOpenGroups;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
INCLUDE_ANNOTATIONS,
INCLUDE_NEIGHBORING_CELLS,
INCLUDE_LABELS,
INCLUDE_POST_EMBRYONIC,
INCLUDE_POST_EMBRYONIC, BOUNDING_BOX_BASENAME,
} from "../../../settings/twoDSettings";
import TwoDMenu from "./TwoDMenu";
import TwoDLegend from "./TwoDLegend";
Expand Down Expand Up @@ -129,7 +129,7 @@ const TwoDViewer = () => {
if (cyRef.current) {
updateGraphElements(cyRef.current, connections);
}
}, [connections, hiddenNodes, workspace.neuronGroups, includePostEmbryonic, splitJoinState]);
}, [connections, hiddenNodes, workspace.neuronGroups, includePostEmbryonic, splitJoinState, openGroups]);

useEffect(() => {
if (cyRef.current) {
Expand Down Expand Up @@ -276,6 +276,7 @@ const TwoDViewer = () => {
workspace,
splitJoinState,
hiddenNodes,
openGroups,
includeNeighboringCellsAsIndividualCells,
includeAnnotations,
includePostEmbryonic,
Expand Down Expand Up @@ -306,7 +307,12 @@ const TwoDViewer = () => {
}
cyRef.current.nodes().forEach((node) => {
const nodeId = node.id();
// Skip bounding box nodes
if (nodeId.startsWith(BOUNDING_BOX_BASENAME)) {
return;
}
const group = workspace.neuronGroups[nodeId];

let colors = [];

if (group) {
Expand Down
Loading

0 comments on commit 4ae26f2

Please sign in to comment.