aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-06 11:30:32 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-06 22:31:51 +0200
commit292e8998f3e7d106a91954e345a70f4cf3a317a8 (patch)
tree9ba04853ca3e3a883b29a34fdf461e6230439e01 /subprojects/frontend/src/editor
parentrefactor(frontend): toolbar sm breakpoint (diff)
downloadrefinery-292e8998f3e7d106a91954e345a70f4cf3a317a8.tar.gz
refinery-292e8998f3e7d106a91954e345a70f4cf3a317a8.tar.zst
refinery-292e8998f3e7d106a91954e345a70f4cf3a317a8.zip
feat(frontend): handle page hide events
Integrate better with the page lifecycle state machine, see https://developer.chrome.com/blog/page-lifecycle-api/ Also makes disconnected notifications less noisy, since they may occur more frequently now (due to a frozen page being resumed).
Diffstat (limited to 'subprojects/frontend/src/editor')
-rw-r--r--subprojects/frontend/src/editor/ConnectionStatusNotification.tsx143
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts9
2 files changed, 121 insertions, 31 deletions
diff --git a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
index e402e296..54c4e834 100644
--- a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
+++ b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
@@ -1,50 +1,83 @@
1import Button from '@mui/material/Button'; 1import Button from '@mui/material/Button';
2import { observer } from 'mobx-react-lite'; 2import { observer } from 'mobx-react-lite';
3import { type SnackbarKey, useSnackbar } from 'notistack'; 3import {
4 useSnackbar,
5 type SnackbarKey,
6 type SnackbarMessage,
7 type OptionsObject,
8} from 'notistack';
4import React, { useEffect } from 'react'; 9import React, { useEffect } from 'react';
5 10
6import { ContrastThemeProvider } from '../theme/ThemeProvider'; 11import { ContrastThemeProvider } from '../theme/ThemeProvider';
7 12
8import type EditorStore from './EditorStore'; 13import type EditorStore from './EditorStore';
9 14
10const CONNECTING_DEBOUNCE_TIMEOUT = 250; 15const DEBOUNCE_TIMEOUT = 350;
16
17function enqueueLater(
18 enqueueSnackbar: (
19 message: SnackbarMessage,
20 options: OptionsObject | undefined,
21 ) => SnackbarKey,
22 closeSnackbar: (key: SnackbarKey) => void,
23 message: SnackbarMessage,
24 options?: OptionsObject | undefined,
25 debounceTimeout = DEBOUNCE_TIMEOUT,
26): () => void {
27 let key: SnackbarKey | undefined;
28 let timeout: number | undefined = setTimeout(() => {
29 timeout = undefined;
30 key = enqueueSnackbar(message, options);
31 }, debounceTimeout);
32 return () => {
33 if (timeout !== undefined) {
34 clearTimeout(timeout);
35 }
36 if (key !== undefined) {
37 closeSnackbar(key);
38 }
39 };
40}
11 41
12export default observer(function ConnectionStatusNotification({ 42export default observer(function ConnectionStatusNotification({
13 editorStore, 43 editorStore,
14}: { 44}: {
15 editorStore: EditorStore; 45 editorStore: EditorStore;
16}): null { 46}): null {
17 const { opened, opening, connectionErrors } = editorStore; 47 const {
48 opened,
49 opening,
50 connectionErrors,
51 disconnectedByUser,
52 networkMissing,
53 } = editorStore;
18 const { enqueueSnackbar, closeSnackbar } = useSnackbar(); 54 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
19 55
20 useEffect(() => { 56 useEffect(() => {
21 if (opening) { 57 if (opening) {
22 let key: SnackbarKey | undefined; 58 return enqueueLater(
23 let timeout: number | undefined = setTimeout(() => { 59 enqueueSnackbar,
24 timeout = undefined; 60 closeSnackbar,
25 key = enqueueSnackbar('Connecting to Refinery', { 61 'Connecting to Refinery',
62 {
26 persist: true, 63 persist: true,
27 action: ( 64 action: (
28 <Button onClick={() => editorStore.disconnect()} color="inherit"> 65 <Button onClick={() => editorStore.disconnect()} color="inherit">
29 Cancel 66 Cancel
30 </Button> 67 </Button>
31 ), 68 ),
32 }); 69 },
33 }, CONNECTING_DEBOUNCE_TIMEOUT); 70 500,
34 return () => { 71 );
35 if (timeout !== undefined) {
36 clearTimeout(timeout);
37 }
38 if (key !== undefined) {
39 closeSnackbar(key);
40 }
41 };
42 } 72 }
43 73
44 if (connectionErrors.length >= 1) { 74 if (connectionErrors.length >= 1 && !opening) {
45 const key = enqueueSnackbar( 75 return enqueueLater(
76 enqueueSnackbar,
77 closeSnackbar,
46 <div> 78 <div>
47 Connection error: <b>{connectionErrors[0]}</b> 79 Connection error:{' '}
80 <b>{connectionErrors[connectionErrors.length - 1]}</b>
48 {connectionErrors.length >= 2 && ( 81 {connectionErrors.length >= 2 && (
49 <> 82 <>
50 {' '} 83 {' '}
@@ -57,28 +90,72 @@ export default observer(function ConnectionStatusNotification({
57 persist: !opened, 90 persist: !opened,
58 variant: 'error', 91 variant: 'error',
59 action: opened ? ( 92 action: opened ? (
60 <ContrastThemeProvider> 93 <Button onClick={() => editorStore.disconnect()} color="inherit">
61 <Button onClick={() => editorStore.disconnect()} color="inherit"> 94 Disconnect
62 Disconnect 95 </Button>
63 </Button>
64 </ContrastThemeProvider>
65 ) : ( 96 ) : (
66 <ContrastThemeProvider> 97 <>
67 <Button onClick={() => editorStore.connect()} color="inherit"> 98 <Button onClick={() => editorStore.connect()} color="inherit">
68 Reconnect 99 Reconnect
69 </Button> 100 </Button>
70 <Button onClick={() => editorStore.disconnect()} color="inherit"> 101 <Button onClick={() => editorStore.disconnect()} color="inherit">
71 Cancel 102 Cancel
72 </Button> 103 </Button>
104 </>
105 ),
106 },
107 );
108 }
109
110 if (networkMissing) {
111 if (disconnectedByUser) {
112 return enqueueLater(
113 enqueueSnackbar,
114 closeSnackbar,
115 <div>
116 <b>No network connection:</b> Some editing features might be
117 degraded
118 </div>,
119 {
120 action: (
121 <ContrastThemeProvider>
122 <Button onClick={() => editorStore.connect()} color="primary">
123 Try reconnecting
124 </Button>
125 </ContrastThemeProvider>
126 ),
127 },
128 0,
129 );
130 }
131
132 return enqueueLater(
133 enqueueSnackbar,
134 closeSnackbar,
135 <div>
136 <b>No network connection:</b> Refinery will try to reconnect when the
137 connection is restored
138 </div>,
139 {
140 persist: true,
141 action: (
142 <ContrastThemeProvider>
143 <Button onClick={() => editorStore.connect()} color="primary">
144 Try now
145 </Button>
146 <Button onClick={() => editorStore.disconnect()} color="inherit">
147 Cancel
148 </Button>
73 </ContrastThemeProvider> 149 </ContrastThemeProvider>
74 ), 150 ),
75 }, 151 },
76 ); 152 );
77 return () => closeSnackbar(key);
78 } 153 }
79 154
80 if (!opened) { 155 if (disconnectedByUser) {
81 const key = enqueueSnackbar( 156 return enqueueLater(
157 enqueueSnackbar,
158 closeSnackbar,
82 <div> 159 <div>
83 <b>Not connected to Refinery:</b> Some editing features might be 160 <b>Not connected to Refinery:</b> Some editing features might be
84 degraded 161 degraded
@@ -86,12 +163,14 @@ export default observer(function ConnectionStatusNotification({
86 { 163 {
87 action: ( 164 action: (
88 <ContrastThemeProvider> 165 <ContrastThemeProvider>
89 <Button onClick={() => editorStore.connect()}>Reconnect</Button> 166 <Button onClick={() => editorStore.connect()} color="primary">
167 Reconnect
168 </Button>
90 </ContrastThemeProvider> 169 </ContrastThemeProvider>
91 ), 170 ),
92 }, 171 },
172 0,
93 ); 173 );
94 return () => closeSnackbar(key);
95 } 174 }
96 175
97 return () => {}; 176 return () => {};
@@ -100,6 +179,8 @@ export default observer(function ConnectionStatusNotification({
100 opened, 179 opened,
101 opening, 180 opening,
102 connectionErrors, 181 connectionErrors,
182 disconnectedByUser,
183 networkMissing,
103 closeSnackbar, 184 closeSnackbar,
104 enqueueSnackbar, 185 enqueueSnackbar,
105 ]); 186 ]);
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index 3ec33b2c..ecbe6ef8 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -67,6 +67,7 @@ export default class EditorStore {
67 contentAssist: false, 67 contentAssist: false,
68 formatText: false, 68 formatText: false,
69 }); 69 });
70 this.client.start();
70 } 71 }
71 72
72 get opened(): boolean { 73 get opened(): boolean {
@@ -77,6 +78,14 @@ export default class EditorStore {
77 return this.client.webSocketClient.opening; 78 return this.client.webSocketClient.opening;
78 } 79 }
79 80
81 get disconnectedByUser(): boolean {
82 return this.client.webSocketClient.disconnectedByUser;
83 }
84
85 get networkMissing(): boolean {
86 return this.client.webSocketClient.networkMissing;
87 }
88
80 get connectionErrors(): string[] { 89 get connectionErrors(): string[] {
81 return this.client.webSocketClient.errors; 90 return this.client.webSocketClient.errors;
82 } 91 }