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