diff options
Diffstat (limited to 'subprojects/frontend/src/ModelWorkArea.tsx')
-rw-r--r-- | subprojects/frontend/src/ModelWorkArea.tsx | 193 |
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 | |||
7 | import CloseIcon from '@mui/icons-material/Close'; | ||
8 | import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied'; | ||
9 | import CircularProgress from '@mui/material/CircularProgress'; | ||
10 | import IconButton from '@mui/material/IconButton'; | ||
11 | import Stack from '@mui/material/Stack'; | ||
12 | import Tab from '@mui/material/Tab'; | ||
13 | import Tabs from '@mui/material/Tabs'; | ||
14 | import { styled } from '@mui/material/styles'; | ||
15 | import { observer } from 'mobx-react-lite'; | ||
16 | |||
17 | import DirectionalSplitPane from './DirectionalSplitPane'; | ||
18 | import Loading from './Loading'; | ||
19 | import { useRootStore } from './RootStoreProvider'; | ||
20 | import type GeneratedModelStore from './editor/GeneratedModelStore'; | ||
21 | import GraphPane from './graph/GraphPane'; | ||
22 | import type GraphStore from './graph/GraphStore'; | ||
23 | import TablePane from './table/TablePane'; | ||
24 | import type ThemeStore from './theme/ThemeStore'; | ||
25 | |||
26 | const 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 | |||
43 | const 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 | |||
50 | const 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 | |||
106 | function 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 | |||
193 | export default observer(ModelWorkArea); | ||