aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-05 01:29:11 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-06 01:05:24 +0200
commiteb94326bb64552dbd7df62ae201ccca37f368467 (patch)
treeb810f0230ace058cac8a6343455ca60113925221 /subprojects/frontend/src/editor
parentrefactor(frontend): more readable indentation (diff)
downloadrefinery-eb94326bb64552dbd7df62ae201ccca37f368467.tar.gz
refinery-eb94326bb64552dbd7df62ae201ccca37f368467.tar.zst
refinery-eb94326bb64552dbd7df62ae201ccca37f368467.zip
feat(frontend): show connection status
Diffstat (limited to 'subprojects/frontend/src/editor')
-rw-r--r--subprojects/frontend/src/editor/ConnectButton.tsx68
-rw-r--r--subprojects/frontend/src/editor/ConnectionStatusNotification.tsx108
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx4
-rw-r--r--subprojects/frontend/src/editor/EditorPane.tsx2
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts20
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx1
6 files changed, 202 insertions, 1 deletions
diff --git a/subprojects/frontend/src/editor/ConnectButton.tsx b/subprojects/frontend/src/editor/ConnectButton.tsx
new file mode 100644
index 00000000..52e7b854
--- /dev/null
+++ b/subprojects/frontend/src/editor/ConnectButton.tsx
@@ -0,0 +1,68 @@
1import CloudIcon from '@mui/icons-material/Cloud';
2import CloudOffIcon from '@mui/icons-material/CloudOff';
3import SyncIcon from '@mui/icons-material/Sync';
4import SyncProblemIcon from '@mui/icons-material/SyncProblem';
5import IconButton from '@mui/material/IconButton';
6import { keyframes, styled } from '@mui/material/styles';
7import { observer } from 'mobx-react-lite';
8import React from 'react';
9
10import type EditorStore from './EditorStore';
11
12const rotateKeyframe = keyframes`
13 0% {
14 transform: rotate(0deg);
15 }
16 100% {
17 transform: rotate(-360deg);
18 }
19`;
20
21const AnimatedSyncIcon = styled(SyncIcon)`
22 animation: ${rotateKeyframe} 1.4s linear infinite;
23`;
24
25export default observer(function ConnectButton({
26 editorStore,
27}: {
28 editorStore: EditorStore | undefined;
29}): JSX.Element {
30 if (
31 editorStore !== undefined &&
32 (editorStore.opening || editorStore.opened)
33 ) {
34 return (
35 <IconButton
36 onClick={() => editorStore.disconnect()}
37 aria-label="Disconnect"
38 color="inherit"
39 >
40 {editorStore.opening ? (
41 <AnimatedSyncIcon fontSize="small" />
42 ) : (
43 <CloudIcon fontSize="small" />
44 )}
45 </IconButton>
46 );
47 }
48
49 let disconnectedIcon: JSX.Element;
50 if (editorStore === undefined) {
51 disconnectedIcon = <SyncIcon fontSize="small" />;
52 } else if (editorStore.connectionErrors.length > 0) {
53 disconnectedIcon = <SyncProblemIcon fontSize="small" />;
54 } else {
55 disconnectedIcon = <CloudOffIcon fontSize="small" />;
56 }
57
58 return (
59 <IconButton
60 disabled={editorStore === undefined}
61 onClick={() => editorStore?.connect()}
62 aria-label="Connect"
63 color="inherit"
64 >
65 {disconnectedIcon}
66 </IconButton>
67 );
68});
diff --git a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
new file mode 100644
index 00000000..e402e296
--- /dev/null
+++ b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
@@ -0,0 +1,108 @@
1import Button from '@mui/material/Button';
2import { observer } from 'mobx-react-lite';
3import { type SnackbarKey, useSnackbar } from 'notistack';
4import React, { useEffect } from 'react';
5
6import { ContrastThemeProvider } from '../theme/ThemeProvider';
7
8import type EditorStore from './EditorStore';
9
10const CONNECTING_DEBOUNCE_TIMEOUT = 250;
11
12export default observer(function ConnectionStatusNotification({
13 editorStore,
14}: {
15 editorStore: EditorStore;
16}): null {
17 const { opened, opening, connectionErrors } = editorStore;
18 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
19
20 useEffect(() => {
21 if (opening) {
22 let key: SnackbarKey | undefined;
23 let timeout: number | undefined = setTimeout(() => {
24 timeout = undefined;
25 key = enqueueSnackbar('Connecting to Refinery', {
26 persist: true,
27 action: (
28 <Button onClick={() => editorStore.disconnect()} color="inherit">
29 Cancel
30 </Button>
31 ),
32 });
33 }, CONNECTING_DEBOUNCE_TIMEOUT);
34 return () => {
35 if (timeout !== undefined) {
36 clearTimeout(timeout);
37 }
38 if (key !== undefined) {
39 closeSnackbar(key);
40 }
41 };
42 }
43
44 if (connectionErrors.length >= 1) {
45 const key = enqueueSnackbar(
46 <div>
47 Connection error: <b>{connectionErrors[0]}</b>
48 {connectionErrors.length >= 2 && (
49 <>
50 {' '}
51 and <b>{connectionErrors.length - 1}</b> more{' '}
52 {connectionErrors.length >= 3 ? 'errors' : 'error'}
53 </>
54 )}
55 </div>,
56 {
57 persist: !opened,
58 variant: 'error',
59 action: opened ? (
60 <ContrastThemeProvider>
61 <Button onClick={() => editorStore.disconnect()} color="inherit">
62 Disconnect
63 </Button>
64 </ContrastThemeProvider>
65 ) : (
66 <ContrastThemeProvider>
67 <Button onClick={() => editorStore.connect()} color="inherit">
68 Reconnect
69 </Button>
70 <Button onClick={() => editorStore.disconnect()} color="inherit">
71 Cancel
72 </Button>
73 </ContrastThemeProvider>
74 ),
75 },
76 );
77 return () => closeSnackbar(key);
78 }
79
80 if (!opened) {
81 const key = enqueueSnackbar(
82 <div>
83 <b>Not connected to Refinery:</b> Some editing features might be
84 degraded
85 </div>,
86 {
87 action: (
88 <ContrastThemeProvider>
89 <Button onClick={() => editorStore.connect()}>Reconnect</Button>
90 </ContrastThemeProvider>
91 ),
92 },
93 );
94 return () => closeSnackbar(key);
95 }
96
97 return () => {};
98 }, [
99 editorStore,
100 opened,
101 opening,
102 connectionErrors,
103 closeSnackbar,
104 enqueueSnackbar,
105 ]);
106
107 return null;
108});
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index d2273c6c..fd046d46 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -15,6 +15,7 @@ import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
15import { observer } from 'mobx-react-lite'; 15import { observer } from 'mobx-react-lite';
16import React from 'react'; 16import React from 'react';
17 17
18import ConnectButton from './ConnectButton';
18import type EditorStore from './EditorStore'; 19import type EditorStore from './EditorStore';
19 20
20// Exhastive switch as proven by TypeScript. 21// Exhastive switch as proven by TypeScript.
@@ -93,13 +94,14 @@ export default observer(function EditorButtons({
93 </ToggleButton> 94 </ToggleButton>
94 </ToggleButtonGroup> 95 </ToggleButtonGroup>
95 <IconButton 96 <IconButton
96 disabled={editorStore === undefined} 97 disabled={editorStore === undefined || !editorStore.opened}
97 onClick={() => editorStore?.formatText()} 98 onClick={() => editorStore?.formatText()}
98 aria-label="Automatic format" 99 aria-label="Automatic format"
99 color="inherit" 100 color="inherit"
100 > 101 >
101 <FormatPaint fontSize="small" /> 102 <FormatPaint fontSize="small" />
102 </IconButton> 103 </IconButton>
104 <ConnectButton editorStore={editorStore} />
103 </Stack> 105 </Stack>
104 ); 106 );
105}); 107});
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx
index 2651726c..079ebcdc 100644
--- a/subprojects/frontend/src/editor/EditorPane.tsx
+++ b/subprojects/frontend/src/editor/EditorPane.tsx
@@ -7,6 +7,7 @@ import React, { useState } from 'react';
7 7
8import { useRootStore } from '../RootStore'; 8import { useRootStore } from '../RootStore';
9 9
10import ConnectionStatusNotification from './ConnectionStatusNotification';
10import EditorArea from './EditorArea'; 11import EditorArea from './EditorArea';
11import EditorButtons from './EditorButtons'; 12import EditorButtons from './EditorButtons';
12import GenerateButton from './GenerateButton'; 13import GenerateButton from './GenerateButton';
@@ -43,6 +44,7 @@ export default observer(function EditorPane(): JSX.Element {
43 <EditorLoading /> 44 <EditorLoading />
44 ) : ( 45 ) : (
45 <> 46 <>
47 <ConnectionStatusNotification editorStore={editorStore} />
46 <SearchPanelPortal editorStore={editorStore} /> 48 <SearchPanelPortal editorStore={editorStore} />
47 <EditorArea editorStore={editorStore} /> 49 <EditorArea editorStore={editorStore} />
48 </> 50 </>
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index 4407376b..3ec33b2c 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -69,6 +69,26 @@ export default class EditorStore {
69 }); 69 });
70 } 70 }
71 71
72 get opened(): boolean {
73 return this.client.webSocketClient.opened;
74 }
75
76 get opening(): boolean {
77 return this.client.webSocketClient.opening;
78 }
79
80 get connectionErrors(): string[] {
81 return this.client.webSocketClient.errors;
82 }
83
84 connect(): void {
85 this.client.webSocketClient.connect();
86 }
87
88 disconnect(): void {
89 this.client.webSocketClient.disconnect();
90 }
91
72 setDarkMode(darkMode: boolean): void { 92 setDarkMode(darkMode: boolean): void {
73 log.debug('Update editor dark mode', darkMode); 93 log.debug('Update editor dark mode', darkMode);
74 this.dispatch({ 94 this.dispatch({
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx
index 5254f6cb..2ffb1a94 100644
--- a/subprojects/frontend/src/editor/GenerateButton.tsx
+++ b/subprojects/frontend/src/editor/GenerateButton.tsx
@@ -46,6 +46,7 @@ export default observer(function GenerateButton({
46 46
47 return ( 47 return (
48 <Button 48 <Button
49 disabled={!editorStore.opened}
49 color={warningCount > 0 ? 'warning' : 'primary'} 50 color={warningCount > 0 ? 'warning' : 'primary'}
50 className="rounded" 51 className="rounded"
51 startIcon={<PlayArrowIcon />} 52 startIcon={<PlayArrowIcon />}