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(