diff options
author | 2022-09-05 01:29:11 +0200 | |
---|---|---|
committer | 2022-09-06 01:05:24 +0200 | |
commit | eb94326bb64552dbd7df62ae201ccca37f368467 (patch) | |
tree | b810f0230ace058cac8a6343455ca60113925221 /subprojects/frontend | |
parent | refactor(frontend): more readable indentation (diff) | |
download | refinery-eb94326bb64552dbd7df62ae201ccca37f368467.tar.gz refinery-eb94326bb64552dbd7df62ae201ccca37f368467.tar.zst refinery-eb94326bb64552dbd7df62ae201ccca37f368467.zip |
feat(frontend): show connection status
Diffstat (limited to 'subprojects/frontend')
-rw-r--r-- | subprojects/frontend/src/editor/ConnectButton.tsx | 68 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/ConnectionStatusNotification.tsx | 108 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorButtons.tsx | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorPane.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorStore.ts | 20 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/GenerateButton.tsx | 1 | ||||
-rw-r--r-- | subprojects/frontend/src/index.tsx | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/ContentAssistService.ts | 7 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/HighlightingService.ts | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/OccurrencesService.ts | 11 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/UpdateService.ts | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/ValidationService.ts | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextClient.ts | 16 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextWebSocketClient.ts | 12 |
14 files changed, 260 insertions, 5 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 @@ | |||
1 | import CloudIcon from '@mui/icons-material/Cloud'; | ||
2 | import CloudOffIcon from '@mui/icons-material/CloudOff'; | ||
3 | import SyncIcon from '@mui/icons-material/Sync'; | ||
4 | import SyncProblemIcon from '@mui/icons-material/SyncProblem'; | ||
5 | import IconButton from '@mui/material/IconButton'; | ||
6 | import { keyframes, styled } from '@mui/material/styles'; | ||
7 | import { observer } from 'mobx-react-lite'; | ||
8 | import React from 'react'; | ||
9 | |||
10 | import type EditorStore from './EditorStore'; | ||
11 | |||
12 | const rotateKeyframe = keyframes` | ||
13 | 0% { | ||
14 | transform: rotate(0deg); | ||
15 | } | ||
16 | 100% { | ||
17 | transform: rotate(-360deg); | ||
18 | } | ||
19 | `; | ||
20 | |||
21 | const AnimatedSyncIcon = styled(SyncIcon)` | ||
22 | animation: ${rotateKeyframe} 1.4s linear infinite; | ||
23 | `; | ||
24 | |||
25 | export 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 @@ | |||
1 | import Button from '@mui/material/Button'; | ||
2 | import { observer } from 'mobx-react-lite'; | ||
3 | import { type SnackbarKey, useSnackbar } from 'notistack'; | ||
4 | import React, { useEffect } from 'react'; | ||
5 | |||
6 | import { ContrastThemeProvider } from '../theme/ThemeProvider'; | ||
7 | |||
8 | import type EditorStore from './EditorStore'; | ||
9 | |||
10 | const CONNECTING_DEBOUNCE_TIMEOUT = 250; | ||
11 | |||
12 | export 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'; | |||
15 | import { observer } from 'mobx-react-lite'; | 15 | import { observer } from 'mobx-react-lite'; |
16 | import React from 'react'; | 16 | import React from 'react'; |
17 | 17 | ||
18 | import ConnectButton from './ConnectButton'; | ||
18 | import type EditorStore from './EditorStore'; | 19 | import 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 | ||
8 | import { useRootStore } from '../RootStore'; | 8 | import { useRootStore } from '../RootStore'; |
9 | 9 | ||
10 | import ConnectionStatusNotification from './ConnectionStatusNotification'; | ||
10 | import EditorArea from './EditorArea'; | 11 | import EditorArea from './EditorArea'; |
11 | import EditorButtons from './EditorButtons'; | 12 | import EditorButtons from './EditorButtons'; |
12 | import GenerateButton from './GenerateButton'; | 13 | import 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 />} |
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx index 602a68ef..5760327e 100644 --- a/subprojects/frontend/src/index.tsx +++ b/subprojects/frontend/src/index.tsx | |||
@@ -1,5 +1,6 @@ | |||
1 | import Box from '@mui/material/Box'; | 1 | import Box from '@mui/material/Box'; |
2 | import CssBaseline from '@mui/material/CssBaseline'; | 2 | import CssBaseline from '@mui/material/CssBaseline'; |
3 | import Grow from '@mui/material/Grow'; | ||
3 | import { configure } from 'mobx'; | 4 | import { configure } from 'mobx'; |
4 | import { SnackbarProvider } from 'notistack'; | 5 | import { SnackbarProvider } from 'notistack'; |
5 | import React, { Suspense, lazy } from 'react'; | 6 | import React, { Suspense, lazy } from 'react'; |
@@ -77,7 +78,8 @@ const app = ( | |||
77 | <ThemeProvider> | 78 | <ThemeProvider> |
78 | <CssBaseline enableColorScheme /> | 79 | <CssBaseline enableColorScheme /> |
79 | <WindowControlsOverlayColor /> | 80 | <WindowControlsOverlayColor /> |
80 | <SnackbarProvider> | 81 | {/* @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes` */} |
82 | <SnackbarProvider TransitionComponent={Grow}> | ||
81 | <RegisterServiceWorker /> | 83 | <RegisterServiceWorker /> |
82 | <Box height="100vh" overflow="auto"> | 84 | <Box height="100vh" overflow="auto"> |
83 | <Suspense fallback={<Loading />}> | 85 | <Suspense fallback={<Loading />}> |
diff --git a/subprojects/frontend/src/xtext/ContentAssistService.ts b/subprojects/frontend/src/xtext/ContentAssistService.ts index 9e41f57b..101990af 100644 --- a/subprojects/frontend/src/xtext/ContentAssistService.ts +++ b/subprojects/frontend/src/xtext/ContentAssistService.ts | |||
@@ -115,6 +115,13 @@ export default class ContentAssistService { | |||
115 | } | 115 | } |
116 | 116 | ||
117 | async contentAssist(context: CompletionContext): Promise<CompletionResult> { | 117 | async contentAssist(context: CompletionContext): Promise<CompletionResult> { |
118 | if (!this.updateService.opened) { | ||
119 | this.lastCompletion = undefined; | ||
120 | return { | ||
121 | from: context.pos, | ||
122 | options: [], | ||
123 | }; | ||
124 | } | ||
118 | const tokenBefore = findToken(context); | 125 | const tokenBefore = findToken(context); |
119 | if (!context.explicit && !shouldCompleteImplicitly(tokenBefore, context)) { | 126 | if (!context.explicit && !shouldCompleteImplicitly(tokenBefore, context)) { |
120 | return { | 127 | return { |
diff --git a/subprojects/frontend/src/xtext/HighlightingService.ts b/subprojects/frontend/src/xtext/HighlightingService.ts index f9ab7b7e..a126ee40 100644 --- a/subprojects/frontend/src/xtext/HighlightingService.ts +++ b/subprojects/frontend/src/xtext/HighlightingService.ts | |||
@@ -31,4 +31,8 @@ export default class HighlightingService { | |||
31 | }); | 31 | }); |
32 | this.store.updateSemanticHighlighting(ranges); | 32 | this.store.updateSemanticHighlighting(ranges); |
33 | } | 33 | } |
34 | |||
35 | onDisconnect(): void { | ||
36 | this.store.updateSemanticHighlighting([]); | ||
37 | } | ||
34 | } | 38 | } |
diff --git a/subprojects/frontend/src/xtext/OccurrencesService.ts b/subprojects/frontend/src/xtext/OccurrencesService.ts index c8d6fd7b..9d738d76 100644 --- a/subprojects/frontend/src/xtext/OccurrencesService.ts +++ b/subprojects/frontend/src/xtext/OccurrencesService.ts | |||
@@ -41,6 +41,15 @@ export default class OccurrencesService { | |||
41 | private readonly updateService: UpdateService, | 41 | private readonly updateService: UpdateService, |
42 | ) {} | 42 | ) {} |
43 | 43 | ||
44 | onReconnect(): void { | ||
45 | this.clearOccurrences(); | ||
46 | this.findOccurrencesLater(); | ||
47 | } | ||
48 | |||
49 | onDisconnect(): void { | ||
50 | this.clearOccurrences(); | ||
51 | } | ||
52 | |||
44 | onTransaction(transaction: Transaction): void { | 53 | onTransaction(transaction: Transaction): void { |
45 | if (transaction.docChanged) { | 54 | if (transaction.docChanged) { |
46 | // Must clear occurrences asynchronously from `onTransaction`, | 55 | // Must clear occurrences asynchronously from `onTransaction`, |
@@ -91,7 +100,7 @@ export default class OccurrencesService { | |||
91 | } | 100 | } |
92 | 101 | ||
93 | private async updateOccurrences() { | 102 | private async updateOccurrences() { |
94 | if (!this.needsOccurrences) { | 103 | if (!this.needsOccurrences || !this.updateService.opened) { |
95 | this.clearOccurrences(); | 104 | this.clearOccurrences(); |
96 | return; | 105 | return; |
97 | } | 106 | } |
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts index d7471cdc..63e28652 100644 --- a/subprojects/frontend/src/xtext/UpdateService.ts +++ b/subprojects/frontend/src/xtext/UpdateService.ts | |||
@@ -82,6 +82,10 @@ export default class UpdateService { | |||
82 | } | 82 | } |
83 | } | 83 | } |
84 | 84 | ||
85 | get opened(): boolean { | ||
86 | return this.webSocketClient.opened; | ||
87 | } | ||
88 | |||
85 | private idleUpdate(): void { | 89 | private idleUpdate(): void { |
86 | if (!this.webSocketClient.opened || !this.tracker.needsUpdate) { | 90 | if (!this.webSocketClient.opened || !this.tracker.needsUpdate) { |
87 | return; | 91 | return; |
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts index e78318f7..72414590 100644 --- a/subprojects/frontend/src/xtext/ValidationService.ts +++ b/subprojects/frontend/src/xtext/ValidationService.ts | |||
@@ -28,4 +28,8 @@ export default class ValidationService { | |||
28 | }); | 28 | }); |
29 | this.store.updateDiagnostics(diagnostics); | 29 | this.store.updateDiagnostics(diagnostics); |
30 | } | 30 | } |
31 | |||
32 | onDisconnect(): void { | ||
33 | this.store.updateDiagnostics([]); | ||
34 | } | ||
31 | } | 35 | } |
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts index 6351c9fd..c02afb3b 100644 --- a/subprojects/frontend/src/xtext/XtextClient.ts +++ b/subprojects/frontend/src/xtext/XtextClient.ts | |||
@@ -18,7 +18,7 @@ import type { XtextWebPushService } from './xtextMessages'; | |||
18 | const log = getLogger('xtext.XtextClient'); | 18 | const log = getLogger('xtext.XtextClient'); |
19 | 19 | ||
20 | export default class XtextClient { | 20 | export default class XtextClient { |
21 | private readonly webSocketClient: XtextWebSocketClient; | 21 | readonly webSocketClient: XtextWebSocketClient; |
22 | 22 | ||
23 | private readonly updateService: UpdateService; | 23 | private readonly updateService: UpdateService; |
24 | 24 | ||
@@ -32,7 +32,8 @@ export default class XtextClient { | |||
32 | 32 | ||
33 | constructor(store: EditorStore) { | 33 | constructor(store: EditorStore) { |
34 | this.webSocketClient = new XtextWebSocketClient( | 34 | this.webSocketClient = new XtextWebSocketClient( |
35 | () => this.updateService.onReconnect(), | 35 | () => this.onReconnect(), |
36 | () => this.onDisconnect(), | ||
36 | (resource, stateId, service, push) => | 37 | (resource, stateId, service, push) => |
37 | this.onPush(resource, stateId, service, push), | 38 | this.onPush(resource, stateId, service, push), |
38 | ); | 39 | ); |
@@ -46,6 +47,17 @@ export default class XtextClient { | |||
46 | this.occurrencesService = new OccurrencesService(store, this.updateService); | 47 | this.occurrencesService = new OccurrencesService(store, this.updateService); |
47 | } | 48 | } |
48 | 49 | ||
50 | private onReconnect(): void { | ||
51 | this.updateService.onReconnect(); | ||
52 | this.occurrencesService.onReconnect(); | ||
53 | } | ||
54 | |||
55 | private onDisconnect(): void { | ||
56 | this.highlightingService.onDisconnect(); | ||
57 | this.validationService.onDisconnect(); | ||
58 | this.occurrencesService.onDisconnect(); | ||
59 | } | ||
60 | |||
49 | onTransaction(transaction: Transaction): void { | 61 | onTransaction(transaction: Transaction): void { |
50 | // `ContentAssistService.prototype.onTransaction` needs the dirty change desc | 62 | // `ContentAssistService.prototype.onTransaction` needs the dirty change desc |
51 | // _before_ the current edit, so we call it before `updateService`. | 63 | // _before_ the current edit, so we call it before `updateService`. |
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts index eedfa365..b69e1d6c 100644 --- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts +++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts | |||
@@ -22,6 +22,8 @@ const log = getLogger('xtext.XtextWebSocketClient'); | |||
22 | 22 | ||
23 | export type ReconnectHandler = () => void; | 23 | export type ReconnectHandler = () => void; |
24 | 24 | ||
25 | export type DisconnectHandler = () => void; | ||
26 | |||
25 | export type PushHandler = ( | 27 | export type PushHandler = ( |
26 | resourceId: string, | 28 | resourceId: string, |
27 | stateId: string, | 29 | stateId: string, |
@@ -136,6 +138,7 @@ export default class XtextWebSocketClient { | |||
136 | 138 | ||
137 | constructor( | 139 | constructor( |
138 | private readonly onReconnect: ReconnectHandler, | 140 | private readonly onReconnect: ReconnectHandler, |
141 | private readonly onDisconnect: DisconnectHandler, | ||
139 | private readonly onPush: PushHandler, | 142 | private readonly onPush: PushHandler, |
140 | ) { | 143 | ) { |
141 | this.interpreter | 144 | this.interpreter |
@@ -179,10 +182,18 @@ export default class XtextWebSocketClient { | |||
179 | return this.interpreter.state; | 182 | return this.interpreter.state; |
180 | } | 183 | } |
181 | 184 | ||
185 | get opening(): boolean { | ||
186 | return this.state.matches('connection.socketCreated.open.opening'); | ||
187 | } | ||
188 | |||
182 | get opened(): boolean { | 189 | get opened(): boolean { |
183 | return this.state.matches('connection.socketCreated.open.opened'); | 190 | return this.state.matches('connection.socketCreated.open.opened'); |
184 | } | 191 | } |
185 | 192 | ||
193 | get errors(): string[] { | ||
194 | return this.state.context.errors; | ||
195 | } | ||
196 | |||
186 | connect(): void { | 197 | connect(): void { |
187 | this.interpreter.send('CONNECT'); | 198 | this.interpreter.send('CONNECT'); |
188 | } | 199 | } |
@@ -261,6 +272,7 @@ export default class XtextWebSocketClient { | |||
261 | } | 272 | } |
262 | 273 | ||
263 | private cancelPendingRequests(): void { | 274 | private cancelPendingRequests(): void { |
275 | this.onDisconnect(); | ||
264 | this.pendingRequests.forEach((task) => | 276 | this.pendingRequests.forEach((task) => |
265 | task.reject(new CancelledError('Closing connection')), | 277 | task.reject(new CancelledError('Closing connection')), |
266 | ); | 278 | ); |