Skip to content

Commit ecb494b

Browse files
committed
Moved view and gene switching functionality to main views folder for use in all views. Now includes option to switch only the gene, only the view, or both. Added tsdocs
1 parent 5daa927 commit ecb494b

4 files changed

Lines changed: 181 additions & 103 deletions

File tree

Eplant/views/NavigatorViewer/NavigatorView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import CellEFPIcon from './Icons/CellEFPIcon';
1010
import GeneInfoViewIcon from './Icons/GeneInfoViewerIcon'; /** Placeholder icon for those that are not yet implemented in ePlant3 */
1111
import PlantEFPIcon from './Icons/PlantEFPIcon'
1212
import * as constants from './constants';
13-
import { useViewSwitch } from './geneViewHelpers';
13+
import { useViewSwitch } from '../ViewGeneSwitching';
1414
//import { NavigatorContext, useViewSwitch, ViewSwitchProvider} from './index';
1515
import { NavigatorContext, ViewSwitchProvider} from './index';
1616

@@ -618,7 +618,7 @@ export const NavigatorViewObject = () => {
618618
const svgRef = useRef<SVGSVGElement | null>(null);
619619
const gRef = useRef<SVGGElement | null>(null);
620620
const theme = useTheme();
621-
const { switchView } = useViewSwitch();
621+
const { switchViewAndGene } = useViewSwitch();
622622

623623
/** Initialize dimensions with default calculation */
624624
const [dimensions, setDimensions] = useState(calculateDimensions());
@@ -917,7 +917,7 @@ useEffect(() => {
917917
/** Extract the geneName */
918918
const geneName = displayName;
919919
/** Call switch view function to swap the view using designated view id and gene name */
920-
switchView('plant', geneName);
920+
switchViewAndGene('plant', geneName);
921921
}}
922922
>
923923
<rect
@@ -946,7 +946,7 @@ useEffect(() => {
946946
/** Extract the geneName */
947947
const geneName = displayName;
948948
/** Call switch view function to swap the view using designated view id and gene name */
949-
switchView('Cell eFP', geneName);
949+
switchViewAndGene('Cell eFP', geneName);
950950
}}
951951
>
952952
<rect

Eplant/views/NavigatorViewer/geneViewHelpers.tsx

Lines changed: 0 additions & 96 deletions
This file was deleted.

Eplant/views/NavigatorViewer/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import GeneticElement from '@eplant/GeneticElement';
44
import { View } from '@eplant/View';
55

66
import { NavigatorIcon } from './Icons/NavigatorViewIcon';
7-
import { createViewSwitchProvider } from './geneViewHelpers';
7+
import { createViewSwitchProvider } from '../ViewGeneSwitching';
88
import NavigatorViewObject from './NavigatorView';
99

1010
/** Use the provider from helper function */
@@ -20,8 +20,8 @@ const NavigatorView: View = {
2020
name: 'Navigator View',
2121
component: ({ geneticElement }) => {
2222
const baseUrl = 'https://bar.utoronto.ca/webservices/eplant_navigator/cgi-bin/eplant_navigator_service.cgi';
23-
const gene = geneticElement?.id || 'AT3G24650';
24-
const species = geneticElement?.species?.name || 'Arabidopsis';
23+
const gene = geneticElement?.id || '';
24+
const species = geneticElement?.species?.name || '';
2525

2626
const apiUrl = `${baseUrl}?primaryGene=${encodeURIComponent(gene)}&species=${encodeURIComponent(species)}&dataset=Developmental&checkedspecies=arabidopsis_poplar_medicago_soybean_rice_barley_maize_potato_tomato_grape`;
2727

Eplant/views/ViewGeneSwitching.tsx

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { createContext, useContext, useEffect, useState } from 'react';
2+
import _ from 'lodash';
3+
4+
import { useConfig } from '@eplant/config';
5+
import GeneticElement from '@eplant/GeneticElement';
6+
import { useGeneticElements, useSetActiveGeneId, useSetActiveViewId, useSpecies } from '@eplant/state';
7+
import { View } from '@eplant/View';
8+
9+
/**
10+
* Interface defining the shape of the ViewSwitch context
11+
*/
12+
interface ViewSwitchContextType {
13+
/** Currently active view */
14+
activeView: View | null;
15+
/** Currently active genetic element */
16+
activeGene: GeneticElement | null;
17+
/** Function to switch view only */
18+
switchViewOnly: (viewId: string) => Promise<void>;
19+
/** Function to switch gene only */
20+
switchGeneOnly: (geneName: string) => Promise<void>;
21+
/** Function to switch both view and gene */
22+
switchViewAndGene: (viewId: string, geneName: string) => Promise<void>;
23+
}
24+
25+
/** Default context value */
26+
const ViewSwitchContext = createContext<ViewSwitchContextType>({
27+
activeView: null,
28+
activeGene: null,
29+
switchViewOnly: async () => {},
30+
switchGeneOnly: async () => {},
31+
switchViewAndGene: async () => {},
32+
});
33+
34+
/** Hook to access the ViewSwitch context */
35+
export const useViewSwitch = () => useContext(ViewSwitchContext);
36+
37+
/**
38+
* Creates a ViewSwitch provider component
39+
* @returns A React component that provides view switching functionality
40+
*/
41+
export const createViewSwitchProvider = () => {
42+
/**
43+
* ViewSwitch Provider Component
44+
* @param props - Component props
45+
*/
46+
const ViewSwitchProvider = ({ children }: { children: React.ReactNode }) => {
47+
const [activeView, setActiveView] = useState<View | null>(null);
48+
const [activeGene, setActiveGene] = useState<GeneticElement | null>(null);
49+
const [geneticElements, setGeneticElements] = useGeneticElements();
50+
51+
const { userViews } = useConfig();
52+
const setActiveViewId = useSetActiveViewId();
53+
const setActiveGeneId = useSetActiveGeneId();
54+
const [speciesList] = useSpecies();
55+
const species = speciesList.length ? speciesList[0] : undefined;
56+
57+
/**
58+
* Adds genetic elements to the state, ensuring uniqueness
59+
* @param genes - Array of genetic elements to add
60+
*/
61+
const addGeneticElements = (genes: GeneticElement[]) => {
62+
setGeneticElements((prev) => {
63+
const updatedGenes = _.uniqBy([...prev, ...genes], (gene) => gene.id);
64+
return updatedGenes;
65+
});
66+
67+
if (genes.length > 0) {
68+
setActiveGeneId(genes[0].id);
69+
}
70+
};
71+
72+
// Ensure genetic elements remain unique
73+
useEffect(() => {
74+
const uniqueGenes = _.uniqBy(geneticElements, (gene) => gene.id);
75+
if (uniqueGenes.length !== geneticElements.length) {
76+
setGeneticElements(uniqueGenes);
77+
}
78+
}, [geneticElements, setGeneticElements]);
79+
80+
/**
81+
* Validates and retrieves a view by ID
82+
* @param viewId - The ID of the view to validate
83+
* @returns The validated view or null
84+
*/
85+
const validateView = (viewId: string): View | null => {
86+
const targetView = userViews.find((view) => view.id === viewId);
87+
if (!targetView) {
88+
console.warn(`View with ID ${viewId} not found`);
89+
return null;
90+
}
91+
return targetView;
92+
};
93+
94+
/**
95+
* Loads a gene by name
96+
* @param geneName - The name of the gene to load
97+
* @returns The loaded genetic element or null
98+
*/
99+
const loadGene = async (geneName: string): Promise<GeneticElement | null> => {
100+
if (!species) {
101+
console.error("Species configuration is missing.");
102+
return null;
103+
}
104+
105+
try {
106+
const loadedGene = await species.api.searchGene(geneName);
107+
return loadedGene as GeneticElement;
108+
} catch (error) {
109+
console.error(`Error loading gene ${geneName}:`, error);
110+
return null;
111+
}
112+
};
113+
114+
/**
115+
* Switches only the view, keeping the current gene
116+
* @param viewId - ID of the view to switch to
117+
*/
118+
const switchViewOnly = async (viewId: string): Promise<void> => {
119+
const targetView = validateView(viewId);
120+
if (targetView) {
121+
setActiveViewId(targetView.id);
122+
setActiveView(targetView);
123+
}
124+
};
125+
126+
/**
127+
* Switches only the gene, keeping the current view
128+
* @param geneName - Name of the gene to switch to
129+
*/
130+
const switchGeneOnly = async (geneName: string): Promise<void> => {
131+
const geneticElement = await loadGene(geneName);
132+
if (geneticElement) {
133+
addGeneticElements([geneticElement]);
134+
setActiveGeneId(geneticElement.id);
135+
setActiveGene(geneticElement);
136+
}
137+
};
138+
139+
/**
140+
* Switches both view and gene
141+
* @param viewId - ID of the view to switch to
142+
* @param geneName - Name of the gene to switch to
143+
*/
144+
const switchViewAndGene = async (viewId: string, geneName: string): Promise<void> => {
145+
const targetView = validateView(viewId);
146+
const geneticElement = await loadGene(geneName);
147+
148+
if (targetView && geneticElement) {
149+
addGeneticElements([geneticElement]);
150+
setActiveViewId(targetView.id);
151+
setActiveView(targetView);
152+
setActiveGeneId(geneticElement.id);
153+
setActiveGene(geneticElement);
154+
}
155+
};
156+
157+
return (
158+
<ViewSwitchContext.Provider
159+
value={{
160+
activeView,
161+
activeGene,
162+
switchViewOnly,
163+
switchGeneOnly,
164+
switchViewAndGene
165+
}}
166+
>
167+
{children}
168+
</ViewSwitchContext.Provider>
169+
);
170+
};
171+
172+
ViewSwitchProvider.displayName = 'ViewSwitchProvider';
173+
return ViewSwitchProvider;
174+
};

0 commit comments

Comments
 (0)