aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src')
-rw-r--r--subprojects/frontend/src/DirectionalSplitPane.tsx159
-rw-r--r--subprojects/frontend/src/PaneButtons.tsx96
-rw-r--r--subprojects/frontend/src/Refinery.tsx142
-rw-r--r--subprojects/frontend/src/TopBar.tsx91
-rw-r--r--subprojects/frontend/src/WorkArea.tsx33
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx2
-rw-r--r--subprojects/frontend/src/table/TableArea.tsx9
-rw-r--r--subprojects/frontend/src/table/TablePane.tsx28
-rw-r--r--subprojects/frontend/src/theme/ThemeStore.ts48
9 files changed, 413 insertions, 195 deletions
diff --git a/subprojects/frontend/src/DirectionalSplitPane.tsx b/subprojects/frontend/src/DirectionalSplitPane.tsx
new file mode 100644
index 00000000..59c8b739
--- /dev/null
+++ b/subprojects/frontend/src/DirectionalSplitPane.tsx
@@ -0,0 +1,159 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
8import MoreVertIcon from '@mui/icons-material/MoreVert';
9import Box from '@mui/material/Box';
10import Stack from '@mui/material/Stack';
11import { alpha, useTheme } from '@mui/material/styles';
12import { useCallback, useRef, useState } from 'react';
13import { useResizeDetector } from 'react-resize-detector';
14
15export default function DirectionalSplitPane({
16 primary: left,
17 secondary: right,
18 primaryOnly: showLeftOnly,
19 secondaryOnly: showRightOnly,
20}: {
21 primary: React.ReactNode;
22 secondary: React.ReactNode;
23 primaryOnly?: boolean;
24 secondaryOnly?: boolean;
25}): JSX.Element {
26 const theme = useTheme();
27 const stackRef = useRef<HTMLDivElement | null>(null);
28 const { ref: resizeRef, width, height } = useResizeDetector();
29 const sliderRef = useRef<HTMLDivElement>(null);
30 const [resizing, setResizing] = useState(false);
31 const [fraction, setFraction] = useState(0.5);
32
33 const horizontalSplit =
34 width !== undefined && height !== undefined && height > width;
35 const direction = horizontalSplit ? 'column' : 'row';
36 const axis = horizontalSplit ? 'height' : 'width';
37 const primarySize = showLeftOnly
38 ? '100%'
39 : `calc(${fraction * 100}% - 0.5px)`;
40 const secondarySize = showRightOnly
41 ? '100%'
42 : `calc(${(1 - fraction) * 100}% - 0.5px)`;
43 const ref = useCallback(
44 (element: HTMLDivElement | null) => {
45 resizeRef(element);
46 stackRef.current = element;
47 },
48 [resizeRef],
49 );
50
51 return (
52 <Stack
53 direction={direction}
54 height="100%"
55 width="100%"
56 overflow="hidden"
57 ref={ref}
58 >
59 {!showRightOnly && <Box {...{ [axis]: primarySize }}>{left}</Box>}
60 <Box
61 sx={{
62 overflow: 'visible',
63 position: 'relative',
64 [axis]: '0px',
65 display: showLeftOnly || showRightOnly ? 'none' : 'flex',
66 flexDirection: direction,
67 [horizontalSplit
68 ? 'borderBottom'
69 : 'borderRight']: `1px solid ${theme.palette.outer.border}`,
70 }}
71 >
72 <Box
73 ref={sliderRef}
74 sx={{
75 display: 'flex',
76 position: 'absolute',
77 [axis]: theme.spacing(2),
78 ...(horizontalSplit
79 ? {
80 top: theme.spacing(-1),
81 left: 0,
82 right: 0,
83 transform: 'translateY(0.5px)',
84 }
85 : {
86 left: theme.spacing(-1),
87 top: 0,
88 bottom: 0,
89 transform: 'translateX(0.5px)',
90 }),
91 zIndex: 999,
92 alignItems: 'center',
93 justifyContent: 'center',
94 color: theme.palette.text.secondary,
95 cursor: horizontalSplit ? 'ns-resize' : 'ew-resize',
96 '.MuiSvgIcon-root': {
97 opacity: resizing ? 1 : 0,
98 },
99 ...(resizing
100 ? {
101 background: alpha(
102 theme.palette.text.primary,
103 theme.palette.action.activatedOpacity,
104 ),
105 }
106 : {
107 '&:hover': {
108 background: alpha(
109 theme.palette.text.primary,
110 theme.palette.action.hoverOpacity,
111 ),
112 '.MuiSvgIcon-root': {
113 opacity: 1,
114 },
115 },
116 }),
117 }}
118 onPointerDown={(event) => {
119 if (event.button !== 0) {
120 return;
121 }
122 sliderRef.current?.setPointerCapture(event.pointerId);
123 setResizing(true);
124 }}
125 onPointerUp={(event) => {
126 if (event.button !== 0) {
127 return;
128 }
129 sliderRef.current?.releasePointerCapture(event.pointerId);
130 setResizing(false);
131 }}
132 onPointerMove={(event) => {
133 if (!resizing) {
134 return;
135 }
136 const container = stackRef.current;
137 if (container === null) {
138 return;
139 }
140 const rect = container.getBoundingClientRect();
141 const newFraction = horizontalSplit
142 ? (event.clientY - rect.top) / rect.height
143 : (event.clientX - rect.left) / rect.width;
144 setFraction(Math.min(0.9, Math.max(0.1, newFraction)));
145 }}
146 onDoubleClick={() => setFraction(0.5)}
147 >
148 {horizontalSplit ? <MoreHorizIcon /> : <MoreVertIcon />}
149 </Box>
150 </Box>
151 {!showLeftOnly && <Box {...{ [axis]: secondarySize }}>{right}</Box>}
152 </Stack>
153 );
154}
155
156DirectionalSplitPane.defaultProps = {
157 primaryOnly: false,
158 secondaryOnly: false,
159};
diff --git a/subprojects/frontend/src/PaneButtons.tsx b/subprojects/frontend/src/PaneButtons.tsx
new file mode 100644
index 00000000..7b83171d
--- /dev/null
+++ b/subprojects/frontend/src/PaneButtons.tsx
@@ -0,0 +1,96 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import CodeIcon from '@mui/icons-material/Code';
8import SchemaRoundedIcon from '@mui/icons-material/SchemaRounded';
9import TableChartIcon from '@mui/icons-material/TableChart';
10import ToggleButton from '@mui/material/ToggleButton';
11import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
12import { observer } from 'mobx-react-lite';
13
14import type ThemeStore from './theme/ThemeStore';
15
16function PaneButtons({
17 themeStore,
18 hideLabel,
19}: {
20 themeStore: ThemeStore;
21 hideLabel?: boolean;
22}): JSX.Element {
23 return (
24 <ToggleButtonGroup
25 size={hideLabel ? 'small' : 'medium'}
26 className="rounded"
27 sx={{
28 '.MuiToggleButton-root': {
29 ...(hideLabel
30 ? {}
31 : {
32 paddingBlock: '6px',
33 }),
34 fontSize: '1rem',
35 lineHeight: '1.5',
36 },
37 ...(hideLabel
38 ? {}
39 : {
40 '& svg': {
41 margin: '0 6px 0 -4px',
42 },
43 }),
44 }}
45 >
46 <ToggleButton
47 value="code"
48 selected={themeStore.showCode}
49 onClick={(event) => {
50 if (event.shiftKey || event.ctrlKey) {
51 themeStore.setSelectedPane('code');
52 } else {
53 themeStore.toggleCode();
54 }
55 }}
56 >
57 <CodeIcon fontSize="small" />
58 {!hideLabel && 'Code'}
59 </ToggleButton>
60 <ToggleButton
61 value="graph"
62 selected={themeStore.showGraph}
63 onClick={(event) => {
64 if (event.shiftKey || event.ctrlKey) {
65 themeStore.setSelectedPane('graph');
66 } else {
67 themeStore.toggleGraph();
68 }
69 }}
70 >
71 <SchemaRoundedIcon fontSize="small" />
72 {!hideLabel && 'Graph'}
73 </ToggleButton>
74 <ToggleButton
75 value="table"
76 selected={themeStore.showTable}
77 onClick={(event) => {
78 if (event.shiftKey || event.ctrlKey) {
79 themeStore.setSelectedPane('table');
80 } else {
81 themeStore.toggleTable();
82 }
83 }}
84 >
85 <TableChartIcon fontSize="small" />
86 {!hideLabel && 'Table'}
87 </ToggleButton>
88 </ToggleButtonGroup>
89 );
90}
91
92PaneButtons.defaultProps = {
93 hideLabel: false,
94};
95
96export default observer(PaneButtons);
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx
index 099646f0..5ad16000 100644
--- a/subprojects/frontend/src/Refinery.tsx
+++ b/subprojects/frontend/src/Refinery.tsx
@@ -4,151 +4,13 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
8import MoreVertIcon from '@mui/icons-material/MoreVert';
9import Box from '@mui/material/Box';
10import Grow from '@mui/material/Grow'; 7import Grow from '@mui/material/Grow';
11import Stack from '@mui/material/Stack'; 8import Stack from '@mui/material/Stack';
12import { alpha, useTheme } from '@mui/material/styles';
13import { SnackbarProvider } from 'notistack'; 9import { SnackbarProvider } from 'notistack';
14import { memo, useRef, useState } from 'react';
15import { useResizeDetector } from 'react-resize-detector';
16 10
17import TopBar from './TopBar'; 11import TopBar from './TopBar';
18import UpdateNotification from './UpdateNotification'; 12import UpdateNotification from './UpdateNotification';
19import EditorPane from './editor/EditorPane'; 13import WorkArea from './WorkArea';
20import GraphPane from './graph/GraphPane';
21
22const DirectionalSplitPane = memo(function SplitPanel({
23 horizontalSplit,
24}: {
25 horizontalSplit: boolean;
26}): JSX.Element {
27 const theme = useTheme();
28 const stackRef = useRef<HTMLDivElement>(null);
29 const sliderRef = useRef<HTMLDivElement>(null);
30 const [resizing, setResizing] = useState(false);
31 const [fraction, setFraction] = useState(0.5);
32
33 const direction = horizontalSplit ? 'column' : 'row';
34 const axis = horizontalSplit ? 'height' : 'width';
35 const primarySize = `calc(${fraction * 100}% - 0.5px)`;
36 const secondarySize = `calc(${(1 - fraction) * 100}% - 0.5px)`;
37
38 return (
39 <Stack direction={direction} height="100%" overflow="hidden" ref={stackRef}>
40 <Box {...{ [axis]: primarySize }}>
41 <EditorPane />
42 </Box>
43 <Box
44 sx={{
45 overflow: 'visible',
46 position: 'relative',
47 [axis]: '0px',
48 display: 'flex',
49 flexDirection: direction,
50 [horizontalSplit
51 ? 'borderBottom'
52 : 'borderRight']: `1px solid ${theme.palette.outer.border}`,
53 }}
54 >
55 <Box
56 ref={sliderRef}
57 sx={{
58 display: 'flex',
59 position: 'absolute',
60 [axis]: theme.spacing(2),
61 ...(horizontalSplit
62 ? {
63 top: theme.spacing(-1),
64 left: 0,
65 right: 0,
66 transform: 'translateY(0.5px)',
67 }
68 : {
69 left: theme.spacing(-1),
70 top: 0,
71 bottom: 0,
72 transform: 'translateX(0.5px)',
73 }),
74 zIndex: 999,
75 alignItems: 'center',
76 justifyContent: 'center',
77 color: theme.palette.text.secondary,
78 cursor: horizontalSplit ? 'ns-resize' : 'ew-resize',
79 '.MuiSvgIcon-root': {
80 opacity: resizing ? 1 : 0,
81 },
82 ...(resizing
83 ? {
84 background: alpha(
85 theme.palette.text.primary,
86 theme.palette.action.activatedOpacity,
87 ),
88 }
89 : {
90 '&:hover': {
91 background: alpha(
92 theme.palette.text.primary,
93 theme.palette.action.hoverOpacity,
94 ),
95 '.MuiSvgIcon-root': {
96 opacity: 1,
97 },
98 },
99 }),
100 }}
101 onPointerDown={(event) => {
102 if (event.button !== 0) {
103 return;
104 }
105 sliderRef.current?.setPointerCapture(event.pointerId);
106 setResizing(true);
107 }}
108 onPointerUp={(event) => {
109 if (event.button !== 0) {
110 return;
111 }
112 sliderRef.current?.releasePointerCapture(event.pointerId);
113 setResizing(false);
114 }}
115 onPointerMove={(event) => {
116 if (!resizing) {
117 return;
118 }
119 const container = stackRef.current;
120 if (container === null) {
121 return;
122 }
123 const rect = container.getBoundingClientRect();
124 const newFraction = horizontalSplit
125 ? (event.clientY - rect.top) / rect.height
126 : (event.clientX - rect.left) / rect.width;
127 setFraction(Math.min(0.9, Math.max(0.1, newFraction)));
128 }}
129 onDoubleClick={() => setFraction(0.5)}
130 >
131 {horizontalSplit ? <MoreHorizIcon /> : <MoreVertIcon />}
132 </Box>
133 </Box>
134 <Box {...{ [axis]: secondarySize }}>
135 <GraphPane />
136 </Box>
137 </Stack>
138 );
139});
140
141function SplitPane(): JSX.Element {
142 const { ref, width, height } = useResizeDetector();
143 const horizontalSplit =
144 width !== undefined && height !== undefined && height > width;
145
146 return (
147 <Box height="100%" overflow="auto" ref={ref}>
148 <DirectionalSplitPane horizontalSplit={horizontalSplit} />
149 </Box>
150 );
151}
152 14
153export default function Refinery(): JSX.Element { 15export default function Refinery(): JSX.Element {
154 return ( 16 return (
@@ -156,7 +18,7 @@ export default function Refinery(): JSX.Element {
156 <UpdateNotification /> 18 <UpdateNotification />
157 <Stack direction="column" height="100%" overflow="auto"> 19 <Stack direction="column" height="100%" overflow="auto">
158 <TopBar /> 20 <TopBar />
159 <SplitPane /> 21 <WorkArea />
160 </Stack> 22 </Stack>
161 </SnackbarProvider> 23 </SnackbarProvider>
162 ); 24 );
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx
index c722c203..e6350080 100644
--- a/subprojects/frontend/src/TopBar.tsx
+++ b/subprojects/frontend/src/TopBar.tsx
@@ -6,7 +6,6 @@
6 6
7import GitHubIcon from '@mui/icons-material/GitHub'; 7import GitHubIcon from '@mui/icons-material/GitHub';
8import AppBar from '@mui/material/AppBar'; 8import AppBar from '@mui/material/AppBar';
9import Button from '@mui/material/Button';
10import IconButton from '@mui/material/IconButton'; 9import IconButton from '@mui/material/IconButton';
11import Stack from '@mui/material/Stack'; 10import Stack from '@mui/material/Stack';
12import Toolbar from '@mui/material/Toolbar'; 11import Toolbar from '@mui/material/Toolbar';
@@ -17,8 +16,10 @@ import { throttle } from 'lodash-es';
17import { observer } from 'mobx-react-lite'; 16import { observer } from 'mobx-react-lite';
18import { useEffect, useMemo, useState } from 'react'; 17import { useEffect, useMemo, useState } from 'react';
19 18
19import PaneButtons from './PaneButtons';
20import { useRootStore } from './RootStoreProvider'; 20import { useRootStore } from './RootStoreProvider';
21import ToggleDarkModeButton from './ToggleDarkModeButton'; 21import ToggleDarkModeButton from './ToggleDarkModeButton';
22import ConnectButton from './editor/ConnectButton';
22import GenerateButton from './editor/GenerateButton'; 23import GenerateButton from './editor/GenerateButton';
23 24
24function useWindowControlsOverlayVisible(): boolean { 25function useWindowControlsOverlayVisible(): boolean {
@@ -65,11 +66,12 @@ const DevModeBadge = styled('div')(({ theme }) => ({
65})); 66}));
66 67
67export default observer(function TopBar(): JSX.Element { 68export default observer(function TopBar(): JSX.Element {
68 const { editorStore } = useRootStore(); 69 const { editorStore, themeStore } = useRootStore();
69 const overlayVisible = useWindowControlsOverlayVisible(); 70 const overlayVisible = useWindowControlsOverlayVisible();
70 const { breakpoints } = useTheme(); 71 const { breakpoints } = useTheme();
71 const small = useMediaQuery(breakpoints.down('sm')); 72 const medium = useMediaQuery(breakpoints.up('sm'));
72 const large = useMediaQuery(breakpoints.up('md')); 73 const large = useMediaQuery(breakpoints.up('md'));
74 const veryLarge = useMediaQuery(breakpoints.up('lg'));
73 75
74 return ( 76 return (
75 <AppBar 77 <AppBar
@@ -100,59 +102,42 @@ export default observer(function TopBar(): JSX.Element {
100 py: 0.5, 102 py: 0.5,
101 }} 103 }}
102 > 104 >
103 <Typography variant="h6" component="h1" flexGrow={1}> 105 <Typography variant="h6" component="h1">
104 Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>} 106 Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>}
105 </Typography> 107 </Typography>
106 <Stack direction="row" marginRight={1}> 108 <Stack direction="row" flexGrow={1} marginLeft={1} gap={1}>
107 <GenerateButton editorStore={editorStore} hideWarnings={small} /> 109 <ConnectButton editorStore={editorStore} />
110 {medium && !large && (
111 <PaneButtons themeStore={themeStore} hideLabel />
112 )}
113 </Stack>
114 {large && (
115 <Stack
116 direction="row"
117 alignItems="center"
118 sx={{
119 py: 0.5,
120 position: 'absolute',
121 top: 0,
122 bottom: 0,
123 left: '50%',
124 transform: 'translateX(-50%)',
125 }}
126 >
127 <PaneButtons themeStore={themeStore} />
128 </Stack>
129 )}
130 <Stack direction="row" marginLeft={1} marginRight={1} gap={1}>
131 <GenerateButton editorStore={editorStore} hideWarnings={!veryLarge} />
108 {large && ( 132 {large && (
109 <> 133 <IconButton
110 <Button 134 aria-label="GitHub"
111 arial-label="Budapest University of Technology and Economics, Critical Systems Research Group" 135 href="https://github.com/graphs4value/refinery"
112 className="rounded" 136 target="_blank"
113 color="inherit" 137 color="inherit"
114 href="https://ftsrg.mit.bme.hu" 138 >
115 target="_blank" 139 <GitHubIcon />
116 sx={{ marginLeft: 1 }} 140 </IconButton>
117 >
118 BME FTSRG
119 </Button>
120 <Button
121 aria-label="McGill University, Department of Electrical and Computer Engineering"
122 className="rounded"
123 color="inherit"
124 href="https://www.mcgill.ca/ece/daniel-varro"
125 target="_blank"
126 >
127 M<span style={{ textTransform: 'none' }}>c</span>Gill ECE
128 </Button>
129 <Button
130 aria-label="Linkönping University, Department of Computer and Information Science"
131 className="rounded"
132 color="inherit"
133 href="https://liu.se/en/employee/danva91"
134 target="_blank"
135 >
136 L<span style={{ textTransform: 'none' }}>i</span>U IDA
137 </Button>
138 <Button
139 aria-label="2022 Amazon Research Awards recipent"
140 className="rounded"
141 color="inherit"
142 href="https://www.amazon.science/research-awards/recipients/daniel-varro-fall-2021"
143 target="_blank"
144 >
145 Amazon Science
146 </Button>
147 <IconButton
148 aria-label="GitHub"
149 href="https://github.com/graphs4value/refinery"
150 target="_blank"
151 color="inherit"
152 >
153 <GitHubIcon />
154 </IconButton>
155 </>
156 )} 141 )}
157 </Stack> 142 </Stack>
158 <ToggleDarkModeButton /> 143 <ToggleDarkModeButton />
diff --git a/subprojects/frontend/src/WorkArea.tsx b/subprojects/frontend/src/WorkArea.tsx
new file mode 100644
index 00000000..adb29a50
--- /dev/null
+++ b/subprojects/frontend/src/WorkArea.tsx
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { observer } from 'mobx-react-lite';
8
9import DirectionalSplitPane from './DirectionalSplitPane';
10import { useRootStore } from './RootStoreProvider';
11import EditorPane from './editor/EditorPane';
12import GraphPane from './graph/GraphPane';
13import TablePane from './table/TablePane';
14
15export default observer(function WorkArea(): JSX.Element {
16 const { themeStore } = useRootStore();
17
18 return (
19 <DirectionalSplitPane
20 primary={<EditorPane />}
21 secondary={
22 <DirectionalSplitPane
23 primary={<GraphPane />}
24 secondary={<TablePane />}
25 primaryOnly={!themeStore.showTable}
26 secondaryOnly={!themeStore.showGraph}
27 />
28 }
29 primaryOnly={!themeStore.showGraph && !themeStore.showTable}
30 secondaryOnly={!themeStore.showCode}
31 />
32 );
33});
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index ca51f975..a7ac2f85 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -20,7 +20,6 @@ import ToggleButton from '@mui/material/ToggleButton';
20import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; 20import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
21import { observer } from 'mobx-react-lite'; 21import { observer } from 'mobx-react-lite';
22 22
23import ConnectButton from './ConnectButton';
24import type EditorStore from './EditorStore'; 23import type EditorStore from './EditorStore';
25 24
26// Exhastive switch as proven by TypeScript. 25// Exhastive switch as proven by TypeScript.
@@ -106,7 +105,6 @@ export default observer(function EditorButtons({
106 > 105 >
107 <FormatPaint fontSize="small" /> 106 <FormatPaint fontSize="small" />
108 </IconButton> 107 </IconButton>
109 <ConnectButton editorStore={editorStore} />
110 </Stack> 108 </Stack>
111 ); 109 );
112}); 110});
diff --git a/subprojects/frontend/src/table/TableArea.tsx b/subprojects/frontend/src/table/TableArea.tsx
new file mode 100644
index 00000000..b4c6d4ce
--- /dev/null
+++ b/subprojects/frontend/src/table/TableArea.tsx
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7export default function TablePane(): JSX.Element {
8 return <div>Table</div>;
9}
diff --git a/subprojects/frontend/src/table/TablePane.tsx b/subprojects/frontend/src/table/TablePane.tsx
new file mode 100644
index 00000000..01f6ac53
--- /dev/null
+++ b/subprojects/frontend/src/table/TablePane.tsx
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Stack from '@mui/material/Stack';
8import { Suspense, lazy } from 'react';
9
10import Loading from '../Loading';
11
12const TableArea = lazy(() => import('./TableArea'));
13
14export default function TablePane(): JSX.Element {
15 return (
16 <Stack
17 direction="column"
18 height="100%"
19 overflow="auto"
20 alignItems="center"
21 justifyContent="center"
22 >
23 <Suspense fallback={<Loading />}>
24 <TableArea />
25 </Suspense>
26 </Stack>
27 );
28}
diff --git a/subprojects/frontend/src/theme/ThemeStore.ts b/subprojects/frontend/src/theme/ThemeStore.ts
index 7c657449..fa47d873 100644
--- a/subprojects/frontend/src/theme/ThemeStore.ts
+++ b/subprojects/frontend/src/theme/ThemeStore.ts
@@ -12,11 +12,19 @@ export enum ThemePreference {
12 PreferDark, 12 PreferDark,
13} 13}
14 14
15export type SelectedPane = 'code' | 'graph' | 'table';
16
15export default class ThemeStore { 17export default class ThemeStore {
16 preference = ThemePreference.System; 18 preference = ThemePreference.System;
17 19
18 systemDarkMode: boolean; 20 systemDarkMode: boolean;
19 21
22 showCode = true;
23
24 showGraph = true;
25
26 showTable = false;
27
20 constructor() { 28 constructor() {
21 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 29 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
22 this.systemDarkMode = mediaQuery.matches; 30 this.systemDarkMode = mediaQuery.matches;
@@ -48,4 +56,44 @@ export default class ThemeStore {
48 : ThemePreference.PreferDark; 56 : ThemePreference.PreferDark;
49 } 57 }
50 } 58 }
59
60 toggleCode(): void {
61 if (!this.showGraph && !this.showTable) {
62 return;
63 }
64 this.showCode = !this.showCode;
65 }
66
67 toggleGraph(): void {
68 if (!this.showCode && !this.showTable) {
69 return;
70 }
71 this.showGraph = !this.showGraph;
72 }
73
74 toggleTable(): void {
75 if (!this.showCode && !this.showGraph) {
76 return;
77 }
78 this.showTable = !this.showTable;
79 }
80
81 get selectedPane(): SelectedPane {
82 if (this.showCode) {
83 return 'code';
84 }
85 if (this.showGraph) {
86 return 'graph';
87 }
88 if (this.showTable) {
89 return 'table';
90 }
91 return 'code';
92 }
93
94 setSelectedPane(pane: SelectedPane): void {
95 this.showCode = pane === 'code';
96 this.showGraph = pane === 'graph';
97 this.showTable = pane === 'table';
98 }
51} 99}