aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-06 22:32:04 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-08 01:24:50 +0200
commit5f8f4e6484faff23821ca7c009e309382fba914d (patch)
tree6d399fb90b4aa3527c30c502ba474cd83b5c94b2
parentfeat(frontend): handle page hide events (diff)
downloadrefinery-5f8f4e6484faff23821ca7c009e309382fba914d.tar.gz
refinery-5f8f4e6484faff23821ca7c009e309382fba914d.tar.zst
refinery-5f8f4e6484faff23821ca7c009e309382fba914d.zip
feat(frontend): check for updates periodically
-rw-r--r--subprojects/frontend/package.json21
-rw-r--r--subprojects/frontend/src/App.tsx15
-rw-r--r--subprojects/frontend/src/PWAStore.ts94
-rw-r--r--subprojects/frontend/src/RegisterServiceWorker.tsx86
-rw-r--r--subprojects/frontend/src/RootStore.tsx7
-rw-r--r--subprojects/frontend/src/UpdateNotification.tsx51
-rw-r--r--subprojects/frontend/src/editor/ConnectionStatusNotification.tsx49
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts5
-rw-r--r--subprojects/frontend/src/index.tsx17
-rw-r--r--subprojects/frontend/src/utils/useDelayedSnackbar.ts39
-rw-r--r--subprojects/frontend/src/xtext/OccurrencesService.ts5
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts4
-rw-r--r--subprojects/frontend/src/xtext/XtextWebSocketClient.ts3
-rw-r--r--subprojects/frontend/src/xtext/webSocketMachine.ts9
-rw-r--r--yarn.lock245
15 files changed, 359 insertions, 291 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 5856bb47..af345777 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -39,20 +39,21 @@
39 "@lezer/lr": "^1.2.3", 39 "@lezer/lr": "^1.2.3",
40 "@material-icons/svg": "^1.0.33", 40 "@material-icons/svg": "^1.0.33",
41 "@mui/icons-material": "5.10.3", 41 "@mui/icons-material": "5.10.3",
42 "@mui/material": "5.10.3", 42 "@mui/material": "5.10.4",
43 "ansi-styles": "^6.1.0", 43 "ansi-styles": "^6.1.0",
44 "escape-string-regexp": "^5.0.0", 44 "escape-string-regexp": "^5.0.0",
45 "lodash-es": "^4.17.21", 45 "lodash-es": "^4.17.21",
46 "loglevel": "^1.8.0", 46 "loglevel": "^1.8.0",
47 "loglevel-plugin-prefix": "^0.8.4", 47 "loglevel-plugin-prefix": "^0.8.4",
48 "mobx": "^6.6.1", 48 "mobx": "^6.6.2",
49 "mobx-react-lite": "^3.4.0", 49 "mobx-react-lite": "^3.4.0",
50 "ms": "^2.1.3",
50 "nanoid": "^4.0.0", 51 "nanoid": "^4.0.0",
51 "notistack": "^2.0.5", 52 "notistack": "^2.0.5",
52 "react": "^18.2.0", 53 "react": "^18.2.0",
53 "react-dom": "^18.2.0", 54 "react-dom": "^18.2.0",
54 "xstate": "^4.33.5", 55 "xstate": "^4.33.5",
55 "zod": "^3.18.0" 56 "zod": "^3.19.0"
56 }, 57 },
57 "devDependencies": { 58 "devDependencies": {
58 "@lezer/generator": "^1.1.1", 59 "@lezer/generator": "^1.1.1",
@@ -60,32 +61,32 @@
60 "@types/html-minifier-terser": "^7.0.0", 61 "@types/html-minifier-terser": "^7.0.0",
61 "@types/lodash-es": "^4.17.6", 62 "@types/lodash-es": "^4.17.6",
62 "@types/ms": "^0.7.31", 63 "@types/ms": "^0.7.31",
63 "@types/node": "^18.7.15", 64 "@types/node": "^18.7.16",
64 "@types/prettier": "^2.7.0", 65 "@types/prettier": "^2.7.0",
65 "@types/react": "^18.0.18", 66 "@types/react": "^18.0.18",
66 "@types/react-dom": "^18.0.6", 67 "@types/react-dom": "^18.0.6",
67 "@typescript-eslint/eslint-plugin": "^5.36.1", 68 "@typescript-eslint/eslint-plugin": "^5.36.2",
68 "@typescript-eslint/parser": "^5.36.1", 69 "@typescript-eslint/parser": "^5.36.2",
69 "@vitejs/plugin-react": "^2.1.0", 70 "@vitejs/plugin-react": "^2.1.0",
70 "@xstate/cli": "^0.3.2", 71 "@xstate/cli": "^0.3.3",
71 "cross-env": "^7.0.3", 72 "cross-env": "^7.0.3",
72 "eslint": "^8.23.0", 73 "eslint": "^8.23.0",
73 "eslint-config-airbnb": "^19.0.4", 74 "eslint-config-airbnb": "^19.0.4",
74 "eslint-config-airbnb-typescript": "^17.0.0", 75 "eslint-config-airbnb-typescript": "^17.0.0",
75 "eslint-config-prettier": "^8.5.0", 76 "eslint-config-prettier": "^8.5.0",
76 "eslint-import-resolver-typescript": "^3.5.0", 77 "eslint-import-resolver-typescript": "^3.5.1",
77 "eslint-plugin-import": "^2.26.0", 78 "eslint-plugin-import": "^2.26.0",
78 "eslint-plugin-jsx-a11y": "^6.6.1", 79 "eslint-plugin-jsx-a11y": "^6.6.1",
79 "eslint-plugin-mobx": "^0.0.9", 80 "eslint-plugin-mobx": "^0.0.9",
80 "eslint-plugin-prettier": "^4.2.1", 81 "eslint-plugin-prettier": "^4.2.1",
81 "eslint-plugin-react": "^7.31.6", 82 "eslint-plugin-react": "^7.31.7",
82 "eslint-plugin-react-hooks": "^4.6.0", 83 "eslint-plugin-react-hooks": "^4.6.0",
83 "html-minifier-terser": "^7.0.0", 84 "html-minifier-terser": "^7.0.0",
84 "prettier": "^2.7.1", 85 "prettier": "^2.7.1",
85 "typescript": "~4.8.2", 86 "typescript": "~4.8.2",
86 "vite": "^3.1.0", 87 "vite": "^3.1.0",
87 "vite-plugin-inject-preload": "^1.1.0", 88 "vite-plugin-inject-preload": "^1.1.0",
88 "vite-plugin-pwa": "^0.12.6", 89 "vite-plugin-pwa": "^0.12.7",
89 "workbox-window": "^6.5.4" 90 "workbox-window": "^6.5.4"
90 } 91 }
91} 92}
diff --git a/subprojects/frontend/src/App.tsx b/subprojects/frontend/src/App.tsx
index 3a25f43a..90514044 100644
--- a/subprojects/frontend/src/App.tsx
+++ b/subprojects/frontend/src/App.tsx
@@ -1,14 +1,21 @@
1import Grow from '@mui/material/Grow';
1import Stack from '@mui/material/Stack'; 2import Stack from '@mui/material/Stack';
3import { SnackbarProvider } from 'notistack';
2import React from 'react'; 4import React from 'react';
3 5
4import TopBar from './TopBar'; 6import TopBar from './TopBar';
7import UpdateNotification from './UpdateNotification';
5import EditorPane from './editor/EditorPane'; 8import EditorPane from './editor/EditorPane';
6 9
7export default function App(): JSX.Element { 10export default function App(): JSX.Element {
8 return ( 11 return (
9 <Stack direction="column" height="100vh" overflow="auto"> 12 // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes
10 <TopBar /> 13 <SnackbarProvider TransitionComponent={Grow}>
11 <EditorPane /> 14 <UpdateNotification />
12 </Stack> 15 <Stack direction="column" height="100vh" overflow="auto">
16 <TopBar />
17 <EditorPane />
18 </Stack>
19 </SnackbarProvider>
13 ); 20 );
14} 21}
diff --git a/subprojects/frontend/src/PWAStore.ts b/subprojects/frontend/src/PWAStore.ts
new file mode 100644
index 00000000..e9f99e2a
--- /dev/null
+++ b/subprojects/frontend/src/PWAStore.ts
@@ -0,0 +1,94 @@
1import { makeAutoObservable, observable } from 'mobx';
2import ms from 'ms';
3// eslint-disable-next-line import/no-unresolved -- Importing virtual module.
4import { registerSW } from 'virtual:pwa-register';
5
6import getLogger from './utils/getLogger';
7
8const log = getLogger('PWAStore');
9
10const UPDATE_INTERVAL = ms('30m');
11
12export default class PWAStore {
13 needsUpdate = false;
14
15 updateError = false;
16
17 private readonly updateSW: (
18 reloadPage?: boolean | undefined,
19 ) => Promise<void>;
20
21 private registration: ServiceWorkerRegistration | undefined;
22
23 constructor() {
24 if (window.location.host === 'localhost') {
25 // Do not register service worker during local development.
26 this.updateSW = () => Promise.resolve();
27 } else {
28 this.updateSW = registerSW({
29 onNeedRefresh: () => this.requestUpdate(),
30 onOfflineReady() {
31 log.debug('Service worker is ready for offline use');
32 },
33 onRegistered: (registration) => {
34 log.debug('Registered service worker');
35 this.setRegistration(registration);
36 },
37 onRegisterError(error) {
38 log.error('Failed to register service worker', error);
39 },
40 });
41 setInterval(() => this.checkForUpdates(), UPDATE_INTERVAL);
42 }
43 makeAutoObservable<PWAStore, 'updateSW' | 'registration'>(this, {
44 updateSW: false,
45 registration: observable.ref,
46 });
47 }
48
49 private requestUpdate(): void {
50 this.needsUpdate = true;
51 }
52
53 private setRegistration(
54 registration: ServiceWorkerRegistration | undefined,
55 ): void {
56 this.registration = registration;
57 }
58
59 private signalError(): void {
60 this.updateError = true;
61 }
62
63 private update(reloadPage?: boolean | undefined): void {
64 this.updateSW(reloadPage).catch((error) => {
65 log.error('Error while reloading page with updates', error);
66 this.signalError();
67 });
68 }
69
70 checkForUpdates(): void {
71 this.dismissError();
72 // In development mode, the service worker deactives itself,
73 // so we must watch out for a deactivated service worker before updating.
74 if (this.registration !== undefined && this.registration.active) {
75 this.registration.update().catch((error) => {
76 log.error('Error while updating service worker', error);
77 this.signalError();
78 });
79 }
80 }
81
82 reloadWithUpdate(): void {
83 this.dismissUpdate();
84 this.update(true);
85 }
86
87 dismissUpdate(): void {
88 this.needsUpdate = false;
89 }
90
91 dismissError(): void {
92 this.updateError = false;
93 }
94}
diff --git a/subprojects/frontend/src/RegisterServiceWorker.tsx b/subprojects/frontend/src/RegisterServiceWorker.tsx
deleted file mode 100644
index 5f46bc3d..00000000
--- a/subprojects/frontend/src/RegisterServiceWorker.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
1import Button from '@mui/material/Button';
2import {
3 type OptionsObject as SnackbarOptionsObject,
4 useSnackbar,
5} from 'notistack';
6import React, { useEffect } from 'react';
7// eslint-disable-next-line import/no-unresolved -- Importing virtual module.
8import { registerSW } from 'virtual:pwa-register';
9
10import { ContrastThemeProvider } from './theme/ThemeProvider';
11import getLogger from './utils/getLogger';
12
13const log = getLogger('RegisterServiceWorker');
14
15function UpdateSnackbarActions({
16 closeCurrentSnackbar,
17 enqueueSnackbar,
18 updateSW,
19}: {
20 closeCurrentSnackbar: () => void;
21 enqueueSnackbar: (
22 message: string,
23 options?: SnackbarOptionsObject | undefined,
24 ) => void;
25 updateSW: (reloadPage: boolean) => Promise<void>;
26}): JSX.Element {
27 return (
28 <ContrastThemeProvider>
29 <Button
30 color="primary"
31 onClick={() => {
32 closeCurrentSnackbar();
33 updateSW(true).catch((error) => {
34 log.error('Failed to update service worker', error);
35 enqueueSnackbar('Failed to download update', {
36 variant: 'error',
37 });
38 });
39 }}
40 >
41 Reload
42 </Button>
43 <Button color="inherit" onClick={closeCurrentSnackbar}>
44 Dismiss
45 </Button>
46 </ContrastThemeProvider>
47 );
48}
49
50export default function RegisterServiceWorker(): null {
51 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
52 useEffect(() => {
53 if (window.location.host === 'localhost') {
54 // Do not register service worker during local development.
55 return;
56 }
57 if (!('serviceWorker' in navigator)) {
58 log.debug('No service worker support found');
59 return;
60 }
61 const updateSW = registerSW({
62 onNeedRefresh() {
63 const key = enqueueSnackbar('An update for Refinery is available', {
64 persist: true,
65 action: (
66 <UpdateSnackbarActions
67 closeCurrentSnackbar={() => closeSnackbar(key)}
68 enqueueSnackbar={enqueueSnackbar}
69 updateSW={updateSW}
70 />
71 ),
72 });
73 },
74 onOfflineReady() {
75 log.debug('Service worker is ready for offline use');
76 },
77 onRegistered() {
78 log.debug('Registered service worker');
79 },
80 onRegisterError(error) {
81 log.error('Failed to register service worker', error);
82 },
83 });
84 }, [enqueueSnackbar, closeSnackbar]);
85 return null;
86}
diff --git a/subprojects/frontend/src/RootStore.tsx b/subprojects/frontend/src/RootStore.tsx
index 5aa580d1..c1674a3c 100644
--- a/subprojects/frontend/src/RootStore.tsx
+++ b/subprojects/frontend/src/RootStore.tsx
@@ -2,6 +2,7 @@ import { getLogger } from 'loglevel';
2import { makeAutoObservable, runInAction } from 'mobx'; 2import { makeAutoObservable, runInAction } from 'mobx';
3import React, { createContext, useContext } from 'react'; 3import React, { createContext, useContext } from 'react';
4 4
5import PWAStore from './PWAStore';
5import type EditorStore from './editor/EditorStore'; 6import type EditorStore from './editor/EditorStore';
6import ThemeStore from './theme/ThemeStore'; 7import ThemeStore from './theme/ThemeStore';
7 8
@@ -10,17 +11,21 @@ const log = getLogger('RootStore');
10export default class RootStore { 11export default class RootStore {
11 editorStore: EditorStore | undefined; 12 editorStore: EditorStore | undefined;
12 13
14 readonly pwaStore: PWAStore;
15
13 readonly themeStore: ThemeStore; 16 readonly themeStore: ThemeStore;
14 17
15 constructor(initialValue: string) { 18 constructor(initialValue: string) {
19 this.pwaStore = new PWAStore();
16 this.themeStore = new ThemeStore(); 20 this.themeStore = new ThemeStore();
17 makeAutoObservable(this, { 21 makeAutoObservable(this, {
22 pwaStore: false,
18 themeStore: false, 23 themeStore: false,
19 }); 24 });
20 import('./editor/EditorStore') 25 import('./editor/EditorStore')
21 .then(({ default: EditorStore }) => { 26 .then(({ default: EditorStore }) => {
22 runInAction(() => { 27 runInAction(() => {
23 this.editorStore = new EditorStore(initialValue); 28 this.editorStore = new EditorStore(initialValue, this.pwaStore);
24 }); 29 });
25 }) 30 })
26 .catch((error) => { 31 .catch((error) => {
diff --git a/subprojects/frontend/src/UpdateNotification.tsx b/subprojects/frontend/src/UpdateNotification.tsx
new file mode 100644
index 00000000..d260e3b7
--- /dev/null
+++ b/subprojects/frontend/src/UpdateNotification.tsx
@@ -0,0 +1,51 @@
1import Button from '@mui/material/Button';
2import { observer } from 'mobx-react-lite';
3import React, { useEffect } from 'react';
4
5import { useRootStore } from './RootStore';
6import { ContrastThemeProvider } from './theme/ThemeProvider';
7import useDelayedSnackbar from './utils/useDelayedSnackbar';
8
9export default observer(function UpdateNotification(): null {
10 const { pwaStore } = useRootStore();
11 const { needsUpdate, updateError } = pwaStore;
12 const enqueueLater = useDelayedSnackbar();
13
14 useEffect(() => {
15 if (needsUpdate) {
16 return enqueueLater('An update for Refinery is available', {
17 persist: true,
18 action: (
19 <ContrastThemeProvider>
20 <Button color="primary" onClick={() => pwaStore.reloadWithUpdate()}>
21 Reload
22 </Button>
23 <Button color="inherit" onClick={() => pwaStore.dismissUpdate()}>
24 Dismiss
25 </Button>
26 </ContrastThemeProvider>
27 ),
28 });
29 }
30
31 if (updateError) {
32 return enqueueLater('Failed to download update', {
33 variant: 'error',
34 action: (
35 <>
36 <Button color="inherit" onClick={() => pwaStore.checkForUpdates()}>
37 Try again
38 </Button>
39 <Button color="inherit" onClick={() => pwaStore.dismissError()}>
40 Dismiss
41 </Button>
42 </>
43 ),
44 });
45 }
46
47 return () => {};
48 }, [pwaStore, needsUpdate, updateError, enqueueLater]);
49
50 return null;
51});
diff --git a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
index 54c4e834..f7f089f0 100644
--- a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
+++ b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx
@@ -1,44 +1,12 @@
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 {
4 useSnackbar,
5 type SnackbarKey,
6 type SnackbarMessage,
7 type OptionsObject,
8} from 'notistack';
9import React, { useEffect } from 'react'; 3import React, { useEffect } from 'react';
10 4
11import { ContrastThemeProvider } from '../theme/ThemeProvider'; 5import { ContrastThemeProvider } from '../theme/ThemeProvider';
6import useDelayedSnackbar from '../utils/useDelayedSnackbar';
12 7
13import type EditorStore from './EditorStore'; 8import type EditorStore from './EditorStore';
14 9
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}
41
42export default observer(function ConnectionStatusNotification({ 10export default observer(function ConnectionStatusNotification({
43 editorStore, 11 editorStore,
44}: { 12}: {
@@ -51,13 +19,11 @@ export default observer(function ConnectionStatusNotification({
51 disconnectedByUser, 19 disconnectedByUser,
52 networkMissing, 20 networkMissing,
53 } = editorStore; 21 } = editorStore;
54 const { enqueueSnackbar, closeSnackbar } = useSnackbar(); 22 const enqueueLater = useDelayedSnackbar(350);
55 23
56 useEffect(() => { 24 useEffect(() => {
57 if (opening) { 25 if (opening) {
58 return enqueueLater( 26 return enqueueLater(
59 enqueueSnackbar,
60 closeSnackbar,
61 'Connecting to Refinery', 27 'Connecting to Refinery',
62 { 28 {
63 persist: true, 29 persist: true,
@@ -73,8 +39,6 @@ export default observer(function ConnectionStatusNotification({
73 39
74 if (connectionErrors.length >= 1 && !opening) { 40 if (connectionErrors.length >= 1 && !opening) {
75 return enqueueLater( 41 return enqueueLater(
76 enqueueSnackbar,
77 closeSnackbar,
78 <div> 42 <div>
79 Connection error:{' '} 43 Connection error:{' '}
80 <b>{connectionErrors[connectionErrors.length - 1]}</b> 44 <b>{connectionErrors[connectionErrors.length - 1]}</b>
@@ -110,8 +74,6 @@ export default observer(function ConnectionStatusNotification({
110 if (networkMissing) { 74 if (networkMissing) {
111 if (disconnectedByUser) { 75 if (disconnectedByUser) {
112 return enqueueLater( 76 return enqueueLater(
113 enqueueSnackbar,
114 closeSnackbar,
115 <div> 77 <div>
116 <b>No network connection:</b> Some editing features might be 78 <b>No network connection:</b> Some editing features might be
117 degraded 79 degraded
@@ -130,8 +92,6 @@ export default observer(function ConnectionStatusNotification({
130 } 92 }
131 93
132 return enqueueLater( 94 return enqueueLater(
133 enqueueSnackbar,
134 closeSnackbar,
135 <div> 95 <div>
136 <b>No network connection:</b> Refinery will try to reconnect when the 96 <b>No network connection:</b> Refinery will try to reconnect when the
137 connection is restored 97 connection is restored
@@ -154,8 +114,6 @@ export default observer(function ConnectionStatusNotification({
154 114
155 if (disconnectedByUser) { 115 if (disconnectedByUser) {
156 return enqueueLater( 116 return enqueueLater(
157 enqueueSnackbar,
158 closeSnackbar,
159 <div> 117 <div>
160 <b>Not connected to Refinery:</b> Some editing features might be 118 <b>Not connected to Refinery:</b> Some editing features might be
161 degraded 119 degraded
@@ -181,8 +139,7 @@ export default observer(function ConnectionStatusNotification({
181 connectionErrors, 139 connectionErrors,
182 disconnectedByUser, 140 disconnectedByUser,
183 networkMissing, 141 networkMissing,
184 closeSnackbar, 142 enqueueLater,
185 enqueueSnackbar,
186 ]); 143 ]);
187 144
188 return null; 145 return null;
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index ecbe6ef8..c74e732f 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -16,6 +16,7 @@ import { type Command, EditorView } from '@codemirror/view';
16import { makeAutoObservable, observable } from 'mobx'; 16import { makeAutoObservable, observable } from 'mobx';
17import { nanoid } from 'nanoid'; 17import { nanoid } from 'nanoid';
18 18
19import type PWAStore from '../PWAStore';
19import getLogger from '../utils/getLogger'; 20import getLogger from '../utils/getLogger';
20import XtextClient from '../xtext/XtextClient'; 21import XtextClient from '../xtext/XtextClient';
21 22
@@ -51,10 +52,10 @@ export default class EditorStore {
51 52
52 infoCount = 0; 53 infoCount = 0;
53 54
54 constructor(initialValue: string) { 55 constructor(initialValue: string, pwaStore: PWAStore) {
55 this.id = nanoid(); 56 this.id = nanoid();
56 this.state = createEditorState(initialValue, this); 57 this.state = createEditorState(initialValue, this);
57 this.client = new XtextClient(this); 58 this.client = new XtextClient(this, pwaStore);
58 this.searchPanel = new SearchPanelStore(this); 59 this.searchPanel = new SearchPanelStore(this);
59 this.lintPanel = new LintPanelStore(this); 60 this.lintPanel = new LintPanelStore(this);
60 makeAutoObservable<EditorStore, 'client'>(this, { 61 makeAutoObservable<EditorStore, 'client'>(this, {
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx
index 5760327e..55e0590f 100644
--- a/subprojects/frontend/src/index.tsx
+++ b/subprojects/frontend/src/index.tsx
@@ -1,13 +1,10 @@
1import Box from '@mui/material/Box'; 1import Box from '@mui/material/Box';
2import CssBaseline from '@mui/material/CssBaseline'; 2import CssBaseline from '@mui/material/CssBaseline';
3import Grow from '@mui/material/Grow';
4import { configure } from 'mobx'; 3import { configure } from 'mobx';
5import { SnackbarProvider } from 'notistack';
6import React, { Suspense, lazy } from 'react'; 4import React, { Suspense, lazy } from 'react';
7import { createRoot } from 'react-dom/client'; 5import { createRoot } from 'react-dom/client';
8 6
9import Loading from './Loading'; 7import Loading from './Loading';
10import RegisterServiceWorker from './RegisterServiceWorker';
11import RootStore, { RootStoreProvider } from './RootStore'; 8import RootStore, { RootStoreProvider } from './RootStore';
12import WindowControlsOverlayColor from './WindowControlsOverlayColor'; 9import WindowControlsOverlayColor from './WindowControlsOverlayColor';
13import ThemeProvider from './theme/ThemeProvider'; 10import ThemeProvider from './theme/ThemeProvider';
@@ -78,15 +75,11 @@ const app = (
78 <ThemeProvider> 75 <ThemeProvider>
79 <CssBaseline enableColorScheme /> 76 <CssBaseline enableColorScheme />
80 <WindowControlsOverlayColor /> 77 <WindowControlsOverlayColor />
81 {/* @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes` */} 78 <Box height="100vh" overflow="auto">
82 <SnackbarProvider TransitionComponent={Grow}> 79 <Suspense fallback={<Loading />}>
83 <RegisterServiceWorker /> 80 <App />
84 <Box height="100vh" overflow="auto"> 81 </Suspense>
85 <Suspense fallback={<Loading />}> 82 </Box>
86 <App />
87 </Suspense>
88 </Box>
89 </SnackbarProvider>
90 </ThemeProvider> 83 </ThemeProvider>
91 </RootStoreProvider> 84 </RootStoreProvider>
92 </React.StrictMode> 85 </React.StrictMode>
diff --git a/subprojects/frontend/src/utils/useDelayedSnackbar.ts b/subprojects/frontend/src/utils/useDelayedSnackbar.ts
new file mode 100644
index 00000000..03ad6caa
--- /dev/null
+++ b/subprojects/frontend/src/utils/useDelayedSnackbar.ts
@@ -0,0 +1,39 @@
1import {
2 useSnackbar,
3 type SnackbarKey,
4 type SnackbarMessage,
5 type OptionsObject,
6} from 'notistack';
7import { useCallback } from 'react';
8
9export default function useDelayedSnackbar(
10 defaultDelay = 0,
11): (
12 message: SnackbarMessage,
13 options?: OptionsObject | undefined,
14 delay?: number | undefined,
15) => () => void {
16 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
17 return useCallback(
18 (
19 message: SnackbarMessage,
20 options?: OptionsObject | undefined,
21 delay = defaultDelay,
22 ) => {
23 let key: SnackbarKey | undefined;
24 let timeout: number | undefined = setTimeout(() => {
25 timeout = undefined;
26 key = enqueueSnackbar(message, options);
27 }, delay);
28 return () => {
29 if (timeout !== undefined) {
30 clearTimeout(timeout);
31 }
32 if (key !== undefined) {
33 closeSnackbar(key);
34 }
35 };
36 },
37 [defaultDelay, enqueueSnackbar, closeSnackbar],
38 );
39}
diff --git a/subprojects/frontend/src/xtext/OccurrencesService.ts b/subprojects/frontend/src/xtext/OccurrencesService.ts
index 9d738d76..248a9a87 100644
--- a/subprojects/frontend/src/xtext/OccurrencesService.ts
+++ b/subprojects/frontend/src/xtext/OccurrencesService.ts
@@ -1,5 +1,6 @@
1import { Transaction } from '@codemirror/state'; 1import { Transaction } from '@codemirror/state';
2import { debounce } from 'lodash-es'; 2import { debounce } from 'lodash-es';
3import ms from 'ms';
3 4
4import type EditorStore from '../editor/EditorStore'; 5import type EditorStore from '../editor/EditorStore';
5import { 6import {
@@ -11,7 +12,7 @@ import getLogger from '../utils/getLogger';
11import type UpdateService from './UpdateService'; 12import type UpdateService from './UpdateService';
12import type { TextRegion } from './xtextServiceResults'; 13import type { TextRegion } from './xtextServiceResults';
13 14
14const FIND_OCCURRENCES_TIMEOUT_MS = 1000; 15const FIND_OCCURRENCES_TIMEOUT = ms('1s');
15 16
16const log = getLogger('xtext.OccurrencesService'); 17const log = getLogger('xtext.OccurrencesService');
17 18
@@ -33,7 +34,7 @@ export default class OccurrencesService {
33 34
34 private readonly findOccurrencesLater = debounce( 35 private readonly findOccurrencesLater = debounce(
35 () => this.findOccurrences(), 36 () => this.findOccurrences(),
36 FIND_OCCURRENCES_TIMEOUT_MS, 37 FIND_OCCURRENCES_TIMEOUT,
37 ); 38 );
38 39
39 constructor( 40 constructor(
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index 1f7e446f..cd5d280d 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -4,6 +4,7 @@ import type {
4} from '@codemirror/autocomplete'; 4} from '@codemirror/autocomplete';
5import type { Transaction } from '@codemirror/state'; 5import type { Transaction } from '@codemirror/state';
6 6
7import type PWAStore from '../PWAStore';
7import type EditorStore from '../editor/EditorStore'; 8import type EditorStore from '../editor/EditorStore';
8import getLogger from '../utils/getLogger'; 9import getLogger from '../utils/getLogger';
9 10
@@ -30,7 +31,7 @@ export default class XtextClient {
30 31
31 private readonly occurrencesService: OccurrencesService; 32 private readonly occurrencesService: OccurrencesService;
32 33
33 constructor(store: EditorStore) { 34 constructor(store: EditorStore, private readonly pwaStore: PWAStore) {
34 this.webSocketClient = new XtextWebSocketClient( 35 this.webSocketClient = new XtextWebSocketClient(
35 () => this.onReconnect(), 36 () => this.onReconnect(),
36 () => this.onDisconnect(), 37 () => this.onDisconnect(),
@@ -54,6 +55,7 @@ export default class XtextClient {
54 private onReconnect(): void { 55 private onReconnect(): void {
55 this.updateService.onReconnect(); 56 this.updateService.onReconnect();
56 this.occurrencesService.onReconnect(); 57 this.occurrencesService.onReconnect();
58 this.pwaStore.checkForUpdates();
57 } 59 }
58 60
59 private onDisconnect(): void { 61 private onDisconnect(): void {
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
index cba6f064..a39620cb 100644
--- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
+++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
@@ -1,4 +1,5 @@
1import { createAtom, makeAutoObservable, observable } from 'mobx'; 1import { createAtom, makeAutoObservable, observable } from 'mobx';
2import ms from 'ms';
2import { nanoid } from 'nanoid'; 3import { nanoid } from 'nanoid';
3import { interpret } from 'xstate'; 4import { interpret } from 'xstate';
4 5
@@ -18,7 +19,7 @@ const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1';
18 19
19// Use a large enough timeout so that a request can complete successfully 20// Use a large enough timeout so that a request can complete successfully
20// even if the browser has throttled the background tab. 21// even if the browser has throttled the background tab.
21const REQUEST_TIMEOUT = 5000; 22const REQUEST_TIMEOUT = ms('5s');
22 23
23const log = getLogger('xtext.XtextWebSocketClient'); 24const log = getLogger('xtext.XtextWebSocketClient');
24 25
diff --git a/subprojects/frontend/src/xtext/webSocketMachine.ts b/subprojects/frontend/src/xtext/webSocketMachine.ts
index 25689cec..a1eee781 100644
--- a/subprojects/frontend/src/xtext/webSocketMachine.ts
+++ b/subprojects/frontend/src/xtext/webSocketMachine.ts
@@ -1,8 +1,9 @@
1import ms from 'ms';
1import { actions, assign, createMachine, RaiseAction } from 'xstate'; 2import { actions, assign, createMachine, RaiseAction } from 'xstate';
2 3
3const { raise } = actions; 4const { raise } = actions;
4 5
5const ERROR_WAIT_TIMES = [200, 1000, 5000, 30_000]; 6const ERROR_WAIT_TIMES = ['200', '1s', '5s', '30s'].map(ms);
6 7
7export interface WebSocketContext { 8export interface WebSocketContext {
8 webSocketURL: string | undefined; 9 webSocketURL: string | undefined;
@@ -229,9 +230,9 @@ export default createMachine(
229 needsNetwork: ({ webSocketURL }) => !isWebSocketURLLocal(webSocketURL), 230 needsNetwork: ({ webSocketURL }) => !isWebSocketURLLocal(webSocketURL),
230 }, 231 },
231 delays: { 232 delays: {
232 IDLE_TIMEOUT: 300_000, 233 IDLE_TIMEOUT: ms('5m'),
233 OPEN_TIMEOUT: 10_000, 234 OPEN_TIMEOUT: ms('10s'),
234 PING_PERIOD: 10_000, 235 PING_PERIOD: ms('10s'),
235 ERROR_WAIT_TIME: ({ errors: { length: retryCount } }) => { 236 ERROR_WAIT_TIME: ({ errors: { length: retryCount } }) => {
236 const { length } = ERROR_WAIT_TIMES; 237 const { length } = ERROR_WAIT_TIMES;
237 const index = retryCount < length ? retryCount : length - 1; 238 const index = retryCount < length ? retryCount : length - 1;
diff --git a/yarn.lock b/yarn.lock
index a16d8715..57fae9b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1756,9 +1756,9 @@ __metadata:
1756 languageName: node 1756 languageName: node
1757 linkType: hard 1757 linkType: hard
1758 1758
1759"@mui/base@npm:5.0.0-alpha.95": 1759"@mui/base@npm:5.0.0-alpha.96":
1760 version: 5.0.0-alpha.95 1760 version: 5.0.0-alpha.96
1761 resolution: "@mui/base@npm:5.0.0-alpha.95" 1761 resolution: "@mui/base@npm:5.0.0-alpha.96"
1762 dependencies: 1762 dependencies:
1763 "@babel/runtime": ^7.18.9 1763 "@babel/runtime": ^7.18.9
1764 "@emotion/is-prop-valid": ^1.2.0 1764 "@emotion/is-prop-valid": ^1.2.0
@@ -1775,14 +1775,14 @@ __metadata:
1775 peerDependenciesMeta: 1775 peerDependenciesMeta:
1776 "@types/react": 1776 "@types/react":
1777 optional: true 1777 optional: true
1778 checksum: b05f18ed2f1fbb6e1ab1d58e54e38b09aa43ad10a0f5e8cf7c86da745a1feca298429c34d2437298b3125c3096cf8b32ee18ad4dbc616ac88d2bd6be57b5c21c 1778 checksum: 5e35d2c0d5777ea7085f65e45955262ee2d213d62c5a2c2776a234d0c446cbf1f1ed9d19a571359f8ef9b0f9f983c17772041eb5f947b6d9abd06ed9b1dccef0
1779 languageName: node 1779 languageName: node
1780 linkType: hard 1780 linkType: hard
1781 1781
1782"@mui/core-downloads-tracker@npm:^5.10.3": 1782"@mui/core-downloads-tracker@npm:^5.10.4":
1783 version: 5.10.3 1783 version: 5.10.4
1784 resolution: "@mui/core-downloads-tracker@npm:5.10.3" 1784 resolution: "@mui/core-downloads-tracker@npm:5.10.4"
1785 checksum: 07c7d3ddda9dfcd402633e7ab7b367505cbc097ac7ae59bb9232caa11896902e76241f5bf905b62b0c96429cb94009e1006492c172926390f1936fb6e5a1326d 1785 checksum: a4249d4cd02a50f353e7718d14becc9e03b0b5438420d840b49ea4a177b62f90a1953df17ed4126f729b3b180cd853f50d4343a37013631d72e7578cc1b9bc60
1786 languageName: node 1786 languageName: node
1787 linkType: hard 1787 linkType: hard
1788 1788
@@ -1802,14 +1802,14 @@ __metadata:
1802 languageName: node 1802 languageName: node
1803 linkType: hard 1803 linkType: hard
1804 1804
1805"@mui/material@npm:5.10.3": 1805"@mui/material@npm:5.10.4":
1806 version: 5.10.3 1806 version: 5.10.4
1807 resolution: "@mui/material@npm:5.10.3" 1807 resolution: "@mui/material@npm:5.10.4"
1808 dependencies: 1808 dependencies:
1809 "@babel/runtime": ^7.18.9 1809 "@babel/runtime": ^7.18.9
1810 "@mui/base": 5.0.0-alpha.95 1810 "@mui/base": 5.0.0-alpha.96
1811 "@mui/core-downloads-tracker": ^5.10.3 1811 "@mui/core-downloads-tracker": ^5.10.4
1812 "@mui/system": ^5.10.3 1812 "@mui/system": ^5.10.4
1813 "@mui/types": ^7.2.0 1813 "@mui/types": ^7.2.0
1814 "@mui/utils": ^5.10.3 1814 "@mui/utils": ^5.10.3
1815 "@types/react-transition-group": ^4.4.5 1815 "@types/react-transition-group": ^4.4.5
@@ -1831,7 +1831,7 @@ __metadata:
1831 optional: true 1831 optional: true
1832 "@types/react": 1832 "@types/react":
1833 optional: true 1833 optional: true
1834 checksum: af7502c5340bfd4a777daa986940a86664e3c3c724fe9e2821f95172cbc55e27e85bbad011f1c4e0554df93aed1d881d81d8fad16f8b8e011e7b2d2b31f34c9c 1834 checksum: dfee653ca623608c32d78c4281b2be3bc07eaa3dea3198d0e6cdd03fdfad1296053d02078e27202c5e88a133298438253aa68196dc9ef7d4760121753cd135fb
1835 languageName: node 1835 languageName: node
1836 linkType: hard 1836 linkType: hard
1837 1837
@@ -1852,9 +1852,9 @@ __metadata:
1852 languageName: node 1852 languageName: node
1853 linkType: hard 1853 linkType: hard
1854 1854
1855"@mui/styled-engine@npm:^5.10.3": 1855"@mui/styled-engine@npm:^5.10.4":
1856 version: 5.10.3 1856 version: 5.10.4
1857 resolution: "@mui/styled-engine@npm:5.10.3" 1857 resolution: "@mui/styled-engine@npm:5.10.4"
1858 dependencies: 1858 dependencies:
1859 "@babel/runtime": ^7.18.9 1859 "@babel/runtime": ^7.18.9
1860 "@emotion/cache": ^11.10.3 1860 "@emotion/cache": ^11.10.3
@@ -1869,17 +1869,17 @@ __metadata:
1869 optional: true 1869 optional: true
1870 "@emotion/styled": 1870 "@emotion/styled":
1871 optional: true 1871 optional: true
1872 checksum: cc465991fb5f32fd56b7123183631c3c45e8c8f7a69d42a5050c461132ad5fb01f0e389488b9515b274506d92abcc8119da6289ea8652cf44f100a4d442ac8a5 1872 checksum: d9bbb38c4bef00e214d5eb5ccdce1cc98935292e4dff94315c81d77fc36d673fc69b128fa234a702fc5822a22b55ce880bb0e0da10849727df4b54a55587e650
1873 languageName: node 1873 languageName: node
1874 linkType: hard 1874 linkType: hard
1875 1875
1876"@mui/system@npm:^5.10.3": 1876"@mui/system@npm:^5.10.4":
1877 version: 5.10.3 1877 version: 5.10.4
1878 resolution: "@mui/system@npm:5.10.3" 1878 resolution: "@mui/system@npm:5.10.4"
1879 dependencies: 1879 dependencies:
1880 "@babel/runtime": ^7.18.9 1880 "@babel/runtime": ^7.18.9
1881 "@mui/private-theming": ^5.10.3 1881 "@mui/private-theming": ^5.10.3
1882 "@mui/styled-engine": ^5.10.3 1882 "@mui/styled-engine": ^5.10.4
1883 "@mui/types": ^7.2.0 1883 "@mui/types": ^7.2.0
1884 "@mui/utils": ^5.10.3 1884 "@mui/utils": ^5.10.3
1885 clsx: ^1.2.1 1885 clsx: ^1.2.1
@@ -1897,7 +1897,7 @@ __metadata:
1897 optional: true 1897 optional: true
1898 "@types/react": 1898 "@types/react":
1899 optional: true 1899 optional: true
1900 checksum: fee332cfb091c00d3b8fbdc020da1c5bc8a257fb38dafa72b7966a2b38f1d65aa27dcf113b9b016f9bfa82e5e974819e0d7a20f8c5c6ff18810eb5e2885541d0 1900 checksum: ae14f4d05c45fba5e5c52e79d9e2a02c002fa03a8ea7636800a3715fe6c598c24db52c9e60206c0e0ffc0aaa76054598e15d8a547370d23e89bedb54c136dcee
1901 languageName: node 1901 languageName: node
1902 linkType: hard 1902 linkType: hard
1903 1903
@@ -2017,19 +2017,19 @@ __metadata:
2017 "@lezer/lr": ^1.2.3 2017 "@lezer/lr": ^1.2.3
2018 "@material-icons/svg": ^1.0.33 2018 "@material-icons/svg": ^1.0.33
2019 "@mui/icons-material": 5.10.3 2019 "@mui/icons-material": 5.10.3
2020 "@mui/material": 5.10.3 2020 "@mui/material": 5.10.4
2021 "@types/eslint": ^8.4.6 2021 "@types/eslint": ^8.4.6
2022 "@types/html-minifier-terser": ^7.0.0 2022 "@types/html-minifier-terser": ^7.0.0
2023 "@types/lodash-es": ^4.17.6 2023 "@types/lodash-es": ^4.17.6
2024 "@types/ms": ^0.7.31 2024 "@types/ms": ^0.7.31
2025 "@types/node": ^18.7.15 2025 "@types/node": ^18.7.16
2026 "@types/prettier": ^2.7.0 2026 "@types/prettier": ^2.7.0
2027 "@types/react": ^18.0.18 2027 "@types/react": ^18.0.18
2028 "@types/react-dom": ^18.0.6 2028 "@types/react-dom": ^18.0.6
2029 "@typescript-eslint/eslint-plugin": ^5.36.1 2029 "@typescript-eslint/eslint-plugin": ^5.36.2
2030 "@typescript-eslint/parser": ^5.36.1 2030 "@typescript-eslint/parser": ^5.36.2
2031 "@vitejs/plugin-react": ^2.1.0 2031 "@vitejs/plugin-react": ^2.1.0
2032 "@xstate/cli": ^0.3.2 2032 "@xstate/cli": ^0.3.3
2033 ansi-styles: ^6.1.0 2033 ansi-styles: ^6.1.0
2034 cross-env: ^7.0.3 2034 cross-env: ^7.0.3
2035 escape-string-regexp: ^5.0.0 2035 escape-string-regexp: ^5.0.0
@@ -2037,19 +2037,20 @@ __metadata:
2037 eslint-config-airbnb: ^19.0.4 2037 eslint-config-airbnb: ^19.0.4
2038 eslint-config-airbnb-typescript: ^17.0.0 2038 eslint-config-airbnb-typescript: ^17.0.0
2039 eslint-config-prettier: ^8.5.0 2039 eslint-config-prettier: ^8.5.0
2040 eslint-import-resolver-typescript: ^3.5.0 2040 eslint-import-resolver-typescript: ^3.5.1
2041 eslint-plugin-import: ^2.26.0 2041 eslint-plugin-import: ^2.26.0
2042 eslint-plugin-jsx-a11y: ^6.6.1 2042 eslint-plugin-jsx-a11y: ^6.6.1
2043 eslint-plugin-mobx: ^0.0.9 2043 eslint-plugin-mobx: ^0.0.9
2044 eslint-plugin-prettier: ^4.2.1 2044 eslint-plugin-prettier: ^4.2.1
2045 eslint-plugin-react: ^7.31.6 2045 eslint-plugin-react: ^7.31.7
2046 eslint-plugin-react-hooks: ^4.6.0 2046 eslint-plugin-react-hooks: ^4.6.0
2047 html-minifier-terser: ^7.0.0 2047 html-minifier-terser: ^7.0.0
2048 lodash-es: ^4.17.21 2048 lodash-es: ^4.17.21
2049 loglevel: ^1.8.0 2049 loglevel: ^1.8.0
2050 loglevel-plugin-prefix: ^0.8.4 2050 loglevel-plugin-prefix: ^0.8.4
2051 mobx: ^6.6.1 2051 mobx: ^6.6.2
2052 mobx-react-lite: ^3.4.0 2052 mobx-react-lite: ^3.4.0
2053 ms: ^2.1.3
2053 nanoid: ^4.0.0 2054 nanoid: ^4.0.0
2054 notistack: ^2.0.5 2055 notistack: ^2.0.5
2055 prettier: ^2.7.1 2056 prettier: ^2.7.1
@@ -2058,10 +2059,10 @@ __metadata:
2058 typescript: ~4.8.2 2059 typescript: ~4.8.2
2059 vite: ^3.1.0 2060 vite: ^3.1.0
2060 vite-plugin-inject-preload: ^1.1.0 2061 vite-plugin-inject-preload: ^1.1.0
2061 vite-plugin-pwa: ^0.12.6 2062 vite-plugin-pwa: ^0.12.7
2062 workbox-window: ^6.5.4 2063 workbox-window: ^6.5.4
2063 xstate: ^4.33.5 2064 xstate: ^4.33.5
2064 zod: ^3.18.0 2065 zod: ^3.19.0
2065 languageName: unknown 2066 languageName: unknown
2066 linkType: soft 2067 linkType: soft
2067 2068
@@ -2219,10 +2220,10 @@ __metadata:
2219 languageName: node 2220 languageName: node
2220 linkType: hard 2221 linkType: hard
2221 2222
2222"@types/node@npm:*, @types/node@npm:^18.7.15": 2223"@types/node@npm:*, @types/node@npm:^18.7.16":
2223 version: 18.7.15 2224 version: 18.7.16
2224 resolution: "@types/node@npm:18.7.15" 2225 resolution: "@types/node@npm:18.7.16"
2225 checksum: 1435fc7fe44744467a3ba8ace646455be228516530dbb3db64a03cca6abcfdc5ba2e48a3eafc71f25f836d2ca871132361c58a5e760237a53674cb09151147d9 2226 checksum: 01a3d35c764a3f0e7370b56e1ad4203731131883c65784e020009014171b3f53c4649cde6c7aa4f1026b907ee87ef6ae6ece2bc518151dc7b81100fe8b1db3ad
2226 languageName: node 2227 languageName: node
2227 linkType: hard 2228 linkType: hard
2228 2229
@@ -2308,13 +2309,13 @@ __metadata:
2308 languageName: node 2309 languageName: node
2309 linkType: hard 2310 linkType: hard
2310 2311
2311"@typescript-eslint/eslint-plugin@npm:^5.36.1": 2312"@typescript-eslint/eslint-plugin@npm:^5.36.2":
2312 version: 5.36.1 2313 version: 5.36.2
2313 resolution: "@typescript-eslint/eslint-plugin@npm:5.36.1" 2314 resolution: "@typescript-eslint/eslint-plugin@npm:5.36.2"
2314 dependencies: 2315 dependencies:
2315 "@typescript-eslint/scope-manager": 5.36.1 2316 "@typescript-eslint/scope-manager": 5.36.2
2316 "@typescript-eslint/type-utils": 5.36.1 2317 "@typescript-eslint/type-utils": 5.36.2
2317 "@typescript-eslint/utils": 5.36.1 2318 "@typescript-eslint/utils": 5.36.2
2318 debug: ^4.3.4 2319 debug: ^4.3.4
2319 functional-red-black-tree: ^1.0.1 2320 functional-red-black-tree: ^1.0.1
2320 ignore: ^5.2.0 2321 ignore: ^5.2.0
@@ -2327,43 +2328,43 @@ __metadata:
2327 peerDependenciesMeta: 2328 peerDependenciesMeta:
2328 typescript: 2329 typescript:
2329 optional: true 2330 optional: true
2330 checksum: a4c555688d840c3ff0d3d71ceca583291e206cc523eade45c56fb8e1c8af84ae50ef8d344cdf8e3f9c38f430bc03c95eb8d49870094e0e5b57e0fa3e61c0ec91 2331 checksum: edcd9fcecdeb22a689b421cafe3b7adc859bf2fd6227aecdd7412c319c808e7bab063c8f94af32116cfc971962f9780d181cb0a4aa999951c2d2be1f84c6c376
2331 languageName: node 2332 languageName: node
2332 linkType: hard 2333 linkType: hard
2333 2334
2334"@typescript-eslint/parser@npm:^5.36.1": 2335"@typescript-eslint/parser@npm:^5.36.2":
2335 version: 5.36.1 2336 version: 5.36.2
2336 resolution: "@typescript-eslint/parser@npm:5.36.1" 2337 resolution: "@typescript-eslint/parser@npm:5.36.2"
2337 dependencies: 2338 dependencies:
2338 "@typescript-eslint/scope-manager": 5.36.1 2339 "@typescript-eslint/scope-manager": 5.36.2
2339 "@typescript-eslint/types": 5.36.1 2340 "@typescript-eslint/types": 5.36.2
2340 "@typescript-eslint/typescript-estree": 5.36.1 2341 "@typescript-eslint/typescript-estree": 5.36.2
2341 debug: ^4.3.4 2342 debug: ^4.3.4
2342 peerDependencies: 2343 peerDependencies:
2343 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 2344 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
2344 peerDependenciesMeta: 2345 peerDependenciesMeta:
2345 typescript: 2346 typescript:
2346 optional: true 2347 optional: true
2347 checksum: 0f0f94e56ae1d55b6e7223ce5a2b0c93e5cc082ef2951a2b24ae4b22bb8ffbeb90d2d16682bfa8bc972ba2c7fb4703aedd79b7dbd09bcee397e1ab90d11506d9 2348 checksum: d6cc22cbc7aacb5ecebf55eb1d681cb6b964b108e147b418295c3e48701a77768cff128c16da421ae50eabb9f1296ecec7fa3cc5f2ccb63a3febf79f98b4195f
2348 languageName: node 2349 languageName: node
2349 linkType: hard 2350 linkType: hard
2350 2351
2351"@typescript-eslint/scope-manager@npm:5.36.1": 2352"@typescript-eslint/scope-manager@npm:5.36.2":
2352 version: 5.36.1 2353 version: 5.36.2
2353 resolution: "@typescript-eslint/scope-manager@npm:5.36.1" 2354 resolution: "@typescript-eslint/scope-manager@npm:5.36.2"
2354 dependencies: 2355 dependencies:
2355 "@typescript-eslint/types": 5.36.1 2356 "@typescript-eslint/types": 5.36.2
2356 "@typescript-eslint/visitor-keys": 5.36.1 2357 "@typescript-eslint/visitor-keys": 5.36.2
2357 checksum: c46497226af75baed7458838ec0bfbddf19f8084115d78b915b46a8ceb4c05619ac61da127dfd3c8ee11bc916896d57bf8b9f936b0306ce69658160f910e3ad0 2358 checksum: 93ff655f7c237c88ec6dc5911202dd8f81bd8909b27f1a758a9d77e9791040f1ee6fe2891314bde75c808ce586246e98003a1b1396937b0312f2440016dea751
2358 languageName: node 2359 languageName: node
2359 linkType: hard 2360 linkType: hard
2360 2361
2361"@typescript-eslint/type-utils@npm:5.36.1": 2362"@typescript-eslint/type-utils@npm:5.36.2":
2362 version: 5.36.1 2363 version: 5.36.2
2363 resolution: "@typescript-eslint/type-utils@npm:5.36.1" 2364 resolution: "@typescript-eslint/type-utils@npm:5.36.2"
2364 dependencies: 2365 dependencies:
2365 "@typescript-eslint/typescript-estree": 5.36.1 2366 "@typescript-eslint/typescript-estree": 5.36.2
2366 "@typescript-eslint/utils": 5.36.1 2367 "@typescript-eslint/utils": 5.36.2
2367 debug: ^4.3.4 2368 debug: ^4.3.4
2368 tsutils: ^3.21.0 2369 tsutils: ^3.21.0
2369 peerDependencies: 2370 peerDependencies:
@@ -2371,23 +2372,23 @@ __metadata:
2371 peerDependenciesMeta: 2372 peerDependenciesMeta:
2372 typescript: 2373 typescript:
2373 optional: true 2374 optional: true
2374 checksum: d2905289e253a83a9eacbad765cfba03440663086c8beb1b19345b46593c9053fb051ee13d3cc27ccd800fe95ffbf3be2b1273b0f0ac6a59452fc94e6460898b 2375 checksum: c202b7d2cd08ed7f7d1ad7e430e9e1596478e147f0d485d02babfda0211c55fa950de1dc4d1c950008a8a047a31c1e982e97fe5558f93d496830eb9d9532bc71
2375 languageName: node 2376 languageName: node
2376 linkType: hard 2377 linkType: hard
2377 2378
2378"@typescript-eslint/types@npm:5.36.1": 2379"@typescript-eslint/types@npm:5.36.2":
2379 version: 5.36.1 2380 version: 5.36.2
2380 resolution: "@typescript-eslint/types@npm:5.36.1" 2381 resolution: "@typescript-eslint/types@npm:5.36.2"
2381 checksum: 10c8965c64e16bc6920dc0c62aae2b139062aca945d03df2ad6fe7c299d2faa684621d571f8d9807a67643d4e9fa5217c69d5f538f9936fc757f9df5ded57623 2382 checksum: 736cb8a76b58f2f9a7d066933094c5510ffe31479ea8b804a829ec85942420f1b55e0eb2688fbdaaaa9c0e5b3b590fb8f14bbd745353696b4fd33fda620d417b
2382 languageName: node 2383 languageName: node
2383 linkType: hard 2384 linkType: hard
2384 2385
2385"@typescript-eslint/typescript-estree@npm:5.36.1": 2386"@typescript-eslint/typescript-estree@npm:5.36.2":
2386 version: 5.36.1 2387 version: 5.36.2
2387 resolution: "@typescript-eslint/typescript-estree@npm:5.36.1" 2388 resolution: "@typescript-eslint/typescript-estree@npm:5.36.2"
2388 dependencies: 2389 dependencies:
2389 "@typescript-eslint/types": 5.36.1 2390 "@typescript-eslint/types": 5.36.2
2390 "@typescript-eslint/visitor-keys": 5.36.1 2391 "@typescript-eslint/visitor-keys": 5.36.2
2391 debug: ^4.3.4 2392 debug: ^4.3.4
2392 globby: ^11.1.0 2393 globby: ^11.1.0
2393 is-glob: ^4.0.3 2394 is-glob: ^4.0.3
@@ -2396,33 +2397,33 @@ __metadata:
2396 peerDependenciesMeta: 2397 peerDependenciesMeta:
2397 typescript: 2398 typescript:
2398 optional: true 2399 optional: true
2399 checksum: acaf2938001673918dbbe690a353cf92e2cfabc79f74cd5946e303a8c24eb9c08ae2053dd261810ed0c9c471ebe879f386564c1b01dd2504dc84f4ce5f4dc696 2400 checksum: 2827ff57a114b6107ea6d555f3855007133b08a7c2bafba0cfa0c935d8b99fd7b49e982d48cccc1c5ba550d95748d0239f5e2109893f12a165d76ed64a0d261b
2400 languageName: node 2401 languageName: node
2401 linkType: hard 2402 linkType: hard
2402 2403
2403"@typescript-eslint/utils@npm:5.36.1": 2404"@typescript-eslint/utils@npm:5.36.2":
2404 version: 5.36.1 2405 version: 5.36.2
2405 resolution: "@typescript-eslint/utils@npm:5.36.1" 2406 resolution: "@typescript-eslint/utils@npm:5.36.2"
2406 dependencies: 2407 dependencies:
2407 "@types/json-schema": ^7.0.9 2408 "@types/json-schema": ^7.0.9
2408 "@typescript-eslint/scope-manager": 5.36.1 2409 "@typescript-eslint/scope-manager": 5.36.2
2409 "@typescript-eslint/types": 5.36.1 2410 "@typescript-eslint/types": 5.36.2
2410 "@typescript-eslint/typescript-estree": 5.36.1 2411 "@typescript-eslint/typescript-estree": 5.36.2
2411 eslint-scope: ^5.1.1 2412 eslint-scope: ^5.1.1
2412 eslint-utils: ^3.0.0 2413 eslint-utils: ^3.0.0
2413 peerDependencies: 2414 peerDependencies:
2414 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 2415 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
2415 checksum: 77853d526af86ac508d7938916046ed4ad6374c7414981064c5122a2baa96fa234751137f481ac264a07387fd4dcec1cd26b33e29732cc58e855aae77a001d7c 2416 checksum: 45356cf55a8733e3ab1f2c3c19cdaefdb79857e35eb1433c29b81f3df071e9cef8a286bc407abe243889a21d9e793e999f92f03b9c727a0fac1c17a48e64c42a
2416 languageName: node 2417 languageName: node
2417 linkType: hard 2418 linkType: hard
2418 2419
2419"@typescript-eslint/visitor-keys@npm:5.36.1": 2420"@typescript-eslint/visitor-keys@npm:5.36.2":
2420 version: 5.36.1 2421 version: 5.36.2
2421 resolution: "@typescript-eslint/visitor-keys@npm:5.36.1" 2422 resolution: "@typescript-eslint/visitor-keys@npm:5.36.2"
2422 dependencies: 2423 dependencies:
2423 "@typescript-eslint/types": 5.36.1 2424 "@typescript-eslint/types": 5.36.2
2424 eslint-visitor-keys: ^3.3.0 2425 eslint-visitor-keys: ^3.3.0
2425 checksum: 45ab7c2fd455a8e4beff418bed6c9e7e1f9f66bcaad3bfaed868f97a3f8cfec1fa4faa45948af1a1c32ce573a7b1c6d10427119c257622445b06b488fefd8b49 2426 checksum: 87ccdcfa5cdedaa3a1aac30d656969f4f5910b62bcaacdf80a514dbf0cbbd8e79b55f8e987eab34cc79ece8ce4b8c19d5caf8b0afb74e0b0d7ab39fb29aa8eba
2426 languageName: node 2427 languageName: node
2427 linkType: hard 2428 linkType: hard
2428 2429
@@ -2443,41 +2444,41 @@ __metadata:
2443 languageName: node 2444 languageName: node
2444 linkType: hard 2445 linkType: hard
2445 2446
2446"@xstate/cli@npm:^0.3.2": 2447"@xstate/cli@npm:^0.3.3":
2447 version: 0.3.2 2448 version: 0.3.3
2448 resolution: "@xstate/cli@npm:0.3.2" 2449 resolution: "@xstate/cli@npm:0.3.3"
2449 dependencies: 2450 dependencies:
2450 "@babel/core": ^7.12.10 2451 "@babel/core": ^7.12.10
2451 "@xstate/machine-extractor": 0.7.0 2452 "@xstate/machine-extractor": 0.7.1
2452 "@xstate/tools-shared": 1.2.2 2453 "@xstate/tools-shared": 1.2.3
2453 chokidar: ^3.5.3 2454 chokidar: ^3.5.3
2454 commander: ^8.0.0 2455 commander: ^8.0.0
2455 xstate: ^4.29.0 2456 xstate: ^4.29.0
2456 bin: 2457 bin:
2457 xstate: bin/bin.js 2458 xstate: bin/bin.js
2458 checksum: 78136a88a9357d676db09a18d726e1521b5284326a58cc75d4d6c0c86bf7e438c8b2f2d72562ef5133877fe9cbad1a91255600dca73aec7fab36980a2b8b9b7c 2459 checksum: 0522d91c4bd8251402f2f715d60aa4dc3156e0e895827c3c873d9904a987dad32dfcd133697823881b65d8fb8d29e1d5c9aa57356a3f32f7b507290f2dfd1898
2459 languageName: node 2460 languageName: node
2460 linkType: hard 2461 linkType: hard
2461 2462
2462"@xstate/machine-extractor@npm:0.7.0": 2463"@xstate/machine-extractor@npm:0.7.1":
2463 version: 0.7.0 2464 version: 0.7.1
2464 resolution: "@xstate/machine-extractor@npm:0.7.0" 2465 resolution: "@xstate/machine-extractor@npm:0.7.1"
2465 peerDependencies: 2466 peerDependencies:
2466 "@babel/core": ^7 2467 "@babel/core": ^7
2467 xstate: ^4 2468 xstate: ^4
2468 checksum: 139cba4217523034161d373d24e138c0d27e5527ca119f292dfc2c45a13d56cf72cb6ad4c94845b28192d9a908be3edf2d907c58cd35bac51cbacdefc9faefae 2469 checksum: f22218f306dc16e8b58bb522ac67c987794dda36529330daea206fb716bb89440541c780f601b8a4b403f5994b1e84b8328c130a2f53d5c84cf84ad82aa61c49
2469 languageName: node 2470 languageName: node
2470 linkType: hard 2471 linkType: hard
2471 2472
2472"@xstate/tools-shared@npm:1.2.2": 2473"@xstate/tools-shared@npm:1.2.3":
2473 version: 1.2.2 2474 version: 1.2.3
2474 resolution: "@xstate/tools-shared@npm:1.2.2" 2475 resolution: "@xstate/tools-shared@npm:1.2.3"
2475 dependencies: 2476 dependencies:
2476 "@xstate/machine-extractor": 0.7.0 2477 "@xstate/machine-extractor": 0.7.1
2477 peerDependencies: 2478 peerDependencies:
2478 prettier: ^2.3.1 2479 prettier: ^2.3.1
2479 xstate: ^4 2480 xstate: ^4
2480 checksum: a4e0dfb3bb8db9959cda415d0ee5fd4246fd6d803d602710737d947a382d68ea45887dac9ff51d8d1d8cff9acb1568e360babda4b02075b3d7a65ea241c58499 2481 checksum: 191205b0f86912995242c9e7017737c1496744dd799aa42bf0c3082700eaddcea14d00b0014ffcfad7d989ec5ca8e2789e9f5eab8c90d06bdf1430d4a98e91a9
2481 languageName: node 2482 languageName: node
2482 linkType: hard 2483 linkType: hard
2483 2484
@@ -3713,9 +3714,9 @@ __metadata:
3713 languageName: node 3714 languageName: node
3714 linkType: hard 3715 linkType: hard
3715 3716
3716"eslint-import-resolver-typescript@npm:^3.5.0": 3717"eslint-import-resolver-typescript@npm:^3.5.1":
3717 version: 3.5.0 3718 version: 3.5.1
3718 resolution: "eslint-import-resolver-typescript@npm:3.5.0" 3719 resolution: "eslint-import-resolver-typescript@npm:3.5.1"
3719 dependencies: 3720 dependencies:
3720 debug: ^4.3.4 3721 debug: ^4.3.4
3721 enhanced-resolve: ^5.10.0 3722 enhanced-resolve: ^5.10.0
@@ -3727,7 +3728,7 @@ __metadata:
3727 peerDependencies: 3728 peerDependencies:
3728 eslint: "*" 3729 eslint: "*"
3729 eslint-plugin-import: "*" 3730 eslint-plugin-import: "*"
3730 checksum: 9719d1f68b7bb0eaf8939cff2d3b02b526949f73db744877de781640650dd4d0a17d934222b9ac69e27d9f363ee4569c1aa1a2a2aab6500257517f9bf7d25976 3731 checksum: d7a7a33c9fd8b52ae25bd432c3e0b157a9c38d1254325311e04f450083197d47d823ebca447e7b5bfe9b9a959de8d2edbafb028406c2532fd7aa95fe31812a48
3731 languageName: node 3732 languageName: node
3732 linkType: hard 3733 linkType: hard
3733 3734
@@ -3820,9 +3821,9 @@ __metadata:
3820 languageName: node 3821 languageName: node
3821 linkType: hard 3822 linkType: hard
3822 3823
3823"eslint-plugin-react@npm:^7.31.6": 3824"eslint-plugin-react@npm:^7.31.7":
3824 version: 7.31.6 3825 version: 7.31.7
3825 resolution: "eslint-plugin-react@npm:7.31.6" 3826 resolution: "eslint-plugin-react@npm:7.31.7"
3826 dependencies: 3827 dependencies:
3827 array-includes: ^3.1.5 3828 array-includes: ^3.1.5
3828 array.prototype.flatmap: ^1.3.0 3829 array.prototype.flatmap: ^1.3.0
@@ -3840,7 +3841,7 @@ __metadata:
3840 string.prototype.matchall: ^4.0.7 3841 string.prototype.matchall: ^4.0.7
3841 peerDependencies: 3842 peerDependencies:
3842 eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 3843 eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
3843 checksum: fdcee460db2e01398b31dd75bb0597d6dfa2f29beda9c5ef641e6e3c72c8c2191cbaa952f7469a5e091bc2be959a1abc09761bdca70f12aa4a73090f48a98f6b 3844 checksum: 582d422f531d7d3894fc09ac941ef8b6ad595782cfca5e1d52af5895ce117def7a0ff8afeea0166bff7b6ceae8baec2313614b1571754f539575cfa9351cd2da
3844 languageName: node 3845 languageName: node
3845 linkType: hard 3846 linkType: hard
3846 3847
@@ -5267,10 +5268,10 @@ __metadata:
5267 languageName: node 5268 languageName: node
5268 linkType: hard 5269 linkType: hard
5269 5270
5270"mobx@npm:^6.6.1": 5271"mobx@npm:^6.6.2":
5271 version: 6.6.1 5272 version: 6.6.2
5272 resolution: "mobx@npm:6.6.1" 5273 resolution: "mobx@npm:6.6.2"
5273 checksum: 20e876693d1a99916d347ba666f47e7931d5aa5a1172eae48d172477d537577d604207a8f8918041f9b6c74280b6533a516cf1639929735c6bb514f34776225a 5274 checksum: b913abea51ed023d60c71eec0198e953c0b7faee93820be2e8f07037a789b3acf6fd89c340cb9d34e888532ebd34d34b10add7d129277a96026537e5ca48bda6
5274 languageName: node 5275 languageName: node
5275 linkType: hard 5276 linkType: hard
5276 5277
@@ -5288,7 +5289,7 @@ __metadata:
5288 languageName: node 5289 languageName: node
5289 linkType: hard 5290 linkType: hard
5290 5291
5291"ms@npm:^2.0.0, ms@npm:^2.1.1": 5292"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3":
5292 version: 2.1.3 5293 version: 2.1.3
5293 resolution: "ms@npm:2.1.3" 5294 resolution: "ms@npm:2.1.3"
5294 checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d 5295 checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
@@ -6719,9 +6720,9 @@ __metadata:
6719 languageName: node 6720 languageName: node
6720 linkType: hard 6721 linkType: hard
6721 6722
6722"vite-plugin-pwa@npm:^0.12.6": 6723"vite-plugin-pwa@npm:^0.12.7":
6723 version: 0.12.6 6724 version: 0.12.7
6724 resolution: "vite-plugin-pwa@npm:0.12.6" 6725 resolution: "vite-plugin-pwa@npm:0.12.7"
6725 dependencies: 6726 dependencies:
6726 debug: ^4.3.4 6727 debug: ^4.3.4
6727 fast-glob: ^3.2.11 6728 fast-glob: ^3.2.11
@@ -6733,7 +6734,7 @@ __metadata:
6733 vite: ^2.0.0 || ^3.0.0-0 6734 vite: ^2.0.0 || ^3.0.0-0
6734 workbox-build: ^6.4.0 6735 workbox-build: ^6.4.0
6735 workbox-window: ^6.4.0 6736 workbox-window: ^6.4.0
6736 checksum: b3577d401ede64c6937a940eca447e64f6f19f74f4588cdd8f201081ed73c1a87e1cb2defa47f7d6f33adb618f5dd7623e7b99d27fc5605ed175d678e64f8e71 6737 checksum: 0533bde5e64a7eb5bfe5a737466ce2723a0719ffa9d50e59bab65d2f518ae0f75c4145e90e4c7dbd2704c0e9397d61c0b4d6f56bac91e373929b37de8ec910ed
6737 languageName: node 6738 languageName: node
6738 linkType: hard 6739 linkType: hard
6739 6740
@@ -7059,9 +7060,9 @@ __metadata:
7059 languageName: node 7060 languageName: node
7060 linkType: hard 7061 linkType: hard
7061 7062
7062"zod@npm:^3.18.0": 7063"zod@npm:^3.19.0":
7063 version: 3.18.0 7064 version: 3.19.0
7064 resolution: "zod@npm:3.18.0" 7065 resolution: "zod@npm:3.19.0"
7065 checksum: 86a9a9928f4b40a07020d4b9832842fa4a70050c2d7bd1b2866bc1acfd734aa53a40f87a99a4312a637341a608b0105770c7a1f83b56df78e97691b4f5badcd8 7066 checksum: 35b9270ba73ff15e1cc3e529cdd49b78ceb5f9204770b974ffa5d1728ad1be8429315aa25263d088edecaa99ecc3631339fe79313435154060951706294f47e5
7066 languageName: node 7067 languageName: node
7067 linkType: hard 7068 linkType: hard