From 294cb935c0290edc58dbd2f43507d9f6bda5740f Mon Sep 17 00:00:00 2001 From: Yukthi Date: Thu, 4 Dec 2025 12:22:28 -0500 Subject: [PATCH 01/17] Test --- Eplant/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Eplant/main.tsx b/Eplant/main.tsx index c9357ed..a2fd48a 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -20,7 +20,7 @@ import { Config, defaultConfig } from './config' import Eplant from './Eplant' import './css/index.css' -console.log('DEBUG') +console.log('DEBUG-') const router = createBrowserRouter([ { path: '/', From 68aab2f65217038a1b8b540978430a860c8957e5 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Thu, 4 Dec 2025 12:28:11 -0500 Subject: [PATCH 02/17] Fix yaml --- .github/workflows/build.yml | 3 ++- .github/workflows/deploy.yml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62e1def..17b4c0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,8 @@ name: Build on: push: branches-ignore: - - debug-staging + - main + - staging pull_request: jobs: build: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 70598b7..a026420 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,8 +2,7 @@ name: Deploy on: push: branches: - - main - - staging + - staging-debug permissions: contents: read From 7fb4da309ff5c021d5047410d0a44b414b218d83 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Thu, 4 Dec 2025 12:29:40 -0500 Subject: [PATCH 03/17] ignore build on staging-debug --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17b4c0f..7cc3418 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: branches-ignore: - main - staging + - staging-debug pull_request: jobs: build: From 2750c49edc242e444310c04d28d284fe38f305c8 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Thu, 4 Dec 2025 12:59:16 -0500 Subject: [PATCH 04/17] Add correct router path --- Eplant/main.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Eplant/main.tsx b/Eplant/main.tsx index a2fd48a..34107b6 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -20,10 +20,9 @@ import { Config, defaultConfig } from './config' import Eplant from './Eplant' import './css/index.css' -console.log('DEBUG-') const router = createBrowserRouter([ { - path: '/', + path: process.env.BASE_URL ?? '/', element: , children: [ { From ec9d6efe6661c3a040a4d722362aec6e98ff134d Mon Sep 17 00:00:00 2001 From: Yukthi Date: Tue, 16 Dec 2025 10:14:25 -0500 Subject: [PATCH 05/17] Fixed router path --- Eplant/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Eplant/main.tsx b/Eplant/main.tsx index 34107b6..463de8b 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -22,7 +22,7 @@ import Eplant from './Eplant' import './css/index.css' const router = createBrowserRouter([ { - path: process.env.BASE_URL ?? '/', + path: import.meta.env.BASE_URL ?? '/', element: , children: [ { From 3c09e0dc5de08f2b445436de6dcf51b82e143ee1 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Tue, 16 Dec 2025 10:27:29 -0500 Subject: [PATCH 06/17] Added basename arg --- Eplant/main.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Eplant/main.tsx b/Eplant/main.tsx index 463de8b..907e170 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -22,7 +22,7 @@ import Eplant from './Eplant' import './css/index.css' const router = createBrowserRouter([ { - path: import.meta.env.BASE_URL ?? '/', + path: '/', element: , children: [ { @@ -67,7 +67,9 @@ const router = createBrowserRouter([ ], errorElement: , }, -]) +], { + basename: import.meta.env.BASE_URL ?? '/', +}) export const queryClient = new QueryClient() From f949b05f0135ace297ab9a0c0101f27522071849 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Tue, 16 Dec 2025 10:29:30 -0500 Subject: [PATCH 07/17] Prettier --- Eplant/main.tsx | 101 +++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/Eplant/main.tsx b/Eplant/main.tsx index 907e170..5260001 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -20,56 +20,59 @@ import { Config, defaultConfig } from './config' import Eplant from './Eplant' import './css/index.css' -const router = createBrowserRouter([ +const router = createBrowserRouter( + [ + { + path: '/', + element: , + children: [ + { + element: , + }, + { + path: 'cell-efp/:geneid?', + element: , + }, + { + path: 'publications/:geneid?', + element: , + }, + { + path: 'chromosome/:geneid?', + element: , + }, + { + path: 'plant-efp/:geneid?', + element: , + }, + { + path: 'experiment-efp/:geneid?', + element: , + }, + { + path: 'gene-info/:geneid?', + element: , + }, + { + path: 'get-started/:geneid?', + element: , + }, + { + path: 'navigator-view/:geneid?', + element: , + }, + { + path: 'interactions-view/:geneid?', + element: , + }, + ], + errorElement: , + }, + ], { - path: '/', - element: , - children: [ - { - element: , - }, - { - path: 'cell-efp/:geneid?', - element: , - }, - { - path: 'publications/:geneid?', - element: , - }, - { - path: 'chromosome/:geneid?', - element: , - }, - { - path: 'plant-efp/:geneid?', - element: , - }, - { - path: 'experiment-efp/:geneid?', - element: , - }, - { - path: 'gene-info/:geneid?', - element: , - }, - { - path: 'get-started/:geneid?', - element: , - }, - { - path: 'navigator-view/:geneid?', - element: , - }, - { - path: 'interactions-view/:geneid?', - element: , - }, - ], - errorElement: , - }, -], { - basename: import.meta.env.BASE_URL ?? '/', -}) + basename: import.meta.env.BASE_URL ?? '/', + } +) export const queryClient = new QueryClient() From 400cbb09ab0fa76772f23a96c0b507d596ea19e8 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Tue, 16 Dec 2025 10:33:03 -0500 Subject: [PATCH 08/17] Revert guthub actions change --- .github/workflows/build.yml | 1 - .github/workflows/deploy.yml | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cc3418..17b4c0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,6 @@ on: branches-ignore: - main - staging - - staging-debug pull_request: jobs: build: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a026420..70598b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,8 @@ name: Deploy on: push: branches: - - staging-debug + - main + - staging permissions: contents: read From 94f7de51a2f2d6b7bfea51347cb2b5fe4b817ed2 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Tue, 16 Dec 2025 16:35:45 -0500 Subject: [PATCH 09/17] Changed entrypoint path back to being base relative --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index a00036c..af309e3 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + - + Eplant From cd42aa77887df847dcd14e056bfca41dc2e9c62f Mon Sep 17 00:00:00 2001 From: Yukthi Date: Thu, 25 Dec 2025 18:07:05 -0500 Subject: [PATCH 10/17] Moving to hashrouter --- Eplant/UI/Layout/ViewContainer/index.tsx | 85 ++---------------------- Eplant/main.tsx | 4 +- 2 files changed, 7 insertions(+), 82 deletions(-) diff --git a/Eplant/UI/Layout/ViewContainer/index.tsx b/Eplant/UI/Layout/ViewContainer/index.tsx index b46b978..c02c6f8 100644 --- a/Eplant/UI/Layout/ViewContainer/index.tsx +++ b/Eplant/UI/Layout/ViewContainer/index.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from 'react' -import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom' +import { useState } from 'react' +import { Outlet } from 'react-router-dom' import { useConfig } from '@eplant/config' import { @@ -7,7 +7,6 @@ import { useActiveViewId, useGeneticElements, usePrinting, - useSpecies, } from '@eplant/state' import Modal from '@eplant/UI/Modal' import ErrorBoundary from '@eplant/util/ErrorBoundary' @@ -37,83 +36,9 @@ export function ViewContainer({ ...props }) { const [printing, setPrinting] = usePrinting() const [viewingCitations, setViewingCitations] = useState(false) const { views } = useConfig() - const navigate = useNavigate() - const location = useLocation() - const params = useParams() - const [speciesList] = useSpecies() - const [genes, setGenes] = useGeneticElements() - const [activeGeneId, setActiveGeneId] = useActiveGeneId() - const [activeViewId, setActiveViewId] = useActiveViewId() - const [geneNotFound, setGeneNotFound] = useState(false) - - // On app url change, make sure loaded gene and view aligns with URL - useEffect(() => { - const loadGene = async (geneid: string) => { - // TODO: This is super jank, should probably write some better utilities for loading genes - const species = speciesList.find( - (species) => species.name === 'Arabidopsis' - ) - const newGene = await species?.api.searchGene(geneid) - if (newGene) { - setGenes([...genes, newGene]) - } else { - setGeneNotFound(true) - setActiveGeneId('') - } - } - if (params.geneid) { - if (params.geneid !== activeGeneId) { - if (!genes.find((g) => g.id === params.geneid)) { - loadGene(params.geneid) - } - if (!geneNotFound) setActiveGeneId(params.geneid) - } - } else { - // Set active gene to first available if one is already loaded - if (genes.length > 0) { - setActiveGeneId(genes[0].id) - } else { - setActiveGeneId('') - } - } - - // Set activeview - const urlView = - views.find((view) => view.id === location.pathname.split('/')[1]) ?? - GeneInfoViewMetadata - - setActiveViewId(urlView.id) - }, []) - - // On when the activegene or view changes, update path - useEffect(() => { - const oldPathSegments = location.pathname - .split('/') - .filter((segment) => segment !== '') - - const newPathSegments = [] - if (activeViewId) { - newPathSegments.push(activeViewId) - } - if (activeGeneId) { - newPathSegments.push(activeGeneId) - } - - if (newPathSegments.length > 0) { - let newPath - if ( - oldPathSegments.length > 0 && - oldPathSegments[0] == newPathSegments[0] - ) { - // If the view is the same we want to retain quary params in url, else we can wipe - // them and have URLStateManager handle things - newPath = '/' + newPathSegments.join('/') + location.search - } else { - newPath = '/' + newPathSegments.join('/') - } - navigate(newPath) - } - }, [activeGeneId, activeViewId]) + const [genes] = useGeneticElements() + const [activeGeneId] = useActiveGeneId() + const [activeViewId] = useActiveViewId() // Get view and gene objects once everything resolves const activeView = diff --git a/Eplant/main.tsx b/Eplant/main.tsx index 5260001..def8e77 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react' import { Provider } from 'jotai' import * as ReactDOM from 'react-dom/client' -import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' +import { createHashRouter, Navigate, RouterProvider } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' @@ -20,7 +20,7 @@ import { Config, defaultConfig } from './config' import Eplant from './Eplant' import './css/index.css' -const router = createBrowserRouter( +const router = createHashRouter( [ { path: '/', From 1077bde74a1a41ed86237633b0ae7f02a149a8ad Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 14:21:06 -0500 Subject: [PATCH 11/17] Trying to fix view alignment issues --- Eplant/state/URLStateProvider.tsx | 151 +++++++++++++++++++++++++++--- Eplant/state/stateUtils.ts | 5 + 2 files changed, 142 insertions(+), 14 deletions(-) diff --git a/Eplant/state/URLStateProvider.tsx b/Eplant/state/URLStateProvider.tsx index 5fd7f37..bcd3c7a 100644 --- a/Eplant/state/URLStateProvider.tsx +++ b/Eplant/state/URLStateProvider.tsx @@ -4,20 +4,40 @@ import React, { useCallback, useContext, useEffect, + useMemo, useRef, useState, } from 'react' import { debounce } from 'lodash' -import { useSearchParams } from 'react-router-dom' +import { + useLocation, + useNavigate, + useParams, + useSearchParams, +} from 'react-router-dom' import { AnyZodObject } from 'zod' -import { flattenObject, getStateFromParams, getZodDefaults } from './stateUtils' -import { useActiveViewId } from '.' +import { useConfig } from '@eplant/config' +import GeneInfoViewMetadata from '@eplant/views/GeneInfoView' + +import { + flattenObject, + getStateFromParams, + getViewIdFromPathname, + getZodDefaults, +} from './stateUtils' +import { + useActiveGeneId, + useActiveViewId, + useGeneticElements, + useSpecies, +} from '.' interface URLStateContext { state: T | null setState: (updatedState: T) => void initializeState: (schema: AnyZodObject) => void + currentStateViewIdRef: React.MutableRefObject } const URLStateContext = createContext>({ @@ -28,26 +48,105 @@ const URLStateContext = createContext>({ initializeState: () => { throw new Error('initializeState must be used within a URLStateProvider') }, + currentStateViewIdRef: { current: null }, }) export const URLStateProvider = ({ children }: { children: ReactNode }) => { - const [activeViewId] = useActiveViewId() - const [searchParams, setSearchParams] = useSearchParams() - const stateCache = useRef(new Map()) const [state, setState] = useState(() => { null }) + const [activeViewId, setActiveViewId] = useActiveViewId() + const [searchParams, setSearchParams] = useSearchParams() + const stateCache = useRef(new Map()) + const currentStateViewIdRef = useRef(null) + const { views } = useConfig() + const location = useLocation() + const navigate = useNavigate() + const [speciesList] = useSpecies() + const [genes, setGenes] = useGeneticElements() + const [activeGeneId, setActiveGeneId] = useActiveGeneId() + const [geneNotFound, setGeneNotFound] = useState(false) + const params = useParams() + + useEffect(() => { + const loadGene = async (geneid: string) => { + // TODO: This is super jank, should probably write some better utilities for loading genes + const species = speciesList.find( + (species) => species.name === 'Arabidopsis' + ) + const newGene = await species?.api.searchGene(geneid) + if (newGene) { + setGenes([...genes, newGene]) + } else { + setGeneNotFound(true) + setActiveGeneId('') + } + } + if (params.geneid) { + if (params.geneid !== activeGeneId) { + if (!genes.find((g) => g.id === params.geneid)) { + loadGene(params.geneid) + } + if (!geneNotFound) setActiveGeneId(params.geneid) + } + } else { + // Set active gene to first available if one is already loaded + if (genes.length > 0) { + setActiveGeneId(genes[0].id) + } else { + setActiveGeneId('') + } + } + + // Set activeview + const urlView = + views.find((view) => view.id === location.pathname.split('/')[1]) ?? + GeneInfoViewMetadata + + setActiveViewId(urlView.id) + }, []) + + // On when the activegene or view changes, update path + useEffect(() => { + const oldPathSegments = location.pathname + .split('/') + .filter((segment) => segment !== '') + + const newPathSegments = [] + if (activeViewId) { + newPathSegments.push(activeViewId) + } + if (activeGeneId) { + newPathSegments.push(activeGeneId) + } + + if (newPathSegments.length > 0) { + let newPath + if ( + oldPathSegments.length > 0 && + oldPathSegments[0] == newPathSegments[0] + ) { + // If the view is the same we want to retain query params in url, else we can wipe + // them and have URLStateManager handle things + newPath = '/' + newPathSegments.join('/') + location.search + } else { + newPath = '/' + newPathSegments.join('/') + } + navigate(newPath) + } + }, [activeGeneId, activeViewId]) const debouncedUpdateSearchParams = useCallback( debounce((updatedState: any) => { - // Setting component state - stateCache.current.set(activeViewId, updatedState) + // Get current view ID from location to ensure correct caching + const currentViewId = getViewIdFromPathname(location.pathname) + stateCache.current.set(currentViewId, updatedState) // Setting params const flattenedState = flattenObject(updatedState) setSearchParams(new URLSearchParams(flattenedState as any)) }, 50), - [activeViewId] + [location.pathname, setSearchParams] ) useEffect(() => { @@ -61,19 +160,25 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { const initializeState = useCallback( (schema: T) => { - // Get validated state - const defaultState = getZodDefaults(schema) // This returns the defaults defined by Zod Schema + const currentViewId = getViewIdFromPathname(location.pathname) + + const defaultState = getZodDefaults(schema) const paramsState = getStateFromParams(schema, searchParams) - const cachedState = stateCache.current.get(activeViewId) || {} + const cachedState = stateCache.current.get(currentViewId) || {} + // Merge precedence: search params > cached state > defaults const mergedState = { ...defaultState, ...cachedState, ...paramsState, } + + // Track which view ID this state belongs to + currentStateViewIdRef.current = currentViewId + setActiveViewId(currentViewId) setState(mergedState) }, - [searchParams, activeViewId] + [searchParams, location.pathname] ) return ( @@ -82,6 +187,7 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { state, setState: (updatedState) => setState(updatedState), initializeState, + currentStateViewIdRef, }} > {children} @@ -91,8 +197,25 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { export const useURLState = () => { const context = useContext(URLStateContext) + const location = useLocation() + if (!context.setState) { throw new Error('useURLState must be used within a URLStateProvider') } - return context as URLStateContext + + const currentUrlViewId = useMemo( + () => getViewIdFromPathname(location.pathname), + [location.pathname] + ) + + // Clear state to avoid + const safeState = + currentUrlViewId === context.currentStateViewIdRef.current + ? context.state + : null + + return { + ...context, + state: safeState, + } as URLStateContext } diff --git a/Eplant/state/stateUtils.ts b/Eplant/state/stateUtils.ts index 733a646..e202f0d 100644 --- a/Eplant/state/stateUtils.ts +++ b/Eplant/state/stateUtils.ts @@ -111,3 +111,8 @@ export const unflattenObject = ( } return result } + +export const getViewIdFromPathname = (pathname: string): string => { + const pathParts = pathname.split('/').filter(Boolean) + return pathParts[0] || 'gene-info' +} From 6652b373e23aecbcb27d1abe5034e1a2bc0abf31 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 14:43:18 -0500 Subject: [PATCH 12/17] Revert "Trying to fix view alignment issues" This reverts commit 1077bde74a1a41ed86237633b0ae7f02a149a8ad. --- Eplant/state/URLStateProvider.tsx | 151 +++--------------------------- Eplant/state/stateUtils.ts | 5 - 2 files changed, 14 insertions(+), 142 deletions(-) diff --git a/Eplant/state/URLStateProvider.tsx b/Eplant/state/URLStateProvider.tsx index bcd3c7a..5fd7f37 100644 --- a/Eplant/state/URLStateProvider.tsx +++ b/Eplant/state/URLStateProvider.tsx @@ -4,40 +4,20 @@ import React, { useCallback, useContext, useEffect, - useMemo, useRef, useState, } from 'react' import { debounce } from 'lodash' -import { - useLocation, - useNavigate, - useParams, - useSearchParams, -} from 'react-router-dom' +import { useSearchParams } from 'react-router-dom' import { AnyZodObject } from 'zod' -import { useConfig } from '@eplant/config' -import GeneInfoViewMetadata from '@eplant/views/GeneInfoView' - -import { - flattenObject, - getStateFromParams, - getViewIdFromPathname, - getZodDefaults, -} from './stateUtils' -import { - useActiveGeneId, - useActiveViewId, - useGeneticElements, - useSpecies, -} from '.' +import { flattenObject, getStateFromParams, getZodDefaults } from './stateUtils' +import { useActiveViewId } from '.' interface URLStateContext { state: T | null setState: (updatedState: T) => void initializeState: (schema: AnyZodObject) => void - currentStateViewIdRef: React.MutableRefObject } const URLStateContext = createContext>({ @@ -48,105 +28,26 @@ const URLStateContext = createContext>({ initializeState: () => { throw new Error('initializeState must be used within a URLStateProvider') }, - currentStateViewIdRef: { current: null }, }) export const URLStateProvider = ({ children }: { children: ReactNode }) => { + const [activeViewId] = useActiveViewId() + const [searchParams, setSearchParams] = useSearchParams() + const stateCache = useRef(new Map()) const [state, setState] = useState(() => { null }) - const [activeViewId, setActiveViewId] = useActiveViewId() - const [searchParams, setSearchParams] = useSearchParams() - const stateCache = useRef(new Map()) - const currentStateViewIdRef = useRef(null) - const { views } = useConfig() - const location = useLocation() - const navigate = useNavigate() - const [speciesList] = useSpecies() - const [genes, setGenes] = useGeneticElements() - const [activeGeneId, setActiveGeneId] = useActiveGeneId() - const [geneNotFound, setGeneNotFound] = useState(false) - const params = useParams() - - useEffect(() => { - const loadGene = async (geneid: string) => { - // TODO: This is super jank, should probably write some better utilities for loading genes - const species = speciesList.find( - (species) => species.name === 'Arabidopsis' - ) - const newGene = await species?.api.searchGene(geneid) - if (newGene) { - setGenes([...genes, newGene]) - } else { - setGeneNotFound(true) - setActiveGeneId('') - } - } - if (params.geneid) { - if (params.geneid !== activeGeneId) { - if (!genes.find((g) => g.id === params.geneid)) { - loadGene(params.geneid) - } - if (!geneNotFound) setActiveGeneId(params.geneid) - } - } else { - // Set active gene to first available if one is already loaded - if (genes.length > 0) { - setActiveGeneId(genes[0].id) - } else { - setActiveGeneId('') - } - } - - // Set activeview - const urlView = - views.find((view) => view.id === location.pathname.split('/')[1]) ?? - GeneInfoViewMetadata - - setActiveViewId(urlView.id) - }, []) - - // On when the activegene or view changes, update path - useEffect(() => { - const oldPathSegments = location.pathname - .split('/') - .filter((segment) => segment !== '') - - const newPathSegments = [] - if (activeViewId) { - newPathSegments.push(activeViewId) - } - if (activeGeneId) { - newPathSegments.push(activeGeneId) - } - - if (newPathSegments.length > 0) { - let newPath - if ( - oldPathSegments.length > 0 && - oldPathSegments[0] == newPathSegments[0] - ) { - // If the view is the same we want to retain query params in url, else we can wipe - // them and have URLStateManager handle things - newPath = '/' + newPathSegments.join('/') + location.search - } else { - newPath = '/' + newPathSegments.join('/') - } - navigate(newPath) - } - }, [activeGeneId, activeViewId]) const debouncedUpdateSearchParams = useCallback( debounce((updatedState: any) => { - // Get current view ID from location to ensure correct caching - const currentViewId = getViewIdFromPathname(location.pathname) - stateCache.current.set(currentViewId, updatedState) + // Setting component state + stateCache.current.set(activeViewId, updatedState) // Setting params const flattenedState = flattenObject(updatedState) setSearchParams(new URLSearchParams(flattenedState as any)) }, 50), - [location.pathname, setSearchParams] + [activeViewId] ) useEffect(() => { @@ -160,25 +61,19 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { const initializeState = useCallback( (schema: T) => { - const currentViewId = getViewIdFromPathname(location.pathname) - - const defaultState = getZodDefaults(schema) + // Get validated state + const defaultState = getZodDefaults(schema) // This returns the defaults defined by Zod Schema const paramsState = getStateFromParams(schema, searchParams) - const cachedState = stateCache.current.get(currentViewId) || {} - + const cachedState = stateCache.current.get(activeViewId) || {} // Merge precedence: search params > cached state > defaults const mergedState = { ...defaultState, ...cachedState, ...paramsState, } - - // Track which view ID this state belongs to - currentStateViewIdRef.current = currentViewId - setActiveViewId(currentViewId) setState(mergedState) }, - [searchParams, location.pathname] + [searchParams, activeViewId] ) return ( @@ -187,7 +82,6 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { state, setState: (updatedState) => setState(updatedState), initializeState, - currentStateViewIdRef, }} > {children} @@ -197,25 +91,8 @@ export const URLStateProvider = ({ children }: { children: ReactNode }) => { export const useURLState = () => { const context = useContext(URLStateContext) - const location = useLocation() - if (!context.setState) { throw new Error('useURLState must be used within a URLStateProvider') } - - const currentUrlViewId = useMemo( - () => getViewIdFromPathname(location.pathname), - [location.pathname] - ) - - // Clear state to avoid - const safeState = - currentUrlViewId === context.currentStateViewIdRef.current - ? context.state - : null - - return { - ...context, - state: safeState, - } as URLStateContext + return context as URLStateContext } diff --git a/Eplant/state/stateUtils.ts b/Eplant/state/stateUtils.ts index e202f0d..733a646 100644 --- a/Eplant/state/stateUtils.ts +++ b/Eplant/state/stateUtils.ts @@ -111,8 +111,3 @@ export const unflattenObject = ( } return result } - -export const getViewIdFromPathname = (pathname: string): string => { - const pathParts = pathname.split('/').filter(Boolean) - return pathParts[0] || 'gene-info' -} From e7d92bff3622edcc6ef2c14db4384c94aad88fa1 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 14:47:54 -0500 Subject: [PATCH 13/17] Revert "Moving to hashrouter" This reverts commit cd42aa77887df847dcd14e056bfca41dc2e9c62f. --- Eplant/UI/Layout/ViewContainer/index.tsx | 85 ++++++++++++++++++++++-- Eplant/main.tsx | 4 +- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/Eplant/UI/Layout/ViewContainer/index.tsx b/Eplant/UI/Layout/ViewContainer/index.tsx index c02c6f8..b46b978 100644 --- a/Eplant/UI/Layout/ViewContainer/index.tsx +++ b/Eplant/UI/Layout/ViewContainer/index.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react' -import { Outlet } from 'react-router-dom' +import { useEffect, useState } from 'react' +import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom' import { useConfig } from '@eplant/config' import { @@ -7,6 +7,7 @@ import { useActiveViewId, useGeneticElements, usePrinting, + useSpecies, } from '@eplant/state' import Modal from '@eplant/UI/Modal' import ErrorBoundary from '@eplant/util/ErrorBoundary' @@ -36,9 +37,83 @@ export function ViewContainer({ ...props }) { const [printing, setPrinting] = usePrinting() const [viewingCitations, setViewingCitations] = useState(false) const { views } = useConfig() - const [genes] = useGeneticElements() - const [activeGeneId] = useActiveGeneId() - const [activeViewId] = useActiveViewId() + const navigate = useNavigate() + const location = useLocation() + const params = useParams() + const [speciesList] = useSpecies() + const [genes, setGenes] = useGeneticElements() + const [activeGeneId, setActiveGeneId] = useActiveGeneId() + const [activeViewId, setActiveViewId] = useActiveViewId() + const [geneNotFound, setGeneNotFound] = useState(false) + + // On app url change, make sure loaded gene and view aligns with URL + useEffect(() => { + const loadGene = async (geneid: string) => { + // TODO: This is super jank, should probably write some better utilities for loading genes + const species = speciesList.find( + (species) => species.name === 'Arabidopsis' + ) + const newGene = await species?.api.searchGene(geneid) + if (newGene) { + setGenes([...genes, newGene]) + } else { + setGeneNotFound(true) + setActiveGeneId('') + } + } + if (params.geneid) { + if (params.geneid !== activeGeneId) { + if (!genes.find((g) => g.id === params.geneid)) { + loadGene(params.geneid) + } + if (!geneNotFound) setActiveGeneId(params.geneid) + } + } else { + // Set active gene to first available if one is already loaded + if (genes.length > 0) { + setActiveGeneId(genes[0].id) + } else { + setActiveGeneId('') + } + } + + // Set activeview + const urlView = + views.find((view) => view.id === location.pathname.split('/')[1]) ?? + GeneInfoViewMetadata + + setActiveViewId(urlView.id) + }, []) + + // On when the activegene or view changes, update path + useEffect(() => { + const oldPathSegments = location.pathname + .split('/') + .filter((segment) => segment !== '') + + const newPathSegments = [] + if (activeViewId) { + newPathSegments.push(activeViewId) + } + if (activeGeneId) { + newPathSegments.push(activeGeneId) + } + + if (newPathSegments.length > 0) { + let newPath + if ( + oldPathSegments.length > 0 && + oldPathSegments[0] == newPathSegments[0] + ) { + // If the view is the same we want to retain quary params in url, else we can wipe + // them and have URLStateManager handle things + newPath = '/' + newPathSegments.join('/') + location.search + } else { + newPath = '/' + newPathSegments.join('/') + } + navigate(newPath) + } + }, [activeGeneId, activeViewId]) // Get view and gene objects once everything resolves const activeView = diff --git a/Eplant/main.tsx b/Eplant/main.tsx index def8e77..5260001 100644 --- a/Eplant/main.tsx +++ b/Eplant/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react' import { Provider } from 'jotai' import * as ReactDOM from 'react-dom/client' -import { createHashRouter, Navigate, RouterProvider } from 'react-router-dom' +import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' @@ -20,7 +20,7 @@ import { Config, defaultConfig } from './config' import Eplant from './Eplant' import './css/index.css' -const router = createHashRouter( +const router = createBrowserRouter( [ { path: '/', From 3e98a0432228ce7618ddd0d2863fa65049b98fdb Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 15:18:51 -0500 Subject: [PATCH 14/17] Changing entrypoint again --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index af309e3..a00036c 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + - + Eplant From 477c467980b669532bb96d5e9e5afab08d88ef9f Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 15:21:06 -0500 Subject: [PATCH 15/17] Setting homepage variable --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 571621d..9ee5b67 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Eplant", "main": "index.ts", "type": "module", + "homepage": "https://bioanalyticresource.github.io/ePlant/", "scripts": { "test": "jest --passWithNoTests", "dev": "vite", From 3f62c8cf891c359fed825bd22d1f6d661b69c34a Mon Sep 17 00:00:00 2001 From: Yukthi Date: Fri, 26 Dec 2025 15:44:39 -0500 Subject: [PATCH 16/17] Added workaround for github pages --- index.html | 20 +++++++++++++++++++- public/404.html | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 public/404.html diff --git a/index.html b/index.html index a00036c..6442196 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,25 @@ - + + + + + + Redirecting… + + + + From 352aed0f0e357298510c11b03c728fb81882d471 Mon Sep 17 00:00:00 2001 From: Yukthi Date: Mon, 5 Jan 2026 16:56:47 -0500 Subject: [PATCH 17/17] Fixing local url navigation --- index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 6442196..dc22a37 100644 --- a/index.html +++ b/index.html @@ -6,8 +6,13 @@ + Eplant