aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/ModelWorkArea.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/ModelWorkArea.tsx')
-rw-r--r--subprojects/frontend/src/ModelWorkArea.tsx193
1 files changed, 193 insertions, 0 deletions
diff --git a/subprojects/frontend/src/ModelWorkArea.tsx b/subprojects/frontend/src/ModelWorkArea.tsx
new file mode 100644
index 00000000..16e16a97
--- /dev/null
+++ b/subprojects/frontend/src/ModelWorkArea.tsx
@@ -0,0 +1,193 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import CloseIcon from '@mui/icons-material/Close';
8import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
9import CircularProgress from '@mui/material/CircularProgress';
10import IconButton from '@mui/material/IconButton';
11import Stack from '@mui/material/Stack';
12import Tab from '@mui/material/Tab';
13import Tabs from '@mui/material/Tabs';
14import { styled } from '@mui/material/styles';
15import { observer } from 'mobx-react-lite';
16
17import DirectionalSplitPane from './DirectionalSplitPane';
18import Loading from './Loading';
19import { useRootStore } from './RootStoreProvider';
20import type GeneratedModelStore from './editor/GeneratedModelStore';
21import GraphPane from './graph/GraphPane';
22import type GraphStore from './graph/GraphStore';
23import TablePane from './table/TablePane';
24import type ThemeStore from './theme/ThemeStore';
25
26const SplitGraphPane = observer(function SplitGraphPane({
27 graph,
28 themeStore,
29}: {
30 graph: GraphStore;
31 themeStore: ThemeStore;
32}): JSX.Element {
33 return (
34 <DirectionalSplitPane
35 primary={<GraphPane graph={graph} />}
36 secondary={<TablePane graph={graph} />}
37 primaryOnly={!themeStore.showTable}
38 secondaryOnly={!themeStore.showGraph}
39 />
40 );
41});
42
43const GenerationStatus = styled('div', {
44 name: 'ModelWorkArea-GenerationStatus',
45 shouldForwardProp: (prop) => prop !== 'error',
46})<{ error: boolean }>(({ error, theme }) => ({
47 color: error ? theme.palette.error.main : theme.palette.text.primary,
48}));
49
50const GeneratedModelPane = observer(function GeneratedModelPane({
51 generatedModel,
52 themeStore,
53}: {
54 generatedModel: GeneratedModelStore;
55 themeStore: ThemeStore;
56}): JSX.Element {
57 const { message, error, graph } = generatedModel;
58
59 if (graph !== undefined) {
60 return <SplitGraphPane graph={graph} themeStore={themeStore} />;
61 }
62
63 return (
64 <Stack
65 direction="column"
66 alignItems="center"
67 justifyContent="center"
68 height="100%"
69 width="100%"
70 overflow="hidden"
71 my={2}
72 >
73 <Stack
74 direction="column"
75 alignItems="center"
76 flexGrow={1}
77 flexShrink={1}
78 flexBasis={0}
79 sx={(theme) => ({
80 maxHeight: '6rem',
81 height: 'calc(100% - 8rem)',
82 marginBottom: theme.spacing(1),
83 padding: error ? 0 : theme.spacing(1),
84 color: theme.palette.text.secondary,
85 '.MuiCircularProgress-root, .MuiCircularProgress-svg, .MuiSvgIcon-root':
86 {
87 height: '100% !important',
88 width: '100% !important',
89 },
90 })}
91 >
92 {error ? (
93 <SentimentVeryDissatisfiedIcon
94 className="VisibilityDialog-emptyIcon"
95 fontSize="inherit"
96 />
97 ) : (
98 <CircularProgress color="inherit" />
99 )}
100 </Stack>
101 <GenerationStatus error={error}>{message}</GenerationStatus>
102 </Stack>
103 );
104});
105
106function ModelWorkArea(): JSX.Element {
107 const { editorStore, themeStore } = useRootStore();
108
109 if (editorStore === undefined) {
110 return <Loading />;
111 }
112
113 const { graph, generatedModels, selectedGeneratedModel } = editorStore;
114
115 const generatedModelNames: string[] = [];
116 const generatedModelTabs: JSX.Element[] = [];
117 generatedModels.forEach((value, key) => {
118 generatedModelNames.push(key);
119 /* eslint-disable react/no-array-index-key -- Key is a string here, not the array index. */
120 generatedModelTabs.push(
121 <Tab
122 label={value.title}
123 key={key}
124 onAuxClick={(event) => {
125 if (event.button === 1) {
126 editorStore.deleteGeneratedModel(key);
127 event.preventDefault();
128 event.stopPropagation();
129 }
130 }}
131 />,
132 );
133 /* eslint-enable react/no-array-index-key */
134 });
135 const generatedModel =
136 selectedGeneratedModel === undefined
137 ? undefined
138 : generatedModels.get(selectedGeneratedModel);
139 const selectedIndex =
140 selectedGeneratedModel === undefined
141 ? 0
142 : generatedModelNames.indexOf(selectedGeneratedModel) + 1;
143
144 return (
145 <Stack direction="column" height="100%" width="100%" overflow="hidden">
146 <Stack
147 direction="row"
148 sx={(theme) => ({
149 display: generatedModelNames.length === 0 ? 'none' : 'flex',
150 alignItems: 'center',
151 borderBottom: `1px solid ${theme.palette.outer.border}`,
152 })}
153 >
154 <Tabs
155 value={selectedIndex}
156 onChange={(_event, value) => {
157 if (value === 0) {
158 editorStore.selectGeneratedModel(undefined);
159 } else if (typeof value === 'number') {
160 editorStore.selectGeneratedModel(generatedModelNames[value - 1]);
161 }
162 }}
163 variant="scrollable"
164 scrollButtons="auto"
165 sx={{ flexGrow: 1 }}
166 >
167 <Tab label="Initial model" />
168 {generatedModelTabs}
169 </Tabs>
170 <IconButton
171 aria-label="Close generated model"
172 onClick={() =>
173 editorStore.deleteGeneratedModel(selectedGeneratedModel)
174 }
175 disabled={selectedIndex === 0}
176 sx={{ mx: 1 }}
177 >
178 <CloseIcon fontSize="small" />
179 </IconButton>
180 </Stack>
181 {generatedModel === undefined ? (
182 <SplitGraphPane graph={graph} themeStore={themeStore} />
183 ) : (
184 <GeneratedModelPane
185 generatedModel={generatedModel}
186 themeStore={themeStore}
187 />
188 )}
189 </Stack>
190 );
191}
192
193export default observer(ModelWorkArea);