aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend')
-rw-r--r--subprojects/frontend/package.json2
-rw-r--r--subprojects/frontend/src/Refinery.tsx140
-rw-r--r--subprojects/frontend/src/editor/EditorPane.tsx2
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts6
-rw-r--r--subprojects/frontend/src/graph/GraphPane.tsx28
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts5
-rw-r--r--subprojects/frontend/src/xtext/xtextMessages.ts6
7 files changed, 185 insertions, 4 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 06ff9f6b..39ebd1df 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -49,6 +49,7 @@
49 "ansi-styles": "^6.2.1", 49 "ansi-styles": "^6.2.1",
50 "csstype": "^3.1.2", 50 "csstype": "^3.1.2",
51 "escape-string-regexp": "^5.0.0", 51 "escape-string-regexp": "^5.0.0",
52 "json-stringify-pretty-compact": "^4.0.0",
52 "lodash-es": "^4.17.21", 53 "lodash-es": "^4.17.21",
53 "loglevel": "^1.8.1", 54 "loglevel": "^1.8.1",
54 "loglevel-plugin-prefix": "^0.8.4", 55 "loglevel-plugin-prefix": "^0.8.4",
@@ -59,6 +60,7 @@
59 "notistack": "^3.0.1", 60 "notistack": "^3.0.1",
60 "react": "^18.2.0", 61 "react": "^18.2.0",
61 "react-dom": "^18.2.0", 62 "react-dom": "^18.2.0",
63 "react-resize-detector": "^9.1.0",
62 "xstate": "^4.38.2", 64 "xstate": "^4.38.2",
63 "zod": "^3.22.0" 65 "zod": "^3.22.0"
64 }, 66 },
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx
index b5ff94e1..099646f0 100644
--- a/subprojects/frontend/src/Refinery.tsx
+++ b/subprojects/frontend/src/Refinery.tsx
@@ -4,13 +4,151 @@
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';
7import Grow from '@mui/material/Grow'; 10import Grow from '@mui/material/Grow';
8import Stack from '@mui/material/Stack'; 11import Stack from '@mui/material/Stack';
12import { alpha, useTheme } from '@mui/material/styles';
9import { SnackbarProvider } from 'notistack'; 13import { SnackbarProvider } from 'notistack';
14import { memo, useRef, useState } from 'react';
15import { useResizeDetector } from 'react-resize-detector';
10 16
11import TopBar from './TopBar'; 17import TopBar from './TopBar';
12import UpdateNotification from './UpdateNotification'; 18import UpdateNotification from './UpdateNotification';
13import EditorPane from './editor/EditorPane'; 19import EditorPane from './editor/EditorPane';
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}
14 152
15export default function Refinery(): JSX.Element { 153export default function Refinery(): JSX.Element {
16 return ( 154 return (
@@ -18,7 +156,7 @@ export default function Refinery(): JSX.Element {
18 <UpdateNotification /> 156 <UpdateNotification />
19 <Stack direction="column" height="100%" overflow="auto"> 157 <Stack direction="column" height="100%" overflow="auto">
20 <TopBar /> 158 <TopBar />
21 <EditorPane /> 159 <SplitPane />
22 </Stack> 160 </Stack>
23 </SnackbarProvider> 161 </SnackbarProvider>
24 ); 162 );
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx
index 87f408fe..c9f86496 100644
--- a/subprojects/frontend/src/editor/EditorPane.tsx
+++ b/subprojects/frontend/src/editor/EditorPane.tsx
@@ -39,7 +39,7 @@ export default observer(function EditorPane(): JSX.Element {
39 const { editorStore } = useRootStore(); 39 const { editorStore } = useRootStore();
40 40
41 return ( 41 return (
42 <Stack direction="column" flexGrow={1} flexShrink={1} overflow="auto"> 42 <Stack direction="column" height="100%" overflow="auto">
43 <Toolbar variant="dense"> 43 <Toolbar variant="dense">
44 <EditorButtons editorStore={editorStore} /> 44 <EditorButtons editorStore={editorStore} />
45 </Toolbar> 45 </Toolbar>
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index b98f085e..c79f6ec1 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -58,6 +58,8 @@ export default class EditorStore {
58 58
59 disposed = false; 59 disposed = false;
60 60
61 semantics: unknown = {};
62
61 constructor(initialValue: string, pwaStore: PWAStore) { 63 constructor(initialValue: string, pwaStore: PWAStore) {
62 this.id = nanoid(); 64 this.id = nanoid();
63 this.state = createEditorState(initialValue, this); 65 this.state = createEditorState(initialValue, this);
@@ -282,6 +284,10 @@ export default class EditorStore {
282 return true; 284 return true;
283 } 285 }
284 286
287 setSemantics(semantics: unknown) {
288 this.semantics = semantics;
289 }
290
285 dispose(): void { 291 dispose(): void {
286 this.client?.dispose(); 292 this.client?.dispose();
287 this.disposed = true; 293 this.disposed = true;
diff --git a/subprojects/frontend/src/graph/GraphPane.tsx b/subprojects/frontend/src/graph/GraphPane.tsx
new file mode 100644
index 00000000..f69f52a6
--- /dev/null
+++ b/subprojects/frontend/src/graph/GraphPane.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 { styled } from '@mui/material/styles';
9import stringify from 'json-stringify-pretty-compact';
10import { observer } from 'mobx-react-lite';
11
12import { useRootStore } from '../RootStoreProvider';
13
14const StyledCode = styled('code')(({ theme }) => ({
15 ...theme.typography.editor,
16 fontWeight: theme.typography.fontWeightEditorNormal,
17 margin: theme.spacing(2),
18 whiteSpace: 'pre',
19}));
20
21export default observer(function GraphPane(): JSX.Element {
22 const { editorStore } = useRootStore();
23 return (
24 <Stack direction="column" height="100%" overflow="auto">
25 <StyledCode>{stringify(editorStore?.semantics ?? {})}</StyledCode>
26 </Stack>
27 );
28});
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index abdf8518..d145cd30 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -38,7 +38,7 @@ export default class XtextClient {
38 private readonly occurrencesService: OccurrencesService; 38 private readonly occurrencesService: OccurrencesService;
39 39
40 constructor( 40 constructor(
41 store: EditorStore, 41 private readonly store: EditorStore,
42 private readonly pwaStore: PWAStore, 42 private readonly pwaStore: PWAStore,
43 ) { 43 ) {
44 this.webSocketClient = new XtextWebSocketClient( 44 this.webSocketClient = new XtextWebSocketClient(
@@ -114,6 +114,9 @@ export default class XtextClient {
114 case 'validate': 114 case 'validate':
115 this.validationService.onPush(push); 115 this.validationService.onPush(push);
116 return; 116 return;
117 case 'semantics':
118 this.store.setSemantics(push);
119 return;
117 default: 120 default:
118 throw new Error('Unknown service'); 121 throw new Error('Unknown service');
119 } 122 }
diff --git a/subprojects/frontend/src/xtext/xtextMessages.ts b/subprojects/frontend/src/xtext/xtextMessages.ts
index bbbff064..971720e1 100644
--- a/subprojects/frontend/src/xtext/xtextMessages.ts
+++ b/subprojects/frontend/src/xtext/xtextMessages.ts
@@ -34,7 +34,11 @@ export const XtextWebErrorResponse = z.object({
34 34
35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>; 35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>;
36 36
37export const XtextWebPushService = z.enum(['highlight', 'validate']); 37export const XtextWebPushService = z.enum([
38 'highlight',
39 'validate',
40 'semantics',
41]);
38 42
39export type XtextWebPushService = z.infer<typeof XtextWebPushService>; 43export type XtextWebPushService = z.infer<typeof XtextWebPushService>;
40 44