From ffc4c1cf811a23d33322b8089403c198b16f8ca3 Mon Sep 17 00:00:00 2001
From: Zoe Daniels <7177924+zkdan@users.noreply.github.com>
Date: Fri, 8 Mar 2024 10:48:52 -0500
Subject: [PATCH] Revert "Remove panes (#107)"
This reverts commit 7975c06c91f81716cfb33651ab93f3149b693c47.
---
Eplant/DirectPane.tsx | 84 ++++++++
Eplant/Eplant.tsx | 2 +
Eplant/EplantLayout.tsx | 219 ++++++++++++++++---
Eplant/UI/Layout/ViewContainer/index.tsx | 9 +-
Eplant/UI/LeftNav/index.tsx | 12 +-
Eplant/UI/Sidebar.tsx | 15 +-
Eplant/View/viewData.ts | 100 +++++----
Eplant/config.ts | 7 +-
Eplant/main.tsx | 3 +-
Eplant/state/index.tsx | 254 +++++++++++------------
Eplant/views/DebugView/index.tsx | 14 +-
Eplant/views/GeneInfoView/component.tsx | 11 +-
Eplant/views/GetStartedView/Tile.tsx | 18 +-
Eplant/views/eFP/svg.tsx | 7 +-
14 files changed, 501 insertions(+), 254 deletions(-)
create mode 100644 Eplant/DirectPane.tsx
diff --git a/Eplant/DirectPane.tsx b/Eplant/DirectPane.tsx
new file mode 100644
index 00000000..01f6602e
--- /dev/null
+++ b/Eplant/DirectPane.tsx
@@ -0,0 +1,84 @@
+import { useEffect } from 'react'
+import { useSearchParams } from 'react-router-dom'
+
+import { CallReceived } from '@mui/icons-material'
+import { Box, CircularProgress, IconButton } from '@mui/material'
+import { useTheme } from '@mui/material/styles'
+
+import { useConfig } from './config'
+import { useActiveId, useModel, usePageLoad, usePanes } from './state'
+import { updateColors } from './updateColors'
+import ViewTab from './ViewTab'
+/**
+ * Directly render a pane based on its id
+ */
+function DirectPane() {
+ const [panes, panesDispatch] = usePanes()
+ const params = useSearchParams()[0]
+ const id = params.get('id') as string
+ const theme = useTheme()
+ const [activeId] = useActiveId()
+ const [model] = useModel()
+ const { tabHeight, views } = useConfig()
+ const [globalProgress, loaded] = usePageLoad()
+ useEffect(() => {
+ updateColors(theme)
+ }, [theme, loaded])
+
+ useEffect(() => {
+ if (loaded && panes[id] && model.getNodeById(id)) window.close()
+ }, [panes, loaded, model])
+
+ return loaded ? (
+
+
+
+
+ {panes[id]?.activeGene ? panes[id]?.activeGene + ' - ' : ''}
+ {views.find((v) => v.id == panes[id]?.view)?.name}
+
+
+
+ {
+ panesDispatch({ type: 'close-popout', id })
+ }}
+ >
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ )
+}
+export default DirectPane
diff --git a/Eplant/Eplant.tsx b/Eplant/Eplant.tsx
index f71f4c8c..c84e7b94 100644
--- a/Eplant/Eplant.tsx
+++ b/Eplant/Eplant.tsx
@@ -6,6 +6,7 @@ import { CssBaseline, ThemeProvider } from '@mui/material'
import { dark, light } from './css/theme'
import Sidebar from './UI/Sidebar'
import { useConfig } from './config'
+import DirectPane from './DirectPane'
import EplantLayout from './EplantLayout'
import { useDarkMode } from './state'
export type EplantProps = Record
@@ -19,6 +20,7 @@ export default function Eplant() {
} />
+ } />
diff --git a/Eplant/EplantLayout.tsx b/Eplant/EplantLayout.tsx
index b865c830..0aada4a9 100644
--- a/Eplant/EplantLayout.tsx
+++ b/Eplant/EplantLayout.tsx
@@ -1,37 +1,71 @@
-import { useEffect} from 'react'
+import { useEffect, useRef } from 'react'
+import * as FlexLayout from 'flexlayout-react'
+import {
+ Actions,
+ BorderNode,
+ ITabSetRenderValues,
+ Layout,
+ TabSetNode,
+} from 'flexlayout-react'
import { Add, CallMade, Close } from '@mui/icons-material'
import { Box, CircularProgress, IconButton } from '@mui/material'
import { useTheme } from '@mui/material/styles'
-import { ViewContainer } from './UI/Layout/ViewContainer'
+import TabsetPlaceholder from './UI/Layout/TabsetPlaceholder'
import { sidebarWidth } from './UI/Sidebar'
-import FallbackView from './views/FallbackView'
import { useConfig } from './config'
-import GeneticElement, { Species } from './GeneticElement'
import {
- pageLoad,
- useActiveGeneId,
- useActiveViewId,
- useGeneticElements,
+ getPaneName,
+ storage,
+ useActiveId,
+ useModel,
usePageLoad,
- useSpecies} from './state'
+ usePanes,
+} from './state'
import { updateColors } from './updateColors'
+import ViewTab from './ViewTab'
const EplantLayout = () => {
- const [activeGeneId, setActiveGeneId] = useActiveGeneId()
- const [activeViewId, setActiveViewId] = useActiveViewId()
-
- const [genes] = useGeneticElements()
+ const [panes, panesDispatch] = usePanes()
+ const layout = useRef(null)
+ const [activeId, setActiveId] = useActiveId()
+ const { tabHeight } = useConfig()
+ const [model, setModel] = useModel()
const theme = useTheme()
const [globalProgress, loaded] = usePageLoad()
- const config = useConfig()
+
useEffect(() => {
if (loaded) {
updateColors(theme)
}
}, [theme, loaded])
+ // Update the model when the activeId changes
+ useEffect(() => {
+ if (model.getNodeById(activeId)) model.doAction(Actions.selectTab(activeId))
+ // TODO: Need to add back if using flex-layout from Alex's fork
+ // else model.doAction(Actions.deselectTabset())
+ }, [activeId, model])
+
+ // Add a new tab when there is a non-popout pane
+ useEffect(() => {
+ if (loaded) {
+ for (const id in panes) {
+ if (panes[id].popout) continue
+ if (!model.getNodeById(id)) addTab({ tabId: id })
+ }
+ }
+ }, [panes, model, loaded])
+
+ useEffect(() => {
+ if (!loaded) return
+ const json = model.toJson()
+ if (!json.global) json.global = {}
+ json.global.tabSetTabStripHeight = tabHeight
+ setModel(FlexLayout.Model.fromJson(json))
+ }, [tabHeight, loaded])
+
return (
({
@@ -48,26 +82,52 @@ const EplantLayout = () => {
width: '100%',
height: '100%',
display: 'flex',
- alignItems: 'stretch',
- justifyContent: 'stretch',
+ alignItems: 'center',
+ justifyContent: 'center',
}}
>
{loaded ? (
- gene.id === activeGeneId) ?? null}
- view={
- config.views.find(
- (view) => view.id === (activeViewId ?? config.defaultView)
- ) ?? FallbackView
- }
- setView={(view) => {
- setActiveViewId(view.id)
+ factory(node, model)}
+ onTabSetPlaceHolder={() => (
+ addTab({})} />
+ )}
+ onModelChange={(newModel) => {
+ // Update the selected tab
+ const newId = newModel
+ .getActiveTabset()
+ ?.getSelectedNode?.()
+ ?.getId?.()
+ if (newId) setActiveId(newId)
+ storage.set('flexlayout-model', JSON.stringify(model.toJson()))
}}
- sx={{
- width: '100%',
- height: '100%',
+ onRenderTabSet={onRenderTabSet}
+ onRenderTab={(node, renderValues) => {
+ renderValues.buttons = [
+ {
+ e.stopPropagation()
+ }}
+ onTouchStart={(e) => {
+ e.stopPropagation()
+ }}
+ onMouseUp={(e) => {
+ panesDispatch({ type: 'close', id: node.getId() })
+ model.doAction(Actions.deleteTab(node.getId()))
+ }}
+ size='small'
+ >
+
+ ,
+ ]
}}
- />
+ >
) : (
{
)
+ function addTab({ tabsetId, tabId }: { tabsetId?: string; tabId?: string }) {
+ if (!layout.current) return
+
+ const id = tabId ?? (Math.random().toString(16).split('.')[1] as string)
+ const activeTab = model.getActiveTabset()?.getSelectedNode?.()?.getId?.()
+ const activeGene = activeTab ? panes[activeTab]?.activeGene ?? null : null
+ // Make a pane if a new id needs to be generated
+ // If the id is provided then assume that there already is a pane
+ if (!tabId) {
+ panesDispatch({
+ type: 'new',
+ id,
+ activeGene,
+ })
+ }
+
+ const name = panes[id] ? getPaneName(panes[id]) : 'Get started'
+
+ layout.current.addTabToTabSet(
+ tabsetId ??
+ model.getActiveTabset()?.getId?.() ??
+ model.getRoot().getChildren()[0]?.getId() ??
+ '',
+ {
+ name: name,
+ component: 'view',
+ id,
+ type: 'tab',
+ }
+ )
+ }
+
+ function makePopout(id: string) {
+ panesDispatch({
+ type: 'make-popout',
+ id,
+ })
+ const url =
+ (window.location.pathname + '/pane').replace('//', '/') + '?id=' + id
+ const pane = window.open(url, id, 'popup,width=800,height=600')
+ if (pane) {
+ model.doAction(Actions.deleteTab(id))
+ }
+ }
+
+ function onRenderTabSet(
+ node: TabSetNode | BorderNode,
+ renderValues: ITabSetRenderValues
+ ) {
+ if (node.getChildren().length == 0) return
+ renderValues.stickyButtons.push(
+ addTab({ tabsetId: node.getId() })}
+ size='small'
+ key='add-tab'
+ >
+
+
+ )
+ renderValues.buttons.push(
+ {
+ const id = node.getSelectedNode()?.getId()
+ if (id) makePopout(id)
+ }}
+ size='small'
+ key='make-popout'
+ >
+
+
+ )
+ }
+}
+export default EplantLayout
+
+/**
+ * The flexlayout factory is a function that takes a layout node and returns the React component that should be rendered there.
+ * @param node The node to render
+ * @param model The flexlayout model
+ * @returns The React component to render
+ */
+const factory: (
+ node: FlexLayout.TabNode,
+ model: FlexLayout.Model
+) => JSX.Element | undefined = (node, model) => {
+ const id = node.getId() as string
+ return (
+
+
+
+ )
}
-export default EplantLayout
\ No newline at end of file
diff --git a/Eplant/UI/Layout/ViewContainer/index.tsx b/Eplant/UI/Layout/ViewContainer/index.tsx
index 3b1263d8..5eb69a31 100644
--- a/Eplant/UI/Layout/ViewContainer/index.tsx
+++ b/Eplant/UI/Layout/ViewContainer/index.tsx
@@ -2,7 +2,7 @@ import * as React from 'react'
import { useConfig } from '@eplant/config'
import GeneticElement from '@eplant/GeneticElement'
-import { usePrinting } from '@eplant/state'
+import { usePrinting, useViewID } from '@eplant/state'
import Modal from '@eplant/UI/Modal'
import downloadFile from '@eplant/util/downloadFile'
import ErrorBoundary from '@eplant/util/ErrorBoundary'
@@ -54,14 +54,15 @@ export function ViewContainer({
const [viewingCitations, setViewingCitations] = React.useState(false)
+ const viewId = useViewID()
const { userViews, views, genericViews } = useConfig()
React.useEffect(() => {
- if (printing) {
+ if (printing == viewId) {
setTimeout(() => {
window.print()
- setPrinting(false)
+ setPrinting(null)
}, 100)
}
}, [printing])
@@ -266,7 +267,7 @@ export function ViewContainer({
borderWidth: '0px 0px 0px 1px',
borderColor: theme.palette.background.edgeLight,
flexDirection: 'column',
- ...(printing
+ ...(printing == viewId
? {
display: 'block !important',
padding: 0,
diff --git a/Eplant/UI/LeftNav/index.tsx b/Eplant/UI/LeftNav/index.tsx
index 62b138a2..e102bcfc 100644
--- a/Eplant/UI/LeftNav/index.tsx
+++ b/Eplant/UI/LeftNav/index.tsx
@@ -3,9 +3,10 @@ import _ from 'lodash'
import GeneticElement from '@eplant/GeneticElement'
import {
+ useActiveId,
useDarkMode,
useGeneticElements,
- useSetActiveGeneId,
+ usePanesDispatch,
} from '@eplant/state'
import { Box, FormControlLabel, FormGroup, Switch } from '@mui/material'
import Stack from '@mui/material/Stack'
@@ -28,7 +29,8 @@ export function LeftNav(props: {
const [geneticElements, setGeneticElements] = useGeneticElements()
const [darkMode, setDarkMode] = useDarkMode()
- const setActiveGeneId = useSetActiveGeneId()
+ const panesDispatch = usePanesDispatch()
+ const activeID = useActiveId()[0]
React.useEffect(() => {
const uniq = _.uniqBy(geneticElements, (g) => g.id)
@@ -50,7 +52,11 @@ export function LeftNav(props: {
)
)
if (s.length > 0) {
- setActiveGeneId(s[0].id)
+ panesDispatch({
+ type: 'set-active-gene',
+ id: activeID,
+ activeGene: s[0].id,
+ })
}
}}
/>
diff --git a/Eplant/UI/Sidebar.tsx b/Eplant/UI/Sidebar.tsx
index 2bc15238..9e3bd39f 100644
--- a/Eplant/UI/Sidebar.tsx
+++ b/Eplant/UI/Sidebar.tsx
@@ -1,7 +1,7 @@
import { Container } from '@mui/material'
import SerializedGeneticElement from '../GeneticElement'
-import { useActiveGeneId } from '../state'
+import { useActiveId, usePanes } from '../state'
import { LeftNav } from './LeftNav'
import ResponsiveDrawer from './ResponsiveDrawer'
@@ -9,7 +9,9 @@ import ResponsiveDrawer from './ResponsiveDrawer'
export const sidebarWidth = 300
export default function Sidebar() {
- const [activeGeneId, setActiveGeneId] = useActiveGeneId()
+ const [panes, panesDispatch] = usePanes()
+ const [activeId] = useActiveId()
+
return (
- setActiveGeneId(gene.id)
+ panesDispatch({
+ type: 'set-active-gene',
+ id: activeId,
+ activeGene: gene.id,
+ })
}
- selectedGene={activeGeneId} />
+ selectedGene={panes[activeId ?? '']?.activeGene ?? undefined}
+ />
)
diff --git a/Eplant/View/viewData.ts b/Eplant/View/viewData.ts
index c4753a0b..91259fc2 100644
--- a/Eplant/View/viewData.ts
+++ b/Eplant/View/viewData.ts
@@ -1,8 +1,8 @@
-import {SetStateAction,useEffect,useMemo} from 'react'
+import * as React from 'react'
import { atom, useAtom, WritableAtom } from 'jotai'
import GeneticElement from '@eplant/GeneticElement'
-import { atomWithStorage } from '@eplant/state'
+import { atomWithStorage, useViewID } from '@eplant/state'
import Storage from '@eplant/util/Storage'
import { View, ViewDispatch } from './index'
@@ -40,13 +40,13 @@ export type UseViewDataType = ViewDataType & {
const viewDataAtoms: {
[key: string]: WritableAtom<
ViewDataType,
- [SetStateAction>],
+ [React.SetStateAction>],
void
>
} = {}
const viewStateAtoms: {
- [key: string]: WritableAtom], void>
+ [key: string]: WritableAtom], void>
} = {}
function getViewAtom(key: string) {
@@ -86,67 +86,65 @@ export function useViewData(
gene: GeneticElement | null
): UseViewDataType {
const key = getViewDataKey(view.id, gene)
- const id = view?.id ?? 'generic-view'
+ const id = (view?.id ?? 'generic-view') + '-' + useViewID()
const [viewData, setViewData] = useAtom(getViewAtom(key))
const [viewState, setViewState] = useAtom(getViewStateAtom(id))
// console.log(viewData)
// If there is no cached viewData then load it using the view's loader
- const loadData = async () => {
- const loader = await gene?.species.api.loaders[view.id] ?? view.getInitialData;
- // Guaranteed to work even though types are broken because if gene is null then view.getInitialData (which accepts null) is always used
- try {
- // If there already is data then don't load it again
- if (viewData.activeData !== undefined) return
- if (!loader) {
- throw ViewDataError.UNSUPPORTED_GENE
- }
- setViewData({
- ...defaultViewData,
- loading: true,
- })
- const data = await loader(gene as GeneticElement, (amount) => {
- setViewData((data) => {
- return {
- ...data,
- loadingAmount: Math.max(amount, data.loadingAmount),
- }
- })
- })
- const newData = {
- ...defaultViewData,
- activeData: data ?? null,
- loading: false,
- }
- setViewData(newData)
- viewDataStorage.set(key, newData)
- } catch (e) {
- const errorData = {
- ...defaultViewData,
- error:
- e instanceof Error
- ? ViewDataError.FAILED_TO_LOAD
- : (e as ViewDataError),
- loading: false,
- }
- setViewData(errorData)
- }
- }
-
- useEffect(() => {
+ React.useEffect(() => {
// Don't load data if the view data and they key aren't synced
if (!viewData.loading && !viewData.error) {
- loadData();
+ const loader = gene?.species.api.loaders[view.id] ?? view.getInitialData
+ ;(async () => {
+ // Guaranteed to work even though types are broken because if gene is null then view.getInitialData (which accepts null) is always used
+ try {
+ // If there already is data then don't load it again
+ if (viewData.activeData !== undefined) return
+ if (!loader) {
+ throw ViewDataError.UNSUPPORTED_GENE
+ }
+ setViewData({
+ ...defaultViewData,
+ loading: true,
+ })
+ const data = await loader(gene as GeneticElement, (amount) => {
+ setViewData((data) => {
+ return {
+ ...data,
+ loadingAmount: Math.max(amount, data.loadingAmount),
+ }
+ })
+ })
+ const newData = {
+ ...defaultViewData,
+ activeData: data ?? null,
+ loading: false,
+ }
+ setViewData(newData)
+ viewDataStorage.set(key, newData)
+ } catch (e) {
+ const newData = {
+ ...defaultViewData,
+ error:
+ e instanceof Error
+ ? ViewDataError.FAILED_TO_LOAD
+ : (e as ViewDataError),
+ loading: false,
+ }
+ setViewData(newData)
+ }
+ })()
}
- }, [viewData, loadData])
+ }, [viewData, id, gene?.id, view.id])
- useEffect(() => {
+ React.useEffect(() => {
if (viewState === undefined) {
setViewState(view.getInitialState?.() ?? null)
}
}, [id])
- const dispatch = useMemo(
+ const dispatch = React.useMemo(
() => (action: A) => {
setViewState((viewState: S) =>
view.reducer && viewState !== undefined
diff --git a/Eplant/config.ts b/Eplant/config.ts
index 7eea9c77..078394c4 100644
--- a/Eplant/config.ts
+++ b/Eplant/config.ts
@@ -6,19 +6,16 @@ export type EplantConfig = {
readonly genericViews: View[]
readonly userViews: View[]
readonly views: View[]
+ readonly tabHeight: number
readonly rootPath: string
- readonly defaultView:string
- readonly defaultSpecies:string
-
}
export const Config = React.createContext({
genericViews: [],
userViews: [],
views: [],
+ tabHeight: 48,
rootPath: '',
- defaultView:'gene-info',
- defaultSpecies:''
})
export const useConfig = () => React.useContext(Config)
diff --git a/Eplant/main.tsx b/Eplant/main.tsx
index db872e0f..783f0a03 100644
--- a/Eplant/main.tsx
+++ b/Eplant/main.tsx
@@ -57,8 +57,7 @@ function RootApp() {
)
}
-const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
-root.render(
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
)
diff --git a/Eplant/state/index.tsx b/Eplant/state/index.tsx
index bfe43c4a..df62b2a7 100644
--- a/Eplant/state/index.tsx
+++ b/Eplant/state/index.tsx
@@ -192,110 +192,102 @@ export const speciesAtom = atom([arabidopsis])
export const useSpecies = () => useAtom(speciesAtom)
export const useSetSpecies = () => useSetAtom(speciesAtom)
-// type Panes = {
-// [id: string]: {
-// view: string
-// activeGene: string | null
-// popout: boolean
-// }
-// }
+type Panes = {
+ [id: string]: {
+ view: string
+ activeGene: string | null
+ popout: boolean
+ }
+}
-// type PanesAction =
-// | { type: 'set-view'; id: string; view: string }
-// | { type: 'set-active-gene'; id: string; activeGene: string | null }
-// | { type: 'make-popout'; id: string }
-// | { type: 'close-popout'; id: string }
-// | { type: 'new'; id: string; activeGene: string | null }
-// | { type: 'close'; id: string }
-// // All open views, and genes if they are associated
-// export const panesAtom = atomWithOptionalStorage('open-views', {
-// default: {
-// activeGene: null,
-// view: 'get-started',
-// popout: false,
-// },
-// })
+type PanesAction =
+ | { type: 'set-view'; id: string; view: string }
+ | { type: 'set-active-gene'; id: string; activeGene: string | null }
+ | { type: 'make-popout'; id: string }
+ | { type: 'close-popout'; id: string }
+ | { type: 'new'; id: string; activeGene: string | null }
+ | { type: 'close'; id: string }
+// All open views, and genes if they are associated
+export const panesAtom = atomWithOptionalStorage('open-views', {
+ default: {
+ activeGene: null,
+ view: 'get-started',
+ popout: false,
+ },
+})
// TODO: Test this
-// export const panesReducer: (prev: Panes, action: PanesAction) => Panes = (
-// prev: Panes,
-// action: PanesAction
-// ) => {
-// const def = {
-// view: 'get-started',
-// activeGene: null,
-// popout: false,
-// }
-// const { [action.id]: _, ...rest } = prev
-// switch (action.type) {
-// case 'set-view':
-// return {
-// ...prev,
-// [action.id]: {
-// ...def,
-// ...prev[action.id],
-// view: action.view,
-// },
-// }
-// case 'set-active-gene':
-// return {
-// ...prev,
-// [action.id]: {
-// ...def,
-// ...prev[action.id],
-// activeGene: action.activeGene,
-// },
-// }
-// case 'make-popout':
-// return {
-// ...prev,
-// [action.id]: {
-// ...def,
-// ...prev[action.id],
-// popout: true,
-// },
-// }
-// case 'close-popout':
-// return {
-// ...prev,
-// [action.id]: {
-// ...def,
-// ...prev[action.id],
-// popout: false,
-// },
-// }
-// case 'new':
-// return {
-// ...prev,
-// [action.id]: {
-// ...def,
-// activeGene: action.activeGene,
-// },
-// }
-// case 'close':
-// return rest
-// }
-// }
-
-// export const usePanes = () =>
-// [useAtomValue(panesAtom), useAtomReducer(panesAtom, panesReducer)] as [
-// Panes,
-// (action: PanesAction) => void,
-// ]
-// export const usePanesDispatch = () => useAtomReducer(panesAtom, panesReducer)
-
-const activeGeneIdAtom = atom('')
-export const useActiveGeneId = () => useAtom(activeGeneIdAtom)
-export const useSetActiveGeneId = () => useSetAtom(activeGeneIdAtom)
+export const panesReducer: (prev: Panes, action: PanesAction) => Panes = (
+ prev: Panes,
+ action: PanesAction
+) => {
+ const def = {
+ view: 'get-started',
+ activeGene: null,
+ popout: false,
+ }
+ const { [action.id]: _, ...rest } = prev
+ switch (action.type) {
+ case 'set-view':
+ return {
+ ...prev,
+ [action.id]: {
+ ...def,
+ ...prev[action.id],
+ view: action.view,
+ },
+ }
+ case 'set-active-gene':
+ return {
+ ...prev,
+ [action.id]: {
+ ...def,
+ ...prev[action.id],
+ activeGene: action.activeGene,
+ },
+ }
+ case 'make-popout':
+ return {
+ ...prev,
+ [action.id]: {
+ ...def,
+ ...prev[action.id],
+ popout: true,
+ },
+ }
+ case 'close-popout':
+ return {
+ ...prev,
+ [action.id]: {
+ ...def,
+ ...prev[action.id],
+ popout: false,
+ },
+ }
+ case 'new':
+ return {
+ ...prev,
+ [action.id]: {
+ ...def,
+ activeGene: action.activeGene,
+ },
+ }
+ case 'close':
+ return rest
+ }
+}
-const activeViewIdAtom = atom('gene-info')
-export const useActiveViewId = () => useAtom(activeViewIdAtom)
-export const useSetActiveViewId = () => useSetAtom(activeViewIdAtom)
+export const usePanes = () =>
+ [useAtomValue(panesAtom), useAtomReducer(panesAtom, panesReducer)] as [
+ Panes,
+ (action: PanesAction) => void,
+ ]
+export const usePanesDispatch = () => useAtomReducer(panesAtom, panesReducer)
export const ViewIDContext = createContext('')
export const useViewID = () => useContext(ViewIDContext)
-export const printingAtom = atom(false)
+export const printingAtom = atom(null)
export const usePrinting = () => useAtom(printingAtom)
export const useSetPrinting = () => useSetAtom(printingAtom)
@@ -306,39 +298,39 @@ export const useSetDarkMode = () => useSetAtom(darkModeAtom)
export const activeIdAtom = atomWithOptionalStorage('active-id', '')
export const useActiveId = () => useAtom(activeIdAtom)
-// export function getPaneName(pane: Panes[string]) {
-// return `${pane.activeGene ? pane.activeGene + ' - ' : ''}${pane.view}`
-// }
+export function getPaneName(pane: Panes[string]) {
+ return `${pane.activeGene ? pane.activeGene + ' - ' : ''}${pane.view}`
+}
-// export const modelAtom = atomWithOptionalStorage(
-// 'flexlayout-model',
-// FlexLayout.Model.fromJson({
-// global: {
-// tabSetTabStripHeight: 48,
-// tabEnableRename: false,
-// tabEnableClose: false,
-// tabSetEnableMaximize: false,
-// },
-// borders: [],
-// layout: {
-// type: 'row',
-// weight: 100,
-// children: [
-// {
-// type: 'tabset',
-// active: true,
-// children: [
-// {
-// type: 'tab',
-// id: 'default',
-// },
-// ],
-// },
-// ],
-// },
-// }),
-// (model) => JSON.stringify(model.toJson()),
-// (model) => FlexLayout.Model.fromJson(JSON.parse(model))
-// )
-// export const useModel = () => useAtom(modelAtom)
-// export const useSetModel = () => useSetAtom(modelAtom)
+export const modelAtom = atomWithOptionalStorage(
+ 'flexlayout-model',
+ FlexLayout.Model.fromJson({
+ global: {
+ tabSetTabStripHeight: 48,
+ tabEnableRename: false,
+ tabEnableClose: false,
+ tabSetEnableMaximize: false,
+ },
+ borders: [],
+ layout: {
+ type: 'row',
+ weight: 100,
+ children: [
+ {
+ type: 'tabset',
+ active: true,
+ children: [
+ {
+ type: 'tab',
+ id: 'default',
+ },
+ ],
+ },
+ ],
+ },
+ }),
+ (model) => JSON.stringify(model.toJson()),
+ (model) => FlexLayout.Model.fromJson(JSON.parse(model))
+)
+export const useModel = () => useAtom(modelAtom)
+export const useSetModel = () => useSetAtom(modelAtom)
diff --git a/Eplant/views/DebugView/index.tsx b/Eplant/views/DebugView/index.tsx
index 221c9f20..83466953 100644
--- a/Eplant/views/DebugView/index.tsx
+++ b/Eplant/views/DebugView/index.tsx
@@ -1,4 +1,6 @@
-import { useActiveGeneId, useActiveViewId } from '@eplant/state'
+import React from 'react'
+
+import { useViewID } from '@eplant/state'
import { viewDataStorage, viewStateStorage } from '@eplant/View/viewData'
import { BugReportOutlined } from '@mui/icons-material'
import {
@@ -25,8 +27,7 @@ const DebugView: View = {
testToggle: false,
}),
component: (props) => {
- const [activeViewId, setActiveViewId] = useActiveViewId()
- const [activeGeneId, setActiveGeneId] = useActiveGeneId()
+ const viewID = useViewID()
return (
@@ -45,11 +46,8 @@ const DebugView: View = {
{props.state.testToggle ? 'true' : 'false'}
- {activeViewId}
-
-
- Gene ID
- {activeGeneId}
+ View ID
+ {viewID}
diff --git a/Eplant/views/GeneInfoView/component.tsx b/Eplant/views/GeneInfoView/component.tsx
index 2ea6a600..155f7ae3 100644
--- a/Eplant/views/GeneInfoView/component.tsx
+++ b/Eplant/views/GeneInfoView/component.tsx
@@ -3,7 +3,7 @@ import _ from 'lodash'
import { useConfig } from '@eplant/config'
import GeneticElement from '@eplant/GeneticElement'
-import { useSetActiveViewId } from '@eplant/state'
+import { usePanesDispatch, useViewID } from '@eplant/state'
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import {
Alert,
@@ -318,7 +318,8 @@ export default function GeneInfoViewer({
)
}
function ViewSwitcher({ geneticElement }: { geneticElement: GeneticElement }) {
- const setActiveViewId = useSetActiveViewId()
+ const id = useViewID()
+ const panesDispatch = usePanesDispatch()
const { userViews } = useConfig()
return (
@@ -379,6 +380,10 @@ function ViewSwitcher({ geneticElement }: { geneticElement: GeneticElement }) {
)
function switchViews(view: View) {
- setActiveViewId(view.id)
+ panesDispatch({
+ type: 'set-view',
+ id: id,
+ view: view.id,
+ })
}
}
diff --git a/Eplant/views/GetStartedView/Tile.tsx b/Eplant/views/GetStartedView/Tile.tsx
index 24bfe8bb..043db0c1 100644
--- a/Eplant/views/GetStartedView/Tile.tsx
+++ b/Eplant/views/GetStartedView/Tile.tsx
@@ -1,11 +1,7 @@
import React from 'react'
import arabidopsis from '@eplant/Species/arabidopsis'
-import {
- useGeneticElements,
- useSetActiveGeneId,
- useSetActiveViewId,
-} from '@eplant/state'
+import { useGeneticElements, usePanesDispatch, useViewID } from '@eplant/state'
import { View } from '@eplant/View'
import {
Button,
@@ -21,15 +17,19 @@ export type TileProps = {
}
export default function Tile({ view }: TileProps) {
- const setActiveViewId = useSetActiveViewId()
- const setActiveGeneId = useSetActiveGeneId()
+ const id = useViewID()
+ const panesDispatch = usePanesDispatch()
const [genes, setGenes] = useGeneticElements()
async function setView() {
const ABI3 = await arabidopsis.api.searchGene('AT3G24650')
if (ABI3) {
- setActiveGeneId(ABI3.id)
+ setGenes([ABI3, ...genes])
}
- setActiveViewId(view.id)
+ panesDispatch({
+ type: 'set-view',
+ id: id,
+ view: view.id,
+ })
}
return (
{
+ return React.useMemo(() => {
if (!cache[view.id]) return { view: null, loading: true }
const parser = new DOMParser()
const svg = parser.parseFromString(cache[view.id].svg, 'text/xml')
@@ -78,7 +77,7 @@ export const useEFPSVG = (
}
return { view: out, loading: false }
- }, [svgInformation, view.id, options.showText])
+ }, [...Object.values(cache[view.id] ?? {}), view.id, options.showText])
}
export function getColor(