diff --git a/applications/visualizer/frontend/src/components/viewers/ThreeD/STLMesh.tsx b/applications/visualizer/frontend/src/components/viewers/ThreeD/STLMesh.tsx index b81d53d2..bd5a36c8 100644 --- a/applications/visualizer/frontend/src/components/viewers/ThreeD/STLMesh.tsx +++ b/applications/visualizer/frontend/src/components/viewers/ThreeD/STLMesh.tsx @@ -7,6 +7,7 @@ import { useGlobalContext } from "../../../contexts/GlobalContext"; import { getFurthestIntersectedObject } from "../../../helpers/threeDHelpers"; import type { RootState } from "../../../layout-manager/layoutManagerFactory"; import type { Workspace } from "../../../models"; +import { ViewerType } from "../../../models"; import { OUTLINE_COLOR, OUTLINE_THICKNESS } from "../../../settings/threeDSettings"; interface Props { @@ -22,14 +23,21 @@ const STLMesh: FC = ({ id, color, opacity, renderOrder, isWireframe, stl const { workspaces } = useGlobalContext(); const workspaceId = useSelector((state: RootState) => state.workspaceId); const workspace: Workspace = workspaces[workspaceId]; + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); + const isSelected = selectedNeurons.includes(id); + const onClick = (event: ThreeEvent) => { const clicked = getFurthestIntersectedObject(event); + const { id } = clicked.userData; if (clicked) { - workspace.toggleSelectedNeuron(clicked.userData.id); + if (isSelected) { + console.log(`Neurons selected: ${id}`); + } else { + console.log(`Neurons un selected: ${id}`); + } } }; - const isSelected = id in workspace.selectedNeurons; return ( diff --git a/applications/visualizer/frontend/src/components/viewers/TwoD/ContextMenu.tsx b/applications/visualizer/frontend/src/components/viewers/TwoD/ContextMenu.tsx index f1fabc9f..537fbc47 100644 --- a/applications/visualizer/frontend/src/components/viewers/TwoD/ContextMenu.tsx +++ b/applications/visualizer/frontend/src/components/viewers/TwoD/ContextMenu.tsx @@ -38,6 +38,7 @@ interface ContextMenuProps { const ContextMenu: React.FC = ({ open, onClose, position, setSplitJoinState, openGroups, setOpenGroups, cy }) => { const workspace = useSelectedWorkspace(); const [submenuAnchorEl, setSubmenuAnchorEl] = useState(null); + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); const submenuOpen = Boolean(submenuAnchorEl); @@ -55,14 +56,14 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS }; const handleAlignOption = (option: Alignment) => { - alignNeurons(option, Array.from(workspace.selectedNeurons), cy); + alignNeurons(option, selectedNeurons, cy); setSubmenuAnchorEl(null); onClose(); setSubmenuAnchorEl(null); }; const handleDistributeOption = (option: Alignment) => { - distributeNeurons(option, Array.from(workspace.selectedNeurons), cy); + distributeNeurons(option, selectedNeurons, cy); setSubmenuAnchorEl(null); onClose(); }; @@ -75,13 +76,14 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS draft.visibilities[neuronId][ViewerType.Graph].visibility = Visibility.Hidden; } } - draft.selectedNeurons.clear(); + draft.clearSelection(ViewerType.Graph); }); onClose(); }; const handleGroup = () => { - const { newGroupId, newGroup, groupsToDelete } = groupNeurons(workspace.selectedNeurons, workspace); + const selectedNeuronsSet = new Set(selectedNeurons); + const { newGroupId, newGroup, groupsToDelete } = groupNeurons(selectedNeuronsSet, workspace); workspace.customUpdate((draft) => { // Add the new group @@ -95,8 +97,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS } // Clear the current selection and select the new group - draft.selectedNeurons.clear(); - draft.selectedNeurons.add(newGroupId); + draft.setSelection([newGroupId], ViewerType.Graph); }); setOpenGroups((prevOpenGroups: Set) => { @@ -126,7 +127,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS workspace.customUpdate((draft) => { const nextSelected = new Set(); - for (const elementId of draft.selectedNeurons) { + for (const elementId of selectedNeurons) { if (draft.neuronGroups[elementId]) { // Handle the case where the selected element is a group const group = draft.neuronGroups[elementId]; @@ -158,7 +159,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS } } - draft.selectedNeurons = nextSelected; + draft.setSelection(Array.from(nextSelected), ViewerType.Graph); }); // Remove groups from the openGroups set @@ -189,7 +190,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS const handleAddToWorkspace = () => { workspace.customUpdate((draft) => { - for (const neuronId of workspace.selectedNeurons) { + for (const neuronId of selectedNeurons) { const group = workspace.neuronGroups[neuronId]; if (group) { for (const groupedNeuronId of group.neurons) { @@ -206,7 +207,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS }; const handleOpenGroup = () => { - for (const neuronId of workspace.selectedNeurons) { + for (const neuronId of selectedNeurons) { if (workspace.neuronGroups[neuronId] && !openGroups.has(neuronId)) { // Mark the group as open setOpenGroups((prevOpenGroups: Set) => { @@ -219,7 +220,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS onClose(); }; const handleCloseGroup = () => { - for (const neuronId of workspace.selectedNeurons) { + for (const neuronId of selectedNeurons) { if (workspace.neuronGroups[neuronId] && openGroups.has(neuronId)) { // Mark the group as closed setOpenGroups((prevOpenGroups: Set) => { @@ -235,8 +236,7 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS const groupEnabled = useMemo(() => { const groupOrPartOfGroupSet = new Set(); let nonGroupOrPartCount = 0; - - for (const neuronId of Array.from(workspace.selectedNeurons)) { + for (const neuronId of selectedNeurons) { const isGroup = Boolean(workspace.neuronGroups[neuronId]); const isPartOfGroup = Object.entries(workspace.neuronGroups).find(([, group]) => group.neurons.has(neuronId)); @@ -251,10 +251,10 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS // Enable grouping if there are neurons not in any group and at most one group or part of a group is selected. return nonGroupOrPartCount > 0 && groupOrPartOfGroupSet.size <= 1; - }, [workspace.selectedNeurons, workspace.neuronGroups]); + }, [selectedNeurons, workspace.neuronGroups]); const ungroupEnabled = useMemo(() => { - return Array.from(workspace.selectedNeurons).some((neuronId) => { + return selectedNeurons.some((neuronId) => { // Check if the neuronId is a group itself const isGroup = Boolean(workspace.neuronGroups[neuronId]); @@ -264,29 +264,29 @@ const ContextMenu: React.FC = ({ open, onClose, position, setS // Enable ungroup if the neuron is a group or is part of a group return isGroup || isPartOfGroup; }); - }, [workspace.selectedNeurons, workspace.neuronGroups]); + }, [selectedNeurons, workspace.neuronGroups]); const splitEnabled = useMemo(() => { - return Array.from(workspace.selectedNeurons).some((neuronId) => { + return selectedNeurons.some((neuronId) => { const neuron = workspace.availableNeurons[neuronId]; return neuron && neuron.name === neuron.nclass; }); - }, [workspace.selectedNeurons, workspace.availableNeurons]); + }, [selectedNeurons, workspace.availableNeurons]); const joinEnabled = useMemo(() => { - return Array.from(workspace.selectedNeurons).some((neuronId) => { + return selectedNeurons.some((neuronId) => { const neuron = workspace.availableNeurons[neuronId]; return neuron && neuron.name !== neuron.nclass; }); - }, [workspace.selectedNeurons, workspace.availableNeurons]); + }, [selectedNeurons, workspace.availableNeurons]); const openGroupEnabled = useMemo(() => { - return Array.from(workspace.selectedNeurons).some((neuronId) => workspace.neuronGroups[neuronId] && !openGroups.has(neuronId)); - }, [workspace.selectedNeurons, workspace.neuronGroups, openGroups]); + return selectedNeurons.some((neuronId) => workspace.neuronGroups[neuronId] && !openGroups.has(neuronId)); + }, [selectedNeurons, workspace.neuronGroups, openGroups]); const closeGroupEnabled = useMemo(() => { - return Array.from(workspace.selectedNeurons).some((neuronId) => workspace.neuronGroups[neuronId] && openGroups.has(neuronId)); - }, [workspace.selectedNeurons, workspace.neuronGroups, openGroups]); + return selectedNeurons.some((neuronId) => workspace.neuronGroups[neuronId] && openGroups.has(neuronId)); + }, [selectedNeurons, workspace.neuronGroups, openGroups]); const handleContextMenu = (event: React.MouseEvent) => { event.preventDefault(); // Prevent default context menu }; diff --git a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx index 190b2ca5..bca8d9c1 100644 --- a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx +++ b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx @@ -98,7 +98,7 @@ const TwoDMenu = ({ if (neuron) { const currentVisibility = neuron[ViewerType.Graph]?.visibility; neuron[ViewerType.Graph].visibility = currentVisibility === Visibility.Visible ? Visibility.Hidden : Visibility.Visible; - draft.selectedNeurons.delete(neuronId); + draft.removeSelection(neuronId, ViewerType.Graph); } }); }; diff --git a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx index cabbd8da..e3b517cd 100644 --- a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx +++ b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx @@ -69,6 +69,9 @@ const TwoDViewer = () => { reportedNeurons: new Set(), unreportedNeurons: new Set(), }); + + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); + const visibleActiveNeurons = useMemo(() => { return getVisibleActiveNeuronsIn2D(workspace); }, [ @@ -183,9 +186,9 @@ const TwoDViewer = () => { useEffect(() => { if (cyRef.current) { - updateHighlighted(cyRef.current, Array.from(visibleActiveNeurons), Array.from(workspace.selectedNeurons), legendHighlights); + updateHighlighted(cyRef.current, Array.from(visibleActiveNeurons), selectedNeurons, legendHighlights); } - }, [legendHighlights, workspace.selectedNeurons, workspace.neuronGroups]); + }, [legendHighlights, selectedNeurons, workspace.neuronGroups]); // Update layout when layout setting changes useEffect(() => { @@ -246,19 +249,21 @@ const TwoDViewer = () => { const handleNodeClick = (event) => { const neuronId = event.target.id(); - const isSelected = workspace.selectedNeurons.has(neuronId); - workspace.toggleSelectedNeuron(neuronId); + const selectedNeurons = workspace.getSelection(ViewerType.Graph); + const isSelected = selectedNeurons.includes(neuronId); if (isSelected) { + workspace.removeSelection(neuronId, ViewerType.Graph); event.target.removeClass(SELECTED_CLASS); } else { + workspace.addSelection(neuronId, ViewerType.Graph); event.target.addClass(SELECTED_CLASS); } }; const handleBackgroundClick = (event) => { if (event.target === cy) { - workspace.clearSelectedNeurons(); + workspace.clearSelection(ViewerType.Graph); cy.nodes(`.${SELECTED_CLASS}`).removeClass(SELECTED_CLASS); setLegendHighlights(new Map()); // Reset legend highlights @@ -270,8 +275,8 @@ const TwoDViewer = () => { const cyEvent = event as any; // Cast to any to access originalEvent const originalEvent = cyEvent.originalEvent as MouseEvent; - - if (workspace.selectedNeurons.size > 0) { + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); + if (selectedNeurons.length > 0) { setMousePosition({ mouseX: originalEvent.clientX, mouseY: originalEvent.clientY, @@ -383,7 +388,7 @@ const TwoDViewer = () => { }); updateNodeColors(); - updateHighlighted(cy, Array.from(visibleActiveNeurons), Array.from(workspace.selectedNeurons), legendHighlights); + updateHighlighted(cy, Array.from(visibleActiveNeurons), selectedNeurons, legendHighlights); checkSplitNeuronsInGraph(); }; diff --git a/applications/visualizer/frontend/src/helpers/twoD/graphRendering.ts b/applications/visualizer/frontend/src/helpers/twoD/graphRendering.ts index 711124b5..b559f6c9 100644 --- a/applications/visualizer/frontend/src/helpers/twoD/graphRendering.ts +++ b/applications/visualizer/frontend/src/helpers/twoD/graphRendering.ts @@ -27,6 +27,7 @@ export const computeGraphDifferences = ( includePostEmbryonic: boolean, ) => { const visibleActiveNeurons = getVisibleActiveNeuronsIn2D(workspace); + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); // Current nodes and edges in the Cytoscape instance const currentNodes = new Set(cy.nodes().map((node) => node.id())); @@ -115,15 +116,7 @@ export const computeGraphDifferences = ( } const groupPosition = calculateMeanPosition(groupNeurons, workspace); nodesToAdd.push( - createNode( - nodeId, - workspace.selectedNeurons.has(nodeId), - Array.from(attributes), - groupPosition, - true, - undefined, - workspace.activeNeurons.has(nodeId), - ), + createNode(nodeId, selectedNeurons.includes(nodeId), Array.from(attributes), groupPosition, true, undefined, workspace.activeNeurons.has(nodeId)), ); } else { let parent = undefined; @@ -135,7 +128,8 @@ export const computeGraphDifferences = ( break; } } - const attributes = extractNeuronAttributes(workspace.availableNeurons[nodeId]); + const neuron = workspace.availableNeurons[nodeId]; + const attributes = extractNeuronAttributes(neuron); const neuronVisibility = workspace.visibilities[nodeId]; const position = neuronVisibility?.[ViewerType.Graph]?.defaultPosition ?? null; nodesToAdd.push(createNode(nodeId, workspace.selectedNeurons.has(nodeId), attributes, position, false, parent, workspace.activeNeurons.has(nodeId))); diff --git a/applications/visualizer/frontend/src/helpers/twoD/splitJoinHelper.ts b/applications/visualizer/frontend/src/helpers/twoD/splitJoinHelper.ts index b90800a1..d5a2d85a 100644 --- a/applications/visualizer/frontend/src/helpers/twoD/splitJoinHelper.ts +++ b/applications/visualizer/frontend/src/helpers/twoD/splitJoinHelper.ts @@ -10,14 +10,15 @@ interface SplitJoinState { export const processNeuronSplit = (workspace: Workspace, splitJoinState: SplitJoinState): SplitJoinState => { const newSplit = new Set(splitJoinState.split); const newJoin = new Set(splitJoinState.join); + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); - const newSelectedNeurons = new Set(workspace.selectedNeurons); + const newSelectedNeurons = new Set(selectedNeurons); const graphViewDataUpdates: Record> = {}; const groupModifications: Record> = {}; const groupsToDelete = new Set(); - - for (const neuronId of workspace.selectedNeurons) { + + for (const neuronId of selectedNeurons) { if (!isNeuronClass(neuronId, workspace)) { return; } @@ -53,7 +54,7 @@ export const processNeuronSplit = (workspace: Workspace, splitJoinState: SplitJo } workspace.customUpdate((draft) => { - draft.selectedNeurons = newSelectedNeurons; + draft.setSelection(Array.from(newSelectedNeurons), ViewerType.Graph); for (const [groupId, neurons] of Object.entries(groupModifications)) { if (neurons.size === 0) { @@ -82,14 +83,15 @@ export const processNeuronSplit = (workspace: Workspace, splitJoinState: SplitJo export const processNeuronJoin = (workspace: Workspace, splitJoinState: SplitJoinState): SplitJoinState => { const newJoin = new Set(splitJoinState.join); const newSplit = new Set(splitJoinState.split); + const selectedNeurons = workspace.getViewerSelecedNeurons(ViewerType.Graph); - const newSelectedNeurons = new Set(workspace.selectedNeurons); + const newSelectedNeurons = new Set(selectedNeurons); const graphViewDataUpdates: Record> = {}; const groupModifications: Record> = {}; const groupsToDelete = new Set(); - - for (const neuronId of workspace.selectedNeurons) { + + for (const neuronId of selectedNeurons) { if (!isNeuronCell(neuronId, workspace)) { return; } @@ -130,7 +132,7 @@ export const processNeuronJoin = (workspace: Workspace, splitJoinState: SplitJoi } workspace.customUpdate((draft) => { - draft.selectedNeurons = newSelectedNeurons; + draft.setSelection(Array.from(newSelectedNeurons), ViewerType.Graph); for (const [groupId, neurons] of Object.entries(groupModifications)) { if (neurons.size === 0) { diff --git a/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts b/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts index 39bd0ec4..887178b6 100644 --- a/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts +++ b/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts @@ -13,17 +13,14 @@ export const createEdge = (id: string, conn: Connection, workspace: Workspace, i const label = createEdgeLabel(workspace, synapses); const longLabel = createEdgeLongLabel(workspace, synapses); - let annotationClasses: string[] = []; + const annotationClasses: string[] = annotations.map((annotation) => annotationLegend[annotation]?.id).filter(Boolean); - if (includeAnnotations) { - annotationClasses = annotations.map((annotation) => annotationLegend[annotation]?.id).filter(Boolean); - if (annotationClasses.length === 0) { - annotationClasses.push(annotationLegend.notClassified.id); - } - } else { - annotationClasses.push(conn.type); + if (includeAnnotations && annotationClasses.length === 0) { + annotationClasses.push(annotationLegend.notClassified.id); } + annotationClasses.push(conn.type); + const classes = annotationClasses.join(" "); return { group: "edges", diff --git a/applications/visualizer/frontend/src/models/synchronizer.ts b/applications/visualizer/frontend/src/models/synchronizer.ts index 2b403558..499dfd66 100644 --- a/applications/visualizer/frontend/src/models/synchronizer.ts +++ b/applications/visualizer/frontend/src/models/synchronizer.ts @@ -29,18 +29,64 @@ class Synchronizer { return this.viewers.includes(viewer); } - sync(selection: Selection, initiator: ViewerType, contexts: Record) { + sync(selection: Array, initiator: ViewerType, contexts: Record) { if (!this.canHandle(initiator)) { return; } if (!this.active) { - contexts[initiator] = selection.map((n) => n.name); + contexts[initiator] = selection.map((n) => n); return; } for (const viewer of this.viewers) { - contexts[viewer] = selection.map((n) => n.name); + contexts[viewer] = selection.map((n) => n); + } + } + + select(selection: string, initiator: ViewerType, contexts: Record) { + if (!this.canHandle(initiator)) { + return; + } + + if (!this.active) { + contexts[initiator] = [...new Set([...contexts[initiator], selection])]; + return; + } + + for (const viewer of this.viewers) { + contexts[viewer] = [...new Set([...contexts[viewer], selection])]; + } + } + unSelect(selection: string, initiator: ViewerType, contexts: Record) { + if (!this.canHandle(initiator)) { + return; + } + + if (!this.active) { + const storedNodes = [...contexts[initiator]]; + contexts[initiator] = storedNodes.filter((n) => n !== selection); + return; + } + + for (const viewer of this.viewers) { + const storedNodes = [...contexts[viewer]]; + contexts[viewer] = storedNodes.filter((n) => n !== selection); + } + } + + clear(initiator: ViewerType, contexts: Record) { + if (!this.canHandle(initiator)) { + return; + } + + if (!this.active) { + contexts[initiator] = []; + return; + } + + for (const viewer of this.viewers) { + contexts[viewer] = []; } } @@ -77,12 +123,33 @@ export class SynchronizerOrchestrator { return new SynchronizerOrchestrator(synchronizers, contexts); } - public select(selection: Selection, initiator: ViewerType) { + public select(selection: Array, initiator: ViewerType) { for (const synchronizer of this.synchronizers) { synchronizer.sync(selection, initiator, this.contexts); } } + public selectNeuron(selection: string, initiator: ViewerType) { + for (const synchronizer of this.synchronizers) { + synchronizer.select(selection, initiator, this.contexts); + } + } + + public unSelectNeuron(selection: string, initiator: ViewerType) { + for (const synchronizer of this.synchronizers) { + synchronizer.unSelect(selection, initiator, this.contexts); + } + } + + public clearSelection(initiator: ViewerType) { + for (const synchronizer of this.synchronizers) { + synchronizer.clear(initiator, this.contexts); + } + } + + public getSelection(viewerType: ViewerType): SynchronizerContext { + return this.contexts[viewerType]; + } public setActive(synchronizer: ViewerSynchronizationPair, isActive: boolean) { this.synchronizers[synchronizer].setActive(isActive); } diff --git a/applications/visualizer/frontend/src/models/workspace.ts b/applications/visualizer/frontend/src/models/workspace.ts index 75a2f43b..68fbbf8e 100644 --- a/applications/visualizer/frontend/src/models/workspace.ts +++ b/applications/visualizer/frontend/src/models/workspace.ts @@ -19,8 +19,6 @@ export class Workspace { // neuronId activeNeurons: Set; visibilities: Record; - - selectedNeurons: Set; viewers: Record; neuronGroups: Record; @@ -45,7 +43,6 @@ export class Workspace { this.activeDatasets = activeDatasets; this.availableNeurons = {}; this.activeNeurons = activeNeurons || new Set(); - this.selectedNeurons = new Set(); this.viewers = { [ViewerType.Graph]: true, [ViewerType.ThreeD]: false, @@ -87,6 +84,7 @@ export class Workspace { const updated = produce(this, (draft: Workspace) => { if (!(neuronId in draft.visibilities)) { draft.visibilities[neuronId] = emptyViewerData(Visibility.Hidden); + draft.removeSelection(neuronId, ViewerType.Graph); } // todo: add actions for other viewers draft.visibilities[neuronId][ViewerType.Graph].visibility = Visibility.Hidden; @@ -121,32 +119,12 @@ export class Workspace { const updatedWithNeurons = await this._getAvailableNeurons(updated); this.updateContext(updatedWithNeurons); } - - toggleSelectedNeuron(neuronId: string): void { - const updated = produce(this, (draft: Workspace) => { - if (draft.selectedNeurons.has(neuronId)) { - draft.selectedNeurons.delete(neuronId); - } else { - draft.selectedNeurons.add(neuronId); - } - }); - this.updateContext(updated); - } - setActiveNeurons(newActiveNeurons: Set): void { const updated = produce(this, (draft: Workspace) => { draft.activeNeurons = newActiveNeurons; }); this.updateContext(updated); } - - clearSelectedNeurons(): void { - const updated = produce(this, (draft: Workspace) => { - draft.selectedNeurons.clear(); - }); - this.updateContext(updated); - } - updateViewerSynchronizationStatus(pair: ViewerSynchronizationPair, isActive: boolean): void { const updated = produce(this, (draft: Workspace) => { draft.syncOrchestrator.setActive(pair, isActive); @@ -232,12 +210,50 @@ export class Workspace { } setSelection(selection: Array, initiator: ViewerType) { - const selectedNeurons = Object.values(this.availableNeurons).filter((neuron) => selection.includes(neuron.name)); this.customUpdate((draft) => { - draft.syncOrchestrator.select(selectedNeurons, initiator); + draft.syncOrchestrator.select(selection, initiator); + }); + } + clearSelection(initiator: ViewerType): Workspace { + const updated = produce(this, (draft: Workspace) => { + draft.syncOrchestrator.clearSelection(initiator); + }); + this.updateContext(updated); + return updated; + } + + addSelection(selection: string, initiator: ViewerType) { + this.customUpdate((draft) => { + draft.syncOrchestrator.selectNeuron(selection, initiator); + }); + } + + removeSelection(selection: string, initiator: ViewerType) { + this.customUpdate((draft) => { + draft.syncOrchestrator.unSelectNeuron(selection, initiator); }); } + getSelection(viewerType: ViewerType): string[] { + return this.syncOrchestrator.getSelection(viewerType); + } + + getViewerSelecedNeurons(viewerType: ViewerType): string[] { + return this.syncOrchestrator.getSelection(viewerType); + } + getHiddenNeurons() { + const hiddenNodes = new Set(); + + for (const neuronId of this.activeNeurons) { + const neuron = this.availableNeurons[neuronId]; + if (neuron && !neuron.isVisible) { + hiddenNodes.add(neuronId); + } + } + + return hiddenNodes; + } + getNeuronCellsByClass(neuronClassId: string): string[] { return Object.values(this.availableNeurons) .filter((neuron) => neuron.nclass === neuronClassId && neuron.nclass !== neuron.name) diff --git a/applications/visualizer/frontend/src/theme/twoDStyles.ts b/applications/visualizer/frontend/src/theme/twoDStyles.ts index 88de08ad..106e4162 100644 --- a/applications/visualizer/frontend/src/theme/twoDStyles.ts +++ b/applications/visualizer/frontend/src/theme/twoDStyles.ts @@ -115,6 +115,15 @@ const ELECTRICAL_STYLE = [ "source-arrow-shape": "tee", }, }, + { + selector: ".electrical.not_classified:loop", + css: { + "target-arrow-shape": "tee", + "source-arrow-shape": "tee", + "source-arrow-color": "#228B22", + "target-arrow-color": "#228B22", + }, + }, ]; const OPEN_GROUP_STYLE = { @@ -258,6 +267,7 @@ const ANNOTATION_STYLES = Object.entries(annotationLegend).map(([, { id, color } style: { "line-color": color, "target-arrow-color": color, + "sources-arrow-color": color, }, }));