aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/graph/VisibilityDialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/graph/VisibilityDialog.tsx')
-rw-r--r--subprojects/frontend/src/graph/VisibilityDialog.tsx285
1 files changed, 285 insertions, 0 deletions
diff --git a/subprojects/frontend/src/graph/VisibilityDialog.tsx b/subprojects/frontend/src/graph/VisibilityDialog.tsx
new file mode 100644
index 00000000..b28ba31a
--- /dev/null
+++ b/subprojects/frontend/src/graph/VisibilityDialog.tsx
@@ -0,0 +1,285 @@
1/*
2 * SPDX-FileCopyrightText: 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 FilterListIcon from '@mui/icons-material/FilterList';
9import LabelIcon from '@mui/icons-material/Label';
10import LabelOutlinedIcon from '@mui/icons-material/LabelOutlined';
11import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
12import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
13import Button from '@mui/material/Button';
14import Checkbox from '@mui/material/Checkbox';
15import FormControlLabel from '@mui/material/FormControlLabel';
16import IconButton from '@mui/material/IconButton';
17import Switch from '@mui/material/Switch';
18import { styled } from '@mui/material/styles';
19import { observer } from 'mobx-react-lite';
20
21import type GraphStore from './GraphStore';
22import { isVisibilityAllowed } from './GraphStore';
23import RelationName from './RelationName';
24
25const VisibilityDialogRoot = styled('div', {
26 name: 'VisibilityDialog-Root',
27 shouldForwardProp: (propName) => propName !== 'dialog',
28})<{ dialog: boolean }>(({ theme, dialog }) => {
29 const overlayOpacity = dialog ? 0.16 : 0.09;
30 return {
31 maxHeight: '100%',
32 maxWidth: '100%',
33 overflow: 'hidden',
34 display: 'flex',
35 padding: theme.spacing(2),
36 flexDirection: 'column',
37 '.VisibilityDialog-switch': {
38 display: 'flex',
39 flexDirection: 'row',
40 paddingLeft: theme.spacing(1),
41 marginBottom: theme.spacing(1),
42 '.MuiFormControlLabel-root': {
43 flexGrow: 1,
44 },
45 '.MuiIconButton-root': {
46 flexGrow: 0,
47 flexShrink: 0,
48 marginLeft: theme.spacing(2),
49 },
50 },
51 '.VisibilityDialog-scroll': {
52 display: 'flex',
53 flexDirection: 'column',
54 height: 'auto',
55 overflowX: 'hidden',
56 overflowY: 'auto',
57 '& table': {
58 // We use flexbox instead of `display: table` to get proper text-overflow
59 // behavior for overly long relation names.
60 display: 'flex',
61 flexDirection: 'column',
62 },
63 '& thead, & tbody': {
64 display: 'flex',
65 flexDirection: 'column',
66 },
67 '& thead': {
68 position: 'sticky',
69 top: 0,
70 zIndex: 999,
71 backgroundColor: theme.palette.background.paper,
72 ...(theme.palette.mode === 'dark'
73 ? {
74 // In dark mode, MUI Paper gets a lighter overlay.
75 backgroundImage: `linear-gradient(
76 rgba(255, 255, 255, ${overlayOpacity}),
77 rgba(255, 255, 255, ${overlayOpacity})
78 )`,
79 }
80 : {}),
81 '& tr': {
82 height: '44px',
83 },
84 },
85 '& tr': {
86 display: 'flex',
87 flexDirection: 'row',
88 maxWidth: '100%',
89 },
90 '& tbody tr': {
91 transition: theme.transitions.create('background', {
92 duration: theme.transitions.duration.shortest,
93 }),
94 '&:hover': {
95 background: theme.palette.action.hover,
96 '@media (hover: none)': {
97 background: 'transparent',
98 },
99 },
100 },
101 '& th, & td': {
102 display: 'flex',
103 flexDirection: 'row',
104 alignItems: 'center',
105 justifyContent: 'center',
106 // Set width in advance, since we can't rely on `display: table-cell`.
107 width: '44px',
108 },
109 '& th:nth-of-type(3), & td:nth-of-type(3)': {
110 justifyContent: 'start',
111 paddingLeft: theme.spacing(1),
112 paddingRight: theme.spacing(2),
113 // Only let the last column grow or shrink.
114 flexGrow: 1,
115 flexShrink: 1,
116 // Compute the maximum available space in advance to let the text overflow.
117 maxWidth: 'calc(100% - 88px)',
118 width: 'min-content',
119 },
120 '& td:nth-of-type(3)': {
121 cursor: 'pointer',
122 userSelect: 'none',
123 WebkitTapHighlightColor: 'transparent',
124 },
125
126 '& thead th, .VisibilityDialog-custom tr:last-child td': {
127 borderBottom: `1px solid ${theme.palette.divider}`,
128 },
129 },
130 // Hack to apply `text-overflow`.
131 '.VisibilityDialog-nowrap': {
132 maxWidth: '100%',
133 overflow: 'hidden',
134 wordWrap: 'nowrap',
135 textOverflow: 'ellipsis',
136 },
137 '.VisibilityDialog-buttons': {
138 marginTop: theme.spacing(2),
139 display: 'flex',
140 flexDirection: 'row',
141 justifyContent: 'flex-end',
142 },
143 '.VisibilityDialog-empty': {
144 display: 'flex',
145 flexDirection: 'column',
146 alignItems: 'center',
147 color: theme.palette.text.secondary,
148 },
149 '.VisibilityDialog-emptyIcon': {
150 fontSize: '6rem',
151 marginBottom: theme.spacing(1),
152 },
153 };
154});
155
156function VisibilityDialog({
157 graph,
158 close,
159 dialog,
160}: {
161 graph: GraphStore;
162 close: () => void;
163 dialog?: boolean;
164}): JSX.Element {
165 const builtinRows: JSX.Element[] = [];
166 const rows: JSX.Element[] = [];
167 graph.relationMetadata.forEach((metadata, name) => {
168 if (!isVisibilityAllowed(metadata, 'must')) {
169 return;
170 }
171 const visibility = graph.getVisibility(name);
172 const row = (
173 <tr key={metadata.name}>
174 <td>
175 <Checkbox
176 checked={visibility !== 'none'}
177 aria-label={`Show true and error values of ${metadata.simpleName}`}
178 onClick={() =>
179 graph.setVisibility(name, visibility === 'none' ? 'must' : 'none')
180 }
181 />
182 </td>
183 <td>
184 <Checkbox
185 checked={visibility === 'all'}
186 disabled={!isVisibilityAllowed(metadata, 'all')}
187 aria-label={`Show all values of ${metadata.simpleName}`}
188 onClick={() =>
189 graph.setVisibility(name, visibility === 'all' ? 'must' : 'all')
190 }
191 />
192 </td>
193 <td onClick={() => graph.cycleVisibility(name)}>
194 <div className="VisibilityDialog-nowrap">
195 <RelationName metadata={metadata} abbreviate={graph.abbreviate} />
196 </div>
197 </td>
198 </tr>
199 );
200 if (name.startsWith('builtin::')) {
201 builtinRows.push(row);
202 } else {
203 rows.push(row);
204 }
205 });
206
207 const hasRows = rows.length > 0 || builtinRows.length > 0;
208
209 return (
210 <VisibilityDialogRoot
211 dialog={dialog ?? VisibilityDialog.defaultProps.dialog}
212 >
213 <div className="VisibilityDialog-switch">
214 <FormControlLabel
215 control={
216 <Switch
217 checked={!graph.abbreviate}
218 onClick={() => graph.toggleAbbrevaite()}
219 />
220 }
221 label="Fully qualified names"
222 />
223 {dialog && (
224 <IconButton aria-label="Close" onClick={close}>
225 <CloseIcon />
226 </IconButton>
227 )}
228 </div>
229 <div className="VisibilityDialog-scroll">
230 {hasRows ? (
231 <table cellSpacing={0}>
232 <thead>
233 <tr>
234 <th>
235 <LabelIcon />
236 </th>
237 <th>
238 <LabelOutlinedIcon />
239 </th>
240 <th>Symbol</th>
241 </tr>
242 </thead>
243 <tbody className="VisibilityDialog-custom">{...rows}</tbody>
244 <tbody className="VisibilityDialog-builtin">{...builtinRows}</tbody>
245 </table>
246 ) : (
247 <div className="VisibilityDialog-empty">
248 <SentimentVeryDissatisfiedIcon
249 className="VisibilityDialog-emptyIcon"
250 fontSize="inherit"
251 />
252 <div>Partial model is empty</div>
253 </div>
254 )}
255 </div>
256 <div className="VisibilityDialog-buttons">
257 <Button
258 color="inherit"
259 onClick={() => graph.hideAll()}
260 startIcon={<VisibilityOffIcon />}
261 >
262 Hide all
263 </Button>
264 <Button
265 color="inherit"
266 onClick={() => graph.resetFilter()}
267 startIcon={<FilterListIcon />}
268 >
269 Reset filter
270 </Button>
271 {!dialog && (
272 <Button color="inherit" onClick={close}>
273 Close
274 </Button>
275 )}
276 </div>
277 </VisibilityDialogRoot>
278 );
279}
280
281VisibilityDialog.defaultProps = {
282 dialog: false,
283};
284
285export default observer(VisibilityDialog);