diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-09-06 11:30:32 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-09-06 22:31:51 +0200 |
commit | 292e8998f3e7d106a91954e345a70f4cf3a317a8 (patch) | |
tree | 9ba04853ca3e3a883b29a34fdf461e6230439e01 /subprojects/frontend/src/editor/ConnectionStatusNotification.tsx | |
parent | refactor(frontend): toolbar sm breakpoint (diff) | |
download | refinery-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/ConnectionStatusNotification.tsx')
-rw-r--r-- | subprojects/frontend/src/editor/ConnectionStatusNotification.tsx | 143 |
1 files changed, 112 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 @@ | |||
1 | import Button from '@mui/material/Button'; | 1 | import Button from '@mui/material/Button'; |
2 | import { observer } from 'mobx-react-lite'; | 2 | import { observer } from 'mobx-react-lite'; |
3 | import { type SnackbarKey, useSnackbar } from 'notistack'; | 3 | import { |
4 | useSnackbar, | ||
5 | type SnackbarKey, | ||
6 | type SnackbarMessage, | ||
7 | type OptionsObject, | ||
8 | } from 'notistack'; | ||
4 | import React, { useEffect } from 'react'; | 9 | import React, { useEffect } from 'react'; |
5 | 10 | ||
6 | import { ContrastThemeProvider } from '../theme/ThemeProvider'; | 11 | import { ContrastThemeProvider } from '../theme/ThemeProvider'; |
7 | 12 | ||
8 | import type EditorStore from './EditorStore'; | 13 | import type EditorStore from './EditorStore'; |
9 | 14 | ||
10 | const CONNECTING_DEBOUNCE_TIMEOUT = 250; | 15 | const DEBOUNCE_TIMEOUT = 350; |
16 | |||
17 | function 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 | ||
12 | export default observer(function ConnectionStatusNotification({ | 42 | export 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 | ]); |