diff options
author | 2022-08-16 21:14:50 +0200 | |
---|---|---|
committer | 2022-08-16 21:14:50 +0200 | |
commit | 19cd11118cde7160cd447c81bc965007c0437479 (patch) | |
tree | 5fea613e7a46d69380995368a68cc72f186078a4 | |
parent | chore(deps): bump frontend dependencies (diff) | |
download | refinery-19cd11118cde7160cd447c81bc965007c0437479.tar.gz refinery-19cd11118cde7160cd447c81bc965007c0437479.tar.zst refinery-19cd11118cde7160cd447c81bc965007c0437479.zip |
refactor(frondend): improve editor store and theme
Also bumps frontend dependencies.
18 files changed, 894 insertions, 723 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index d438c104..69ff74c6 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -26,7 +26,7 @@ | |||
26 | "@codemirror/commands": "^6.0.1", | 26 | "@codemirror/commands": "^6.0.1", |
27 | "@codemirror/language": "^6.2.1", | 27 | "@codemirror/language": "^6.2.1", |
28 | "@codemirror/lint": "^6.0.0", | 28 | "@codemirror/lint": "^6.0.0", |
29 | "@codemirror/search": "^6.0.1", | 29 | "@codemirror/search": "^6.1.0", |
30 | "@codemirror/state": "^6.1.1", | 30 | "@codemirror/state": "^6.1.1", |
31 | "@codemirror/view": "^6.2.0", | 31 | "@codemirror/view": "^6.2.0", |
32 | "@emotion/react": "^11.10.0", | 32 | "@emotion/react": "^11.10.0", |
@@ -36,9 +36,10 @@ | |||
36 | "@fontsource/roboto": "^4.5.8", | 36 | "@fontsource/roboto": "^4.5.8", |
37 | "@lezer/common": "^1.0.0", | 37 | "@lezer/common": "^1.0.0", |
38 | "@lezer/highlight": "^1.0.0", | 38 | "@lezer/highlight": "^1.0.0", |
39 | "@lezer/lr": "^1.2.2", | 39 | "@lezer/lr": "^1.2.3", |
40 | "@material-icons/svg": "^1.0.32", | ||
40 | "@mui/icons-material": "5.8.4", | 41 | "@mui/icons-material": "5.8.4", |
41 | "@mui/material": "5.10.0", | 42 | "@mui/material": "5.10.1", |
42 | "ansi-styles": "^6.1.0", | 43 | "ansi-styles": "^6.1.0", |
43 | "escape-string-regexp": "^5.0.0", | 44 | "escape-string-regexp": "^5.0.0", |
44 | "loglevel": "^1.8.0", | 45 | "loglevel": "^1.8.0", |
@@ -53,12 +54,12 @@ | |||
53 | "devDependencies": { | 54 | "devDependencies": { |
54 | "@lezer/generator": "^1.1.1", | 55 | "@lezer/generator": "^1.1.1", |
55 | "@types/eslint": "^8.4.5", | 56 | "@types/eslint": "^8.4.5", |
56 | "@types/node": "^18.7.4", | 57 | "@types/node": "^18.7.6", |
57 | "@types/prettier": "^2.7.0", | 58 | "@types/prettier": "^2.7.0", |
58 | "@types/react": "^18.0.17", | 59 | "@types/react": "^18.0.17", |
59 | "@types/react-dom": "^18.0.6", | 60 | "@types/react-dom": "^18.0.6", |
60 | "@typescript-eslint/eslint-plugin": "^5.33.0", | 61 | "@typescript-eslint/eslint-plugin": "^5.33.1", |
61 | "@typescript-eslint/parser": "^5.33.0", | 62 | "@typescript-eslint/parser": "^5.33.1", |
62 | "@vitejs/plugin-react": "^2.0.1", | 63 | "@vitejs/plugin-react": "^2.0.1", |
63 | "cross-env": "^7.0.3", | 64 | "cross-env": "^7.0.3", |
64 | "eslint": "^8.22.0", | 65 | "eslint": "^8.22.0", |
@@ -73,7 +74,7 @@ | |||
73 | "eslint-plugin-react-hooks": "^4.6.0", | 74 | "eslint-plugin-react-hooks": "^4.6.0", |
74 | "prettier": "^2.7.1", | 75 | "prettier": "^2.7.1", |
75 | "typescript": "~4.7.4", | 76 | "typescript": "~4.7.4", |
76 | "vite": "^3.0.7", | 77 | "vite": "^3.0.8", |
77 | "vite-plugin-inject-preload": "^1.0.1" | 78 | "vite-plugin-inject-preload": "^1.0.1" |
78 | } | 79 | } |
79 | } | 80 | } |
diff --git a/subprojects/frontend/src/RootStore.tsx b/subprojects/frontend/src/RootStore.tsx index a7406d7b..4a267b0e 100644 --- a/subprojects/frontend/src/RootStore.tsx +++ b/subprojects/frontend/src/RootStore.tsx | |||
@@ -4,13 +4,13 @@ import EditorStore from './editor/EditorStore'; | |||
4 | import ThemeStore from './theme/ThemeStore'; | 4 | import ThemeStore from './theme/ThemeStore'; |
5 | 5 | ||
6 | export default class RootStore { | 6 | export default class RootStore { |
7 | editorStore; | 7 | readonly editorStore: EditorStore; |
8 | 8 | ||
9 | themeStore; | 9 | readonly themeStore: ThemeStore; |
10 | 10 | ||
11 | constructor(initialValue: string) { | 11 | constructor(initialValue: string) { |
12 | this.editorStore = new EditorStore(initialValue); | ||
12 | this.themeStore = new ThemeStore(); | 13 | this.themeStore = new ThemeStore(); |
13 | this.editorStore = new EditorStore(initialValue, this.themeStore); | ||
14 | } | 14 | } |
15 | } | 15 | } |
16 | 16 | ||
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index af571a1e..5ad80d40 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx | |||
@@ -12,8 +12,8 @@ export default function TopBar(): JSX.Element { | |||
12 | elevation={0} | 12 | elevation={0} |
13 | color="transparent" | 13 | color="transparent" |
14 | sx={(theme) => ({ | 14 | sx={(theme) => ({ |
15 | background: theme.palette.highlight.activeLine, | 15 | background: theme.palette.outer.background, |
16 | borderBottom: `1px solid ${theme.palette.divider2}`, | 16 | borderBottom: `1px solid ${theme.palette.outer.border}`, |
17 | })} | 17 | })} |
18 | > | 18 | > |
19 | <Toolbar> | 19 | <Toolbar> |
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx index d4305610..e5712461 100644 --- a/subprojects/frontend/src/editor/EditorArea.tsx +++ b/subprojects/frontend/src/editor/EditorArea.tsx | |||
@@ -1,139 +1,31 @@ | |||
1 | import { closeLintPanel, openLintPanel } from '@codemirror/lint'; | 1 | import { useTheme } from '@mui/material/styles'; |
2 | import { closeSearchPanel, openSearchPanel } from '@codemirror/search'; | ||
3 | import { type Command, EditorView } from '@codemirror/view'; | ||
4 | import { observer } from 'mobx-react-lite'; | 2 | import { observer } from 'mobx-react-lite'; |
5 | import React, { useCallback, useEffect, useRef, useState } from 'react'; | 3 | import React, { useCallback, useEffect } from 'react'; |
6 | 4 | ||
7 | import { useRootStore } from '../RootStore'; | 5 | import { useRootStore } from '../RootStore'; |
8 | import getLogger from '../utils/getLogger'; | ||
9 | 6 | ||
10 | import EditorParent from './EditorParent'; | 7 | import EditorTheme from './EditorTheme'; |
11 | |||
12 | const log = getLogger('editor.EditorArea'); | ||
13 | |||
14 | function usePanel( | ||
15 | panelId: string, | ||
16 | stateToSet: boolean, | ||
17 | editorView: EditorView | null, | ||
18 | openCommand: Command, | ||
19 | closeCommand: Command, | ||
20 | closeCallback: () => void, | ||
21 | ) { | ||
22 | const [cachedViewState, setCachedViewState] = useState<boolean>(false); | ||
23 | useEffect(() => { | ||
24 | if (editorView === null || cachedViewState === stateToSet) { | ||
25 | return; | ||
26 | } | ||
27 | if (stateToSet) { | ||
28 | openCommand(editorView); | ||
29 | const buttonQuery = `.cm-${panelId}.cm-panel button[name="close"]`; | ||
30 | const closeButton = editorView.dom.querySelector(buttonQuery); | ||
31 | if (closeButton) { | ||
32 | log.debug('Addig close button callback to', panelId, 'panel'); | ||
33 | // We must remove the event listener added by CodeMirror from the button | ||
34 | // that dispatches a transaction without going through `EditorStorre`. | ||
35 | // Cloning a DOM node removes event listeners, | ||
36 | // see https://stackoverflow.com/a/9251864 | ||
37 | const closeButtonWithoutListeners = closeButton.cloneNode(true); | ||
38 | closeButtonWithoutListeners.addEventListener('click', (event) => { | ||
39 | closeCallback(); | ||
40 | event.preventDefault(); | ||
41 | }); | ||
42 | closeButton.replaceWith(closeButtonWithoutListeners); | ||
43 | } else { | ||
44 | log.error('Opened', panelId, 'panel has no close button'); | ||
45 | } | ||
46 | } else { | ||
47 | closeCommand(editorView); | ||
48 | } | ||
49 | setCachedViewState(stateToSet); | ||
50 | }, [ | ||
51 | stateToSet, | ||
52 | editorView, | ||
53 | cachedViewState, | ||
54 | panelId, | ||
55 | openCommand, | ||
56 | closeCommand, | ||
57 | closeCallback, | ||
58 | ]); | ||
59 | return setCachedViewState; | ||
60 | } | ||
61 | |||
62 | function fixCodeMirrorAccessibility(editorView: EditorView) { | ||
63 | // Reported by Lighthouse 8.3.0. | ||
64 | const { contentDOM } = editorView; | ||
65 | contentDOM.removeAttribute('aria-expanded'); | ||
66 | contentDOM.setAttribute('aria-label', 'Code editor'); | ||
67 | } | ||
68 | 8 | ||
69 | function EditorArea(): JSX.Element { | 9 | function EditorArea(): JSX.Element { |
70 | const { editorStore } = useRootStore(); | 10 | const { editorStore } = useRootStore(); |
71 | const editorParentRef = useRef<HTMLDivElement | null>(null); | 11 | const { |
72 | const [editorViewState, setEditorViewState] = useState<EditorView | null>( | 12 | palette: { mode: paletteMode }, |
73 | null, | 13 | } = useTheme(); |
74 | ); | ||
75 | 14 | ||
76 | const setSearchPanelOpen = usePanel( | 15 | useEffect( |
77 | 'search', | 16 | () => editorStore.setDarkMode(paletteMode === 'dark'), |
78 | editorStore.showSearchPanel, | 17 | [editorStore, paletteMode], |
79 | editorViewState, | ||
80 | openSearchPanel, | ||
81 | closeSearchPanel, | ||
82 | useCallback(() => editorStore.setSearchPanelOpen(false), [editorStore]), | ||
83 | ); | 18 | ); |
84 | 19 | ||
85 | const setLintPanelOpen = usePanel( | 20 | const editorParentRef = useCallback( |
86 | 'panel-lint', | 21 | (editorParent: HTMLDivElement | null) => { |
87 | editorStore.showLintPanel, | 22 | editorStore.setEditorParent(editorParent); |
88 | editorViewState, | 23 | }, |
89 | openLintPanel, | 24 | [editorStore], |
90 | closeLintPanel, | ||
91 | useCallback(() => editorStore.setLintPanelOpen(false), [editorStore]), | ||
92 | ); | 25 | ); |
93 | 26 | ||
94 | useEffect(() => { | ||
95 | if (editorParentRef.current === null) { | ||
96 | return () => { | ||
97 | // Nothing to clean up. | ||
98 | }; | ||
99 | } | ||
100 | |||
101 | const editorView = new EditorView({ | ||
102 | state: editorStore.state, | ||
103 | parent: editorParentRef.current, | ||
104 | dispatch: (transaction) => { | ||
105 | editorStore.onTransaction(transaction); | ||
106 | editorView.update([transaction]); | ||
107 | if (editorView.state !== editorStore.state) { | ||
108 | log.error( | ||
109 | 'Failed to synchronize editor state - store state:', | ||
110 | editorStore.state, | ||
111 | 'view state:', | ||
112 | editorView.state, | ||
113 | ); | ||
114 | } | ||
115 | }, | ||
116 | }); | ||
117 | fixCodeMirrorAccessibility(editorView); | ||
118 | setEditorViewState(editorView); | ||
119 | setSearchPanelOpen(false); | ||
120 | setLintPanelOpen(false); | ||
121 | // `dispatch` is bound to the view instance, | ||
122 | // so it does not have to be called as a method. | ||
123 | // eslint-disable-next-line @typescript-eslint/unbound-method | ||
124 | editorStore.updateDispatcher(editorView.dispatch); | ||
125 | log.info('Editor created'); | ||
126 | |||
127 | return () => { | ||
128 | editorStore.updateDispatcher(null); | ||
129 | editorView.destroy(); | ||
130 | log.info('Editor destroyed'); | ||
131 | }; | ||
132 | }, [editorStore, setSearchPanelOpen, setLintPanelOpen]); | ||
133 | |||
134 | return ( | 27 | return ( |
135 | <EditorParent | 28 | <EditorTheme |
136 | className="dark" | ||
137 | showLineNumbers={editorStore.showLineNumbers} | 29 | showLineNumbers={editorStore.showLineNumbers} |
138 | ref={editorParentRef} | 30 | ref={editorParentRef} |
139 | /> | 31 | /> |
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx index 1412a314..34b64751 100644 --- a/subprojects/frontend/src/editor/EditorButtons.tsx +++ b/subprojects/frontend/src/editor/EditorButtons.tsx | |||
@@ -19,7 +19,7 @@ import { useRootStore } from '../RootStore'; | |||
19 | 19 | ||
20 | // Exhastive switch as proven by TypeScript. | 20 | // Exhastive switch as proven by TypeScript. |
21 | // eslint-disable-next-line consistent-return | 21 | // eslint-disable-next-line consistent-return |
22 | function getLintIcon(severity: Diagnostic['severity'] | null) { | 22 | function getLintIcon(severity: Diagnostic['severity'] | undefined) { |
23 | switch (severity) { | 23 | switch (severity) { |
24 | case 'error': | 24 | case 'error': |
25 | return <ErrorIcon fontSize="small" />; | 25 | return <ErrorIcon fontSize="small" />; |
@@ -61,16 +61,16 @@ function EditorButtons(): JSX.Element { | |||
61 | <FormatListNumberedIcon fontSize="small" /> | 61 | <FormatListNumberedIcon fontSize="small" /> |
62 | </ToggleButton> | 62 | </ToggleButton> |
63 | <ToggleButton | 63 | <ToggleButton |
64 | selected={editorStore.showSearchPanel} | 64 | selected={editorStore.searchPanel.state} |
65 | onClick={() => editorStore.toggleSearchPanel()} | 65 | onClick={() => editorStore.searchPanel.toggle()} |
66 | aria-label="Show find/replace" | 66 | aria-label="Show find/replace" |
67 | value="show-search-panel" | 67 | value="show-search-panel" |
68 | > | 68 | > |
69 | <SearchIcon fontSize="small" /> | 69 | <SearchIcon fontSize="small" /> |
70 | </ToggleButton> | 70 | </ToggleButton> |
71 | <ToggleButton | 71 | <ToggleButton |
72 | selected={editorStore.showLintPanel} | 72 | selected={editorStore.lintPanel.state} |
73 | onClick={() => editorStore.toggleLintPanel()} | 73 | onClick={() => editorStore.lintPanel.toggle()} |
74 | aria-label="Show diagnostics panel" | 74 | aria-label="Show diagnostics panel" |
75 | value="show-lint-panel" | 75 | value="show-lint-panel" |
76 | > | 76 | > |
diff --git a/subprojects/frontend/src/editor/EditorParent.ts b/subprojects/frontend/src/editor/EditorParent.ts deleted file mode 100644 index 3742b89c..00000000 --- a/subprojects/frontend/src/editor/EditorParent.ts +++ /dev/null | |||
@@ -1,228 +0,0 @@ | |||
1 | import { alpha, styled } from '@mui/material/styles'; | ||
2 | |||
3 | export default styled('div', { | ||
4 | name: 'EditorParent', | ||
5 | shouldForwardProp: (propName) => propName !== 'showLineNumbers', | ||
6 | })<{ showLineNumbers: boolean }>(({ theme, showLineNumbers }) => { | ||
7 | const codeMirrorLintStyle: Record<string, unknown> = {}; | ||
8 | (['error', 'warning', 'info'] as const).forEach((severity) => { | ||
9 | const color = theme.palette[severity].main; | ||
10 | codeMirrorLintStyle[`.cm-diagnostic-${severity}`] = { | ||
11 | borderLeftColor: color, | ||
12 | }; | ||
13 | codeMirrorLintStyle[`.cm-lintRange-${severity}`] = { | ||
14 | backgroundImage: 'none', | ||
15 | textDecoration: `underline wavy ${color}`, | ||
16 | textDecorationSkipInk: 'none', | ||
17 | }; | ||
18 | }); | ||
19 | |||
20 | return { | ||
21 | background: theme.palette.background.default, | ||
22 | '&, .cm-editor': { | ||
23 | height: '100%', | ||
24 | }, | ||
25 | '.cm-content': { | ||
26 | padding: 0, | ||
27 | }, | ||
28 | '.cm-scroller, .cm-tooltip-autocomplete, .cm-completionLabel, .cm-completionDetail': | ||
29 | { | ||
30 | ...theme.typography.body1, | ||
31 | fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", monospace', | ||
32 | fontFeatureSettings: '"liga", "calt"', | ||
33 | letterSpacing: 0, | ||
34 | textRendering: 'optimizeLegibility', | ||
35 | }, | ||
36 | '.cm-scroller': { | ||
37 | color: theme.palette.text.secondary, | ||
38 | }, | ||
39 | '.cm-gutters': { | ||
40 | background: 'transparent', | ||
41 | color: theme.palette.text.disabled, | ||
42 | border: 'none', | ||
43 | }, | ||
44 | '.cm-specialChar': { | ||
45 | color: theme.palette.secondary.main, | ||
46 | }, | ||
47 | '.cm-activeLine': { | ||
48 | background: theme.palette.highlight.activeLine, | ||
49 | }, | ||
50 | '.cm-foldGutter': { | ||
51 | color: alpha(theme.palette.text.primary, 0), | ||
52 | transition: theme.transitions.create('color', { | ||
53 | duration: theme.transitions.duration.short, | ||
54 | }), | ||
55 | '@media (hover: none)': { | ||
56 | color: theme.palette.text.primary, | ||
57 | }, | ||
58 | }, | ||
59 | '.cm-gutters:hover .cm-foldGutter': { | ||
60 | color: theme.palette.text.primary, | ||
61 | }, | ||
62 | '.cm-activeLineGutter': { | ||
63 | background: 'transparent', | ||
64 | }, | ||
65 | '.cm-lineNumbers': { | ||
66 | ...(!showLineNumbers && { | ||
67 | display: 'none !important', | ||
68 | }), | ||
69 | '.cm-activeLineGutter': { | ||
70 | color: theme.palette.text.primary, | ||
71 | }, | ||
72 | }, | ||
73 | '.cm-cursor, .cm-cursor-primary': { | ||
74 | borderLeft: `2px solid ${theme.palette.primary.main}`, | ||
75 | }, | ||
76 | '.cm-selectionBackground': { | ||
77 | background: theme.palette.selection.main, | ||
78 | }, | ||
79 | '.cm-focused': { | ||
80 | outline: 'none', | ||
81 | '.cm-selectionBackground': { | ||
82 | background: theme.palette.selection.main, | ||
83 | }, | ||
84 | }, | ||
85 | '.cm-panels-top': { | ||
86 | color: theme.palette.text.secondary, | ||
87 | }, | ||
88 | '.cm-panel': { | ||
89 | '&, & button, & input': { | ||
90 | fontFamily: theme.typography.fontFamily, | ||
91 | }, | ||
92 | background: theme.palette.background.default, | ||
93 | borderTop: `1px solid ${theme.palette.divider2}`, | ||
94 | 'button[name="close"]': { | ||
95 | background: 'transparent', | ||
96 | color: theme.palette.text.secondary, | ||
97 | cursor: 'pointer', | ||
98 | }, | ||
99 | }, | ||
100 | '.cm-panel.cm-panel-lint': { | ||
101 | boderBottom: 'none', | ||
102 | 'button[name="close"]': { | ||
103 | // Close button interferes with scrollbar, so we better hide it. | ||
104 | // The panel can still be closed from the toolbar. | ||
105 | display: 'none', | ||
106 | }, | ||
107 | ul: { | ||
108 | li: { | ||
109 | cursor: 'pointer', | ||
110 | color: theme.palette.text.primary, | ||
111 | }, | ||
112 | '[aria-selected], &:focus [aria-selected]': { | ||
113 | background: theme.palette.selection.main, | ||
114 | color: theme.palette.selection.contrastText, | ||
115 | }, | ||
116 | }, | ||
117 | }, | ||
118 | '.cm-foldPlaceholder': { | ||
119 | color: theme.palette.text.secondary, | ||
120 | backgroundColor: alpha(theme.palette.text.secondary, 0), | ||
121 | border: `1px solid ${alpha(theme.palette.text.secondary, 0.5)}`, | ||
122 | borderRadius: theme.shape.borderRadius, | ||
123 | transition: theme.transitions.create( | ||
124 | ['background-color', 'border-color', 'color'], | ||
125 | { | ||
126 | duration: theme.transitions.duration.short, | ||
127 | }, | ||
128 | ), | ||
129 | '&:hover': { | ||
130 | backgroundColor: alpha( | ||
131 | theme.palette.text.secondary, | ||
132 | theme.palette.action.hoverOpacity, | ||
133 | ), | ||
134 | borderColor: theme.palette.text.secondary, | ||
135 | '@media (hover: none)': { | ||
136 | backgroundColor: 'transparent', | ||
137 | }, | ||
138 | }, | ||
139 | }, | ||
140 | '.tok-comment': { | ||
141 | fontStyle: 'italic', | ||
142 | color: theme.palette.highlight.comment, | ||
143 | }, | ||
144 | '.tok-number': { | ||
145 | color: theme.palette.highlight.number, | ||
146 | }, | ||
147 | '.tok-string': { | ||
148 | color: theme.palette.secondary.dark, | ||
149 | }, | ||
150 | '.tok-keyword': { | ||
151 | color: theme.palette.primary.main, | ||
152 | }, | ||
153 | '.tok-typeName, .tok-macroName, .tok-atom': { | ||
154 | color: theme.palette.text.primary, | ||
155 | }, | ||
156 | '.tok-variableName': { | ||
157 | color: theme.palette.highlight.parameter, | ||
158 | }, | ||
159 | '.tok-problem-node': { | ||
160 | '&, & .tok-variableName': { | ||
161 | color: theme.palette.text.secondary, | ||
162 | }, | ||
163 | }, | ||
164 | '.tok-problem-individual': { | ||
165 | '&, & .tok-variableName': { | ||
166 | color: theme.palette.text.primary, | ||
167 | }, | ||
168 | }, | ||
169 | '.tok-problem-abstract, .tok-problem-new': { | ||
170 | fontStyle: 'italic', | ||
171 | }, | ||
172 | '.tok-problem-containment': { | ||
173 | fontWeight: 700, | ||
174 | }, | ||
175 | '.tok-problem-error': { | ||
176 | '&, & .tok-typeName': { | ||
177 | color: theme.palette.error.main, | ||
178 | }, | ||
179 | }, | ||
180 | '.tok-problem-builtin': { | ||
181 | '&, & .tok-typeName, & .tok-atom, & .tok-variableName': { | ||
182 | color: theme.palette.primary.main, | ||
183 | fontWeight: 400, | ||
184 | fontStyle: 'normal', | ||
185 | }, | ||
186 | }, | ||
187 | '.cm-tooltip-autocomplete': { | ||
188 | background: theme.palette.background.paper, | ||
189 | ...(theme.palette.mode === 'dark' && { | ||
190 | overflow: 'hidden', | ||
191 | borderRadius: theme.shape.borderRadius, | ||
192 | // https://github.com/mui/material-ui/blob/10c72729c7d03bab8cdce6eb422642684c56dca2/packages/mui-material/src/Paper/Paper.js#L18 | ||
193 | backgroundImage: | ||
194 | 'linear-gradient(rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09))', | ||
195 | }), | ||
196 | boxShadow: theme.shadows[4], | ||
197 | '.cm-completionIcon': { | ||
198 | color: theme.palette.text.secondary, | ||
199 | }, | ||
200 | '.cm-completionLabel': { | ||
201 | color: theme.palette.text.primary, | ||
202 | }, | ||
203 | '.cm-completionDetail': { | ||
204 | color: theme.palette.text.secondary, | ||
205 | fontStyle: 'normal', | ||
206 | }, | ||
207 | '[aria-selected]': { | ||
208 | background: `${theme.palette.primary.main} !important`, | ||
209 | '.cm-completionIcon, .cm-completionLabel, .cm-completionDetail': { | ||
210 | color: theme.palette.primary.contrastText, | ||
211 | }, | ||
212 | }, | ||
213 | }, | ||
214 | '.cm-completionIcon': { | ||
215 | width: 16, | ||
216 | padding: 0, | ||
217 | marginRight: '0.5em', | ||
218 | textAlign: 'center', | ||
219 | }, | ||
220 | ...codeMirrorLintStyle, | ||
221 | '.cm-problem-read': { | ||
222 | background: theme.palette.highlight.occurences.read, | ||
223 | }, | ||
224 | '.cm-problem-write': { | ||
225 | background: theme.palette.highlight.occurences.write, | ||
226 | }, | ||
227 | }; | ||
228 | }); | ||
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts index f75147a4..4bad68b3 100644 --- a/subprojects/frontend/src/editor/EditorStore.ts +++ b/subprojects/frontend/src/editor/EditorStore.ts | |||
@@ -1,58 +1,30 @@ | |||
1 | import { CompletionContext, CompletionResult } from '@codemirror/autocomplete'; | ||
2 | import { redo, redoDepth, undo, undoDepth } from '@codemirror/commands'; | ||
1 | import { | 3 | import { |
2 | closeBrackets, | 4 | type Diagnostic, |
3 | closeBracketsKeymap, | 5 | setDiagnostics, |
4 | autocompletion, | 6 | closeLintPanel, |
5 | completionKeymap, | 7 | openLintPanel, |
6 | } from '@codemirror/autocomplete'; | 8 | nextDiagnostic, |
9 | } from '@codemirror/lint'; | ||
10 | import { closeSearchPanel, openSearchPanel } from '@codemirror/search'; | ||
7 | import { | 11 | import { |
8 | defaultKeymap, | ||
9 | history, | ||
10 | historyKeymap, | ||
11 | indentWithTab, | ||
12 | redo, | ||
13 | redoDepth, | ||
14 | undo, | ||
15 | undoDepth, | ||
16 | } from '@codemirror/commands'; | ||
17 | import { | ||
18 | bracketMatching, | ||
19 | foldGutter, | ||
20 | foldKeymap, | ||
21 | indentOnInput, | ||
22 | syntaxHighlighting, | ||
23 | } from '@codemirror/language'; | ||
24 | import { type Diagnostic, lintKeymap, setDiagnostics } from '@codemirror/lint'; | ||
25 | import { search, searchKeymap } from '@codemirror/search'; | ||
26 | import { | ||
27 | EditorState, | ||
28 | type StateCommand, | 12 | type StateCommand, |
29 | StateEffect, | 13 | StateEffect, |
30 | type Transaction, | 14 | type Transaction, |
31 | type TransactionSpec, | 15 | type TransactionSpec, |
16 | type EditorState, | ||
32 | } from '@codemirror/state'; | 17 | } from '@codemirror/state'; |
33 | import { | 18 | import { type Command, EditorView } from '@codemirror/view'; |
34 | drawSelection, | 19 | import { action, computed, makeObservable, observable } from 'mobx'; |
35 | EditorView, | 20 | |
36 | highlightActiveLine, | ||
37 | highlightActiveLineGutter, | ||
38 | highlightSpecialChars, | ||
39 | keymap, | ||
40 | lineNumbers, | ||
41 | rectangularSelection, | ||
42 | } from '@codemirror/view'; | ||
43 | import { classHighlighter } from '@lezer/highlight'; | ||
44 | import { makeAutoObservable, observable, reaction } from 'mobx'; | ||
45 | |||
46 | import problemLanguageSupport from '../language/problemLanguageSupport'; | ||
47 | import type ThemeStore from '../theme/ThemeStore'; | ||
48 | import getLogger from '../utils/getLogger'; | 21 | import getLogger from '../utils/getLogger'; |
49 | import XtextClient from '../xtext/XtextClient'; | 22 | import XtextClient from '../xtext/XtextClient'; |
50 | 23 | ||
51 | import findOccurrences, { | 24 | import PanelStore from './PanelStore'; |
52 | type IOccurrence, | 25 | import createEditorState from './createEditorState'; |
53 | setOccurrences, | 26 | import { type IOccurrence, setOccurrences } from './findOccurrences'; |
54 | } from './findOccurrences'; | 27 | import { |
55 | import semanticHighlighting, { | ||
56 | type IHighlightRange, | 28 | type IHighlightRange, |
57 | setSemanticHighlighting, | 29 | setSemanticHighlighting, |
58 | } from './semanticHighlighting'; | 30 | } from './semanticHighlighting'; |
@@ -60,17 +32,17 @@ import semanticHighlighting, { | |||
60 | const log = getLogger('editor.EditorStore'); | 32 | const log = getLogger('editor.EditorStore'); |
61 | 33 | ||
62 | export default class EditorStore { | 34 | export default class EditorStore { |
63 | private readonly themeStore; | ||
64 | |||
65 | state: EditorState; | 35 | state: EditorState; |
66 | 36 | ||
67 | private readonly client: XtextClient; | 37 | private readonly client: XtextClient; |
68 | 38 | ||
69 | showLineNumbers = false; | 39 | view: EditorView | undefined; |
70 | 40 | ||
71 | showSearchPanel = false; | 41 | readonly searchPanel: PanelStore; |
72 | 42 | ||
73 | showLintPanel = false; | 43 | readonly lintPanel: PanelStore; |
44 | |||
45 | showLineNumbers = false; | ||
74 | 46 | ||
75 | errorCount = 0; | 47 | errorCount = 0; |
76 | 48 | ||
@@ -78,116 +50,124 @@ export default class EditorStore { | |||
78 | 50 | ||
79 | infoCount = 0; | 51 | infoCount = 0; |
80 | 52 | ||
81 | private readonly defaultDispatcher = (tr: Transaction): void => { | 53 | constructor(initialValue: string) { |
82 | this.onTransaction(tr); | 54 | this.state = createEditorState(initialValue, this); |
83 | }; | ||
84 | |||
85 | private dispatcher = this.defaultDispatcher; | ||
86 | |||
87 | constructor(initialValue: string, themeStore: ThemeStore) { | ||
88 | this.themeStore = themeStore; | ||
89 | this.state = EditorState.create({ | ||
90 | doc: initialValue, | ||
91 | extensions: [ | ||
92 | autocompletion({ | ||
93 | activateOnTyping: true, | ||
94 | override: [(context) => this.client.contentAssist(context)], | ||
95 | }), | ||
96 | closeBrackets(), | ||
97 | bracketMatching(), | ||
98 | drawSelection(), | ||
99 | EditorState.allowMultipleSelections.of(true), | ||
100 | EditorView.theme( | ||
101 | {}, | ||
102 | { | ||
103 | dark: this.themeStore.darkMode, | ||
104 | }, | ||
105 | ), | ||
106 | findOccurrences, | ||
107 | highlightActiveLine(), | ||
108 | highlightActiveLineGutter(), | ||
109 | highlightSpecialChars(), | ||
110 | history(), | ||
111 | indentOnInput(), | ||
112 | rectangularSelection(), | ||
113 | search({ | ||
114 | top: true, | ||
115 | caseSensitive: true, | ||
116 | }), | ||
117 | syntaxHighlighting(classHighlighter), | ||
118 | semanticHighlighting, | ||
119 | // We add the gutters to `extensions` in the order we want them to appear. | ||
120 | lineNumbers(), | ||
121 | foldGutter(), | ||
122 | keymap.of([ | ||
123 | { key: 'Mod-Shift-f', run: () => this.formatText() }, | ||
124 | ...closeBracketsKeymap, | ||
125 | ...completionKeymap, | ||
126 | ...foldKeymap, | ||
127 | ...historyKeymap, | ||
128 | indentWithTab, | ||
129 | // Override keys in `lintKeymap` to go through the `EditorStore`. | ||
130 | { key: 'Mod-Shift-m', run: () => this.setLintPanelOpen(true) }, | ||
131 | ...lintKeymap, | ||
132 | // Override keys in `searchKeymap` to go through the `EditorStore`. | ||
133 | { | ||
134 | key: 'Mod-f', | ||
135 | run: () => this.setSearchPanelOpen(true), | ||
136 | scope: 'editor search-panel', | ||
137 | }, | ||
138 | { | ||
139 | key: 'Escape', | ||
140 | run: () => this.setSearchPanelOpen(false), | ||
141 | scope: 'editor search-panel', | ||
142 | }, | ||
143 | ...searchKeymap, | ||
144 | ...defaultKeymap, | ||
145 | ]), | ||
146 | problemLanguageSupport(), | ||
147 | ], | ||
148 | }); | ||
149 | this.client = new XtextClient(this); | 55 | this.client = new XtextClient(this); |
150 | reaction( | 56 | this.searchPanel = new PanelStore( |
151 | () => this.themeStore.darkMode, | 57 | 'search', |
152 | (darkMode) => { | 58 | openSearchPanel, |
153 | log.debug('Update editor dark mode', darkMode); | 59 | closeSearchPanel, |
154 | this.dispatch({ | 60 | this, |
155 | effects: [ | 61 | ); |
156 | StateEffect.appendConfig.of( | 62 | this.lintPanel = new PanelStore( |
157 | EditorView.theme( | 63 | 'panel-lint', |
158 | {}, | 64 | openLintPanel, |
159 | { | 65 | closeLintPanel, |
160 | dark: darkMode, | 66 | this, |
161 | }, | ||
162 | ), | ||
163 | ), | ||
164 | ], | ||
165 | }); | ||
166 | }, | ||
167 | ); | 67 | ); |
168 | makeAutoObservable(this, { | 68 | makeObservable(this, { |
169 | state: observable.ref, | 69 | state: observable.ref, |
70 | view: observable.ref, | ||
71 | showLineNumbers: observable, | ||
72 | errorCount: observable, | ||
73 | warningCount: observable, | ||
74 | infoCount: observable, | ||
75 | highestDiagnosticLevel: computed, | ||
76 | canUndo: computed, | ||
77 | canRedo: computed, | ||
78 | setDarkMode: action, | ||
79 | setEditorParent: action, | ||
80 | dispatch: action, | ||
81 | dispatchTransaction: action, | ||
82 | doCommand: action, | ||
83 | doStateCommand: action, | ||
84 | updateDiagnostics: action, | ||
85 | nextDiagnostic: action, | ||
86 | updateOccurrences: action, | ||
87 | updateSemanticHighlighting: action, | ||
88 | undo: action, | ||
89 | redo: action, | ||
90 | toggleLineNumbers: action, | ||
170 | }); | 91 | }); |
171 | } | 92 | } |
172 | 93 | ||
173 | updateDispatcher(newDispatcher: ((tr: Transaction) => void) | null): void { | 94 | setDarkMode(darkMode: boolean): void { |
174 | this.dispatcher = newDispatcher || this.defaultDispatcher; | 95 | log.debug('Update editor dark mode', darkMode); |
96 | this.dispatch({ | ||
97 | effects: [ | ||
98 | StateEffect.appendConfig.of([EditorView.darkTheme.of(darkMode)]), | ||
99 | ], | ||
100 | }); | ||
175 | } | 101 | } |
176 | 102 | ||
177 | onTransaction(tr: Transaction): void { | 103 | setEditorParent(editorParent: Element | null): void { |
178 | log.trace('Editor transaction', tr); | 104 | if (this.view !== undefined) { |
179 | this.state = tr.state; | 105 | this.view.destroy(); |
180 | this.client.onTransaction(tr); | 106 | } |
107 | if (editorParent === null) { | ||
108 | this.view = undefined; | ||
109 | return; | ||
110 | } | ||
111 | const view = new EditorView({ | ||
112 | state: this.state, | ||
113 | parent: editorParent, | ||
114 | dispatch: (transaction) => { | ||
115 | this.dispatchTransactionWithoutView(transaction); | ||
116 | view.update([transaction]); | ||
117 | if (view.state !== this.state) { | ||
118 | log.error( | ||
119 | 'Failed to synchronize editor state - store state:', | ||
120 | this.state, | ||
121 | 'view state:', | ||
122 | view.state, | ||
123 | ); | ||
124 | } | ||
125 | }, | ||
126 | }); | ||
127 | this.view = view; | ||
128 | this.searchPanel.synchronizeStateToView(); | ||
129 | this.lintPanel.synchronizeStateToView(); | ||
130 | |||
131 | // Reported by Lighthouse 8.3.0. | ||
132 | const { contentDOM } = view; | ||
133 | contentDOM.removeAttribute('aria-expanded'); | ||
134 | contentDOM.setAttribute('aria-label', 'Code editor'); | ||
135 | |||
136 | log.info('Editor created'); | ||
181 | } | 137 | } |
182 | 138 | ||
183 | dispatch(...specs: readonly TransactionSpec[]): void { | 139 | dispatch(...specs: readonly TransactionSpec[]): void { |
184 | this.dispatcher(this.state.update(...specs)); | 140 | const transaction = this.state.update(...specs); |
141 | this.dispatchTransaction(transaction); | ||
142 | } | ||
143 | |||
144 | dispatchTransaction(transaction: Transaction): void { | ||
145 | if (this.view === undefined) { | ||
146 | this.dispatchTransactionWithoutView(transaction); | ||
147 | } else { | ||
148 | this.view.dispatch(transaction); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | private readonly dispatchTransactionWithoutView = action( | ||
153 | (tr: Transaction) => { | ||
154 | log.trace('Editor transaction', tr); | ||
155 | this.state = tr.state; | ||
156 | this.client.onTransaction(tr); | ||
157 | }, | ||
158 | ); | ||
159 | |||
160 | doCommand(command: Command): boolean { | ||
161 | if (this.view === undefined) { | ||
162 | return false; | ||
163 | } | ||
164 | return command(this.view); | ||
185 | } | 165 | } |
186 | 166 | ||
187 | doStateCommand(command: StateCommand): boolean { | 167 | doStateCommand(command: StateCommand): boolean { |
188 | return command({ | 168 | return command({ |
189 | state: this.state, | 169 | state: this.state, |
190 | dispatch: this.dispatcher, | 170 | dispatch: (transaction) => this.dispatchTransaction(transaction), |
191 | }); | 171 | }); |
192 | } | 172 | } |
193 | 173 | ||
@@ -213,7 +193,11 @@ export default class EditorStore { | |||
213 | }); | 193 | }); |
214 | } | 194 | } |
215 | 195 | ||
216 | get highestDiagnosticLevel(): Diagnostic['severity'] | null { | 196 | nextDiagnostic(): void { |
197 | this.doCommand(nextDiagnostic); | ||
198 | } | ||
199 | |||
200 | get highestDiagnosticLevel(): Diagnostic['severity'] | undefined { | ||
217 | if (this.errorCount > 0) { | 201 | if (this.errorCount > 0) { |
218 | return 'error'; | 202 | return 'error'; |
219 | } | 203 | } |
@@ -223,7 +207,7 @@ export default class EditorStore { | |||
223 | if (this.infoCount > 0) { | 207 | if (this.infoCount > 0) { |
224 | return 'info'; | 208 | return 'info'; |
225 | } | 209 | } |
226 | return null; | 210 | return undefined; |
227 | } | 211 | } |
228 | 212 | ||
229 | updateSemanticHighlighting(ranges: IHighlightRange[]): void { | 213 | updateSemanticHighlighting(ranges: IHighlightRange[]): void { |
@@ -234,6 +218,10 @@ export default class EditorStore { | |||
234 | this.dispatch(setOccurrences(write, read)); | 218 | this.dispatch(setOccurrences(write, read)); |
235 | } | 219 | } |
236 | 220 | ||
221 | contentAssist(context: CompletionContext): Promise<CompletionResult> { | ||
222 | return this.client.contentAssist(context); | ||
223 | } | ||
224 | |||
237 | /** | 225 | /** |
238 | * @returns `true` if there is history to undo | 226 | * @returns `true` if there is history to undo |
239 | */ | 227 | */ |
@@ -241,7 +229,6 @@ export default class EditorStore { | |||
241 | return undoDepth(this.state) > 0; | 229 | return undoDepth(this.state) > 0; |
242 | } | 230 | } |
243 | 231 | ||
244 | // eslint-disable-next-line class-methods-use-this | ||
245 | undo(): void { | 232 | undo(): void { |
246 | log.debug('Undo', this.doStateCommand(undo)); | 233 | log.debug('Undo', this.doStateCommand(undo)); |
247 | } | 234 | } |
@@ -253,7 +240,6 @@ export default class EditorStore { | |||
253 | return redoDepth(this.state) > 0; | 240 | return redoDepth(this.state) > 0; |
254 | } | 241 | } |
255 | 242 | ||
256 | // eslint-disable-next-line class-methods-use-this | ||
257 | redo(): void { | 243 | redo(): void { |
258 | log.debug('Redo', this.doStateCommand(redo)); | 244 | log.debug('Redo', this.doStateCommand(redo)); |
259 | } | 245 | } |
@@ -263,44 +249,6 @@ export default class EditorStore { | |||
263 | log.debug('Show line numbers', this.showLineNumbers); | 249 | log.debug('Show line numbers', this.showLineNumbers); |
264 | } | 250 | } |
265 | 251 | ||
266 | /** | ||
267 | * Sets whether the CodeMirror search panel should be open. | ||
268 | * | ||
269 | * This method can be used as a CodeMirror command, | ||
270 | * because it returns `false` if it didn't execute, | ||
271 | * allowing other commands for the same keybind to run instead. | ||
272 | * This matches the behavior of the `openSearchPanel` and `closeSearchPanel` | ||
273 | * commands from `'@codemirror/search'`. | ||
274 | * | ||
275 | * @param newShowSearchPanel whether we should show the search panel | ||
276 | * @returns `true` if the state was changed, `false` otherwise | ||
277 | */ | ||
278 | setSearchPanelOpen(newShowSearchPanel: boolean): boolean { | ||
279 | if (this.showSearchPanel === newShowSearchPanel) { | ||
280 | return false; | ||
281 | } | ||
282 | this.showSearchPanel = newShowSearchPanel; | ||
283 | log.debug('Show search panel', this.showSearchPanel); | ||
284 | return true; | ||
285 | } | ||
286 | |||
287 | toggleSearchPanel(): void { | ||
288 | this.setSearchPanelOpen(!this.showSearchPanel); | ||
289 | } | ||
290 | |||
291 | setLintPanelOpen(newShowLintPanel: boolean): boolean { | ||
292 | if (this.showLintPanel === newShowLintPanel) { | ||
293 | return false; | ||
294 | } | ||
295 | this.showLintPanel = newShowLintPanel; | ||
296 | log.debug('Show lint panel', this.showLintPanel); | ||
297 | return true; | ||
298 | } | ||
299 | |||
300 | toggleLintPanel(): void { | ||
301 | this.setLintPanelOpen(!this.showLintPanel); | ||
302 | } | ||
303 | |||
304 | formatText(): boolean { | 252 | formatText(): boolean { |
305 | this.client.formatText(); | 253 | this.client.formatText(); |
306 | return true; | 254 | return true; |
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts new file mode 100644 index 00000000..8d98e832 --- /dev/null +++ b/subprojects/frontend/src/editor/EditorTheme.ts | |||
@@ -0,0 +1,342 @@ | |||
1 | import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; | ||
2 | import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; | ||
3 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; | ||
4 | import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; | ||
5 | import { alpha, styled } from '@mui/material/styles'; | ||
6 | |||
7 | import editorClassNames from './editorClassNames'; | ||
8 | |||
9 | function svgURL(svg: string): string { | ||
10 | return `url('data:image/svg+xml;utf8,${svg}')`; | ||
11 | } | ||
12 | |||
13 | export default styled('div', { | ||
14 | name: 'EditorTheme', | ||
15 | shouldForwardProp: (propName) => propName !== 'showLineNumbers', | ||
16 | })<{ showLineNumbers: boolean }>(({ theme, showLineNumbers }) => { | ||
17 | let codeMirrorLintStyle: Record<string, unknown> = {}; | ||
18 | ( | ||
19 | [ | ||
20 | { | ||
21 | severity: 'error', | ||
22 | icon: errorSVG, | ||
23 | }, | ||
24 | { | ||
25 | severity: 'warning', | ||
26 | icon: warningSVG, | ||
27 | }, | ||
28 | { | ||
29 | severity: 'info', | ||
30 | icon: infoSVG, | ||
31 | }, | ||
32 | ] as const | ||
33 | ).forEach(({ severity, icon }) => { | ||
34 | const palette = theme.palette[severity]; | ||
35 | const color = palette.main; | ||
36 | const iconStyle = { | ||
37 | background: color, | ||
38 | maskImage: svgURL(icon), | ||
39 | maskSize: '16px 16px', | ||
40 | height: 16, | ||
41 | width: 16, | ||
42 | }; | ||
43 | const tooltipColor = | ||
44 | theme.palette.mode === 'dark' ? palette.main : palette.light; | ||
45 | codeMirrorLintStyle = { | ||
46 | ...codeMirrorLintStyle, | ||
47 | [`.cm-lintRange-${severity}`]: { | ||
48 | backgroundImage: 'none', | ||
49 | textDecoration: `underline wavy ${color}`, | ||
50 | textDecorationSkipInk: 'none', | ||
51 | }, | ||
52 | [`.cm-diagnostic-${severity}`]: { | ||
53 | marginLeft: 0, | ||
54 | padding: '4px 8px 4px 32px', | ||
55 | borderLeft: 'none', | ||
56 | position: 'relative', | ||
57 | '::before': { | ||
58 | ...iconStyle, | ||
59 | content: '" "', | ||
60 | position: 'absolute', | ||
61 | top: 6, | ||
62 | left: 8, | ||
63 | }, | ||
64 | }, | ||
65 | [`.cm-tooltip .cm-diagnostic-${severity}::before`]: { | ||
66 | background: tooltipColor, | ||
67 | }, | ||
68 | [`.cm-lint-marker-${severity}`]: { | ||
69 | ...iconStyle, | ||
70 | display: 'block', | ||
71 | margin: '4px 0', | ||
72 | // Remove original CodeMirror icon. | ||
73 | content: '""', | ||
74 | '::before': { | ||
75 | // Remove original CodeMirror icon. | ||
76 | content: '""', | ||
77 | display: 'none', | ||
78 | }, | ||
79 | }, | ||
80 | }; | ||
81 | }); | ||
82 | |||
83 | return { | ||
84 | background: theme.palette.background.default, | ||
85 | '&, .cm-editor': { | ||
86 | height: '100%', | ||
87 | }, | ||
88 | '.cm-content': { | ||
89 | padding: 0, | ||
90 | }, | ||
91 | '.cm-scroller': { | ||
92 | color: theme.palette.text.secondary, | ||
93 | }, | ||
94 | '.cm-scroller, .cm-tooltip-autocomplete, .cm-completionLabel, .cm-completionDetail': | ||
95 | { | ||
96 | ...theme.typography.body1, | ||
97 | fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", monospace', | ||
98 | fontFeatureSettings: '"liga", "calt"', | ||
99 | letterSpacing: 0, | ||
100 | textRendering: 'optimizeLegibility', | ||
101 | }, | ||
102 | '.cm-gutters': { | ||
103 | background: 'transparent', | ||
104 | color: theme.palette.text.disabled, | ||
105 | border: 'none', | ||
106 | }, | ||
107 | '.cm-specialChar': { | ||
108 | color: theme.palette.secondary.main, | ||
109 | }, | ||
110 | '.cm-activeLine': { | ||
111 | background: theme.palette.highlight.activeLine, | ||
112 | }, | ||
113 | '.cm-gutter-lint': { | ||
114 | width: 16, | ||
115 | '.cm-gutterElement': { | ||
116 | padding: 0, | ||
117 | }, | ||
118 | }, | ||
119 | '.cm-foldGutter': { | ||
120 | opacity: 0, | ||
121 | width: 16, | ||
122 | transition: theme.transitions.create('opacity', { | ||
123 | duration: theme.transitions.duration.short, | ||
124 | }), | ||
125 | '@media (hover: none)': { | ||
126 | opacity: 1, | ||
127 | }, | ||
128 | }, | ||
129 | '.cm-gutters:hover .cm-foldGutter': { | ||
130 | opacity: 1, | ||
131 | }, | ||
132 | [`.${editorClassNames.foldMarker}`]: { | ||
133 | display: 'block', | ||
134 | margin: '4px 0', | ||
135 | padding: 0, | ||
136 | maskImage: svgURL(expandMoreSVG), | ||
137 | maskSize: '16px 16px', | ||
138 | height: 16, | ||
139 | width: 16, | ||
140 | background: theme.palette.text.primary, | ||
141 | border: 'none', | ||
142 | cursor: 'pointer', | ||
143 | }, | ||
144 | [`.${editorClassNames.foldMarkerClosed}`]: { | ||
145 | transform: 'rotate(-90deg)', | ||
146 | }, | ||
147 | '.cm-activeLineGutter': { | ||
148 | background: 'transparent', | ||
149 | }, | ||
150 | '.cm-lineNumbers': { | ||
151 | ...(!showLineNumbers && { | ||
152 | display: 'none !important', | ||
153 | }), | ||
154 | '.cm-activeLineGutter': { | ||
155 | color: theme.palette.text.primary, | ||
156 | }, | ||
157 | }, | ||
158 | '.cm-cursor, .cm-cursor-primary': { | ||
159 | borderLeft: `2px solid ${theme.palette.primary.main}`, | ||
160 | }, | ||
161 | '.cm-selectionBackground': { | ||
162 | background: theme.palette.highlight.selection, | ||
163 | }, | ||
164 | '.cm-focused': { | ||
165 | outline: 'none', | ||
166 | '.cm-selectionBackground': { | ||
167 | background: theme.palette.highlight.selection, | ||
168 | }, | ||
169 | }, | ||
170 | '.cm-panels-top': { | ||
171 | color: theme.palette.text.secondary, | ||
172 | borderBottom: `1px solid ${theme.palette.outer.border}`, | ||
173 | marginBottom: theme.spacing(1), | ||
174 | }, | ||
175 | '.cm-panel': { | ||
176 | position: 'relative', | ||
177 | overflow: 'hidden', | ||
178 | background: theme.palette.outer.background, | ||
179 | borderTop: `1px solid ${theme.palette.outer.border}`, | ||
180 | '&, & button, & input': { | ||
181 | fontFamily: theme.typography.fontFamily, | ||
182 | }, | ||
183 | 'button[name="close"]': { | ||
184 | background: 'transparent', | ||
185 | color: theme.palette.text.secondary, | ||
186 | cursor: 'pointer', | ||
187 | }, | ||
188 | }, | ||
189 | '.cm-panel.cm-panel-lint': { | ||
190 | borderTop: `1px solid ${theme.palette.outer.border}`, | ||
191 | borderBottom: 'none', | ||
192 | 'button[name="close"]': { | ||
193 | // Close button interferes with scrollbar, so we better hide it. | ||
194 | // The panel can still be closed from the toolbar. | ||
195 | display: 'none', | ||
196 | }, | ||
197 | ul: { | ||
198 | maxHeight: 'max(112px, 20vh)', | ||
199 | li: { | ||
200 | cursor: 'pointer', | ||
201 | color: theme.palette.text.primary, | ||
202 | }, | ||
203 | '.cm-diagnostic': { | ||
204 | ...theme.typography.body2, | ||
205 | '&[aria-selected="true"]': { | ||
206 | color: theme.palette.text.primary, | ||
207 | background: 'transparent', | ||
208 | fontWeight: 700, | ||
209 | }, | ||
210 | ':hover': { | ||
211 | background: alpha( | ||
212 | theme.palette.text.primary, | ||
213 | theme.palette.action.hoverOpacity, | ||
214 | ), | ||
215 | }, | ||
216 | }, | ||
217 | }, | ||
218 | }, | ||
219 | [`.${editorClassNames.foldPlaceholder}`]: { | ||
220 | ...theme.typography.body1, | ||
221 | padding: 0, | ||
222 | fontFamily: 'inherit', | ||
223 | fontFeatureSettings: '"liga", "calt"', | ||
224 | color: theme.palette.text.secondary, | ||
225 | backgroundColor: alpha( | ||
226 | theme.palette.text.secondary, | ||
227 | theme.palette.action.focusOpacity, | ||
228 | ), | ||
229 | border: 'none', | ||
230 | cursor: 'pointer', | ||
231 | transition: theme.transitions.create(['background-color', 'color'], { | ||
232 | duration: theme.transitions.duration.short, | ||
233 | }), | ||
234 | '&:hover': { | ||
235 | color: theme.palette.text.primary, | ||
236 | backgroundColor: alpha( | ||
237 | theme.palette.text.secondary, | ||
238 | theme.palette.action.focusOpacity + theme.palette.action.hoverOpacity, | ||
239 | ), | ||
240 | }, | ||
241 | }, | ||
242 | '.tok-comment': { | ||
243 | fontStyle: 'italic', | ||
244 | color: theme.palette.highlight.comment, | ||
245 | }, | ||
246 | '.tok-number': { | ||
247 | color: theme.palette.highlight.number, | ||
248 | }, | ||
249 | '.tok-string': { | ||
250 | color: theme.palette.secondary, | ||
251 | }, | ||
252 | '.tok-keyword': { | ||
253 | color: theme.palette.primary.main, | ||
254 | }, | ||
255 | '.tok-typeName, .tok-atom': { | ||
256 | color: theme.palette.text.primary, | ||
257 | }, | ||
258 | '.tok-variableName': { | ||
259 | color: theme.palette.highlight.parameter, | ||
260 | }, | ||
261 | '.tok-problem-node': { | ||
262 | '&, & .tok-variableName': { | ||
263 | color: theme.palette.text.secondary, | ||
264 | }, | ||
265 | }, | ||
266 | '.tok-problem-individual': { | ||
267 | '&, & .tok-variableName': { | ||
268 | color: theme.palette.text.primary, | ||
269 | }, | ||
270 | }, | ||
271 | '.tok-problem-abstract, .tok-problem-new': { | ||
272 | fontStyle: 'italic', | ||
273 | }, | ||
274 | '.tok-problem-containment': { | ||
275 | fontWeight: 700, | ||
276 | }, | ||
277 | '.tok-problem-error': { | ||
278 | '&, & .tok-typeName': { | ||
279 | color: theme.palette.error.main, | ||
280 | }, | ||
281 | }, | ||
282 | '.tok-problem-builtin': { | ||
283 | '&, & .tok-typeName, & .tok-atom, & .tok-variableName': { | ||
284 | color: theme.palette.primary.main, | ||
285 | fontWeight: 400, | ||
286 | fontStyle: 'normal', | ||
287 | }, | ||
288 | }, | ||
289 | '.cm-tooltip.cm-tooltip-autocomplete': { | ||
290 | background: theme.palette.background.paper, | ||
291 | borderRadius: theme.shape.borderRadius, | ||
292 | overflow: 'hidden', | ||
293 | ...(theme.palette.mode === 'dark' && { | ||
294 | // https://github.com/mui/material-ui/blob/10c72729c7d03bab8cdce6eb422642684c56dca2/packages/mui-material/src/Paper/Paper.js#L18 | ||
295 | backgroundImage: | ||
296 | 'linear-gradient(rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.07))', | ||
297 | }), | ||
298 | boxShadow: theme.shadows[2], | ||
299 | '.cm-completionIcon': { | ||
300 | color: theme.palette.text.secondary, | ||
301 | }, | ||
302 | '.cm-completionLabel': { | ||
303 | color: theme.palette.text.primary, | ||
304 | }, | ||
305 | '.cm-completionDetail': { | ||
306 | color: theme.palette.text.secondary, | ||
307 | fontStyle: 'normal', | ||
308 | }, | ||
309 | 'li[aria-selected="true"]': { | ||
310 | background: alpha( | ||
311 | theme.palette.text.primary, | ||
312 | theme.palette.action.focusOpacity, | ||
313 | ), | ||
314 | '.cm-completionIcon, .cm-completionLabel, .cm-completionDetail': { | ||
315 | color: theme.palette.text.primary, | ||
316 | }, | ||
317 | }, | ||
318 | }, | ||
319 | '.cm-tooltip.cm-tooltip-hover, .cm-tooltip.cm-tooltip-lint': { | ||
320 | ...theme.typography.body2, | ||
321 | // https://github.com/mui/material-ui/blob/dee9529f7a298c54ae760761112c3ae9ba082137/packages/mui-material/src/Tooltip/Tooltip.js#L121-L125 | ||
322 | background: alpha(theme.palette.grey[700], 0.92), | ||
323 | borderRadius: theme.shape.borderRadius, | ||
324 | color: theme.palette.common.white, | ||
325 | overflow: 'hidden', | ||
326 | maxWidth: 400, | ||
327 | }, | ||
328 | '.cm-completionIcon': { | ||
329 | width: 16, | ||
330 | padding: 0, | ||
331 | marginRight: '0.5em', | ||
332 | textAlign: 'center', | ||
333 | }, | ||
334 | ...codeMirrorLintStyle, | ||
335 | '.cm-problem-read': { | ||
336 | background: theme.palette.highlight.occurences.read, | ||
337 | }, | ||
338 | '.cm-problem-write': { | ||
339 | background: theme.palette.highlight.occurences.write, | ||
340 | }, | ||
341 | }; | ||
342 | }); | ||
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx index 4d5c4e44..0eed129e 100644 --- a/subprojects/frontend/src/editor/GenerateButton.tsx +++ b/subprojects/frontend/src/editor/GenerateButton.tsx | |||
@@ -22,7 +22,7 @@ function GenerateButton(): JSX.Element { | |||
22 | 22 | ||
23 | if (errorCount > 0) { | 23 | if (errorCount > 0) { |
24 | return ( | 24 | return ( |
25 | <Button color="error" onClick={() => editorStore.toggleLintPanel()}> | 25 | <Button color="error" onClick={() => editorStore.nextDiagnostic()}> |
26 | {summary} | 26 | {summary} |
27 | </Button> | 27 | </Button> |
28 | ); | 28 | ); |
diff --git a/subprojects/frontend/src/editor/PanelStore.ts b/subprojects/frontend/src/editor/PanelStore.ts new file mode 100644 index 00000000..653d309c --- /dev/null +++ b/subprojects/frontend/src/editor/PanelStore.ts | |||
@@ -0,0 +1,90 @@ | |||
1 | import type { Command } from '@codemirror/view'; | ||
2 | import { action, makeObservable, observable } from 'mobx'; | ||
3 | |||
4 | import getLogger from '../utils/getLogger'; | ||
5 | |||
6 | import type EditorStore from './EditorStore'; | ||
7 | |||
8 | const log = getLogger('editor.PanelStore'); | ||
9 | |||
10 | export default class PanelStore { | ||
11 | state = false; | ||
12 | |||
13 | constructor( | ||
14 | private readonly panelId: string, | ||
15 | private readonly openCommand: Command, | ||
16 | private readonly closeCommand: Command, | ||
17 | private readonly store: EditorStore, | ||
18 | ) { | ||
19 | makeObservable(this, { | ||
20 | state: observable, | ||
21 | open: action, | ||
22 | close: action, | ||
23 | toggle: action, | ||
24 | synchronizeStateToView: action, | ||
25 | }); | ||
26 | } | ||
27 | |||
28 | open(): boolean { | ||
29 | return this.setState(true); | ||
30 | } | ||
31 | |||
32 | close(): boolean { | ||
33 | return this.setState(false); | ||
34 | } | ||
35 | |||
36 | toggle(): void { | ||
37 | this.setState(!this.state); | ||
38 | } | ||
39 | |||
40 | private setState(newState: boolean): boolean { | ||
41 | if (this.state === newState) { | ||
42 | return false; | ||
43 | } | ||
44 | log.debug('Show', this.panelId, 'panel', newState); | ||
45 | if (newState) { | ||
46 | this.doOpen(); | ||
47 | } else { | ||
48 | this.doClose(); | ||
49 | } | ||
50 | this.state = newState; | ||
51 | return true; | ||
52 | } | ||
53 | |||
54 | synchronizeStateToView(): void { | ||
55 | this.doClose(); | ||
56 | if (this.state) { | ||
57 | this.doOpen(); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | private doOpen(): void { | ||
62 | if (!this.store.doCommand(this.openCommand)) { | ||
63 | return; | ||
64 | } | ||
65 | const { view } = this.store; | ||
66 | if (view === undefined) { | ||
67 | return; | ||
68 | } | ||
69 | const buttonQuery = `.cm-${this.panelId}.cm-panel button[name="close"]`; | ||
70 | const closeButton = view.dom.querySelector(buttonQuery); | ||
71 | if (closeButton !== null) { | ||
72 | log.debug('Addig close button callback to', this.panelId, 'panel'); | ||
73 | // We must remove the event listener from the button that dispatches a transaction | ||
74 | // without going through `EditorStore`. This listened is added by CodeMirror, | ||
75 | // and we can only remove it by cloning the DOM node: https://stackoverflow.com/a/9251864 | ||
76 | const closeButtonWithoutListeners = closeButton.cloneNode(true); | ||
77 | closeButtonWithoutListeners.addEventListener('click', (event) => { | ||
78 | this.close(); | ||
79 | event.preventDefault(); | ||
80 | }); | ||
81 | closeButton.replaceWith(closeButtonWithoutListeners); | ||
82 | } else { | ||
83 | log.error('Opened', this.panelId, 'panel has no close button'); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | private doClose(): void { | ||
88 | this.store.doCommand(this.closeCommand); | ||
89 | } | ||
90 | } | ||
diff --git a/subprojects/frontend/src/editor/createEditorState.ts b/subprojects/frontend/src/editor/createEditorState.ts new file mode 100644 index 00000000..33346c05 --- /dev/null +++ b/subprojects/frontend/src/editor/createEditorState.ts | |||
@@ -0,0 +1,121 @@ | |||
1 | import { | ||
2 | closeBrackets, | ||
3 | closeBracketsKeymap, | ||
4 | autocompletion, | ||
5 | completionKeymap, | ||
6 | } from '@codemirror/autocomplete'; | ||
7 | import { | ||
8 | defaultKeymap, | ||
9 | history, | ||
10 | historyKeymap, | ||
11 | indentWithTab, | ||
12 | } from '@codemirror/commands'; | ||
13 | import { | ||
14 | bracketMatching, | ||
15 | codeFolding, | ||
16 | foldGutter, | ||
17 | foldKeymap, | ||
18 | indentOnInput, | ||
19 | syntaxHighlighting, | ||
20 | } from '@codemirror/language'; | ||
21 | import { lintKeymap, lintGutter } from '@codemirror/lint'; | ||
22 | import { search, searchKeymap } from '@codemirror/search'; | ||
23 | import { EditorState } from '@codemirror/state'; | ||
24 | import { | ||
25 | drawSelection, | ||
26 | highlightActiveLine, | ||
27 | highlightActiveLineGutter, | ||
28 | highlightSpecialChars, | ||
29 | keymap, | ||
30 | lineNumbers, | ||
31 | rectangularSelection, | ||
32 | } from '@codemirror/view'; | ||
33 | import { classHighlighter } from '@lezer/highlight'; | ||
34 | |||
35 | import problemLanguageSupport from '../language/problemLanguageSupport'; | ||
36 | |||
37 | import type EditorStore from './EditorStore'; | ||
38 | import editorClassNames from './editorClassNames'; | ||
39 | import findOccurrences from './findOccurrences'; | ||
40 | import semanticHighlighting from './semanticHighlighting'; | ||
41 | |||
42 | export default function createEditorState( | ||
43 | initialValue: string, | ||
44 | store: EditorStore, | ||
45 | ): EditorState { | ||
46 | return EditorState.create({ | ||
47 | doc: initialValue, | ||
48 | extensions: [ | ||
49 | autocompletion({ | ||
50 | activateOnTyping: true, | ||
51 | override: [(context) => store.contentAssist(context)], | ||
52 | }), | ||
53 | closeBrackets(), | ||
54 | bracketMatching(), | ||
55 | drawSelection(), | ||
56 | EditorState.allowMultipleSelections.of(true), | ||
57 | findOccurrences, | ||
58 | highlightActiveLine(), | ||
59 | highlightActiveLineGutter(), | ||
60 | highlightSpecialChars(), | ||
61 | history(), | ||
62 | indentOnInput(), | ||
63 | rectangularSelection(), | ||
64 | search({ top: true }), | ||
65 | syntaxHighlighting(classHighlighter), | ||
66 | semanticHighlighting, | ||
67 | // We add the gutters to `extensions` in the order we want them to appear. | ||
68 | lintGutter(), | ||
69 | lineNumbers(), | ||
70 | codeFolding({ | ||
71 | placeholderDOM(_view, onClick) { | ||
72 | const button = document.createElement('button'); | ||
73 | button.className = editorClassNames.foldPlaceholder; | ||
74 | button.ariaLabel = 'Unfold lines'; | ||
75 | button.innerText = '...'; | ||
76 | button.onclick = onClick; | ||
77 | return button; | ||
78 | }, | ||
79 | }), | ||
80 | foldGutter({ | ||
81 | markerDOM(open) { | ||
82 | const button = document.createElement('button'); | ||
83 | button.className = [ | ||
84 | editorClassNames.foldMarker, | ||
85 | open | ||
86 | ? editorClassNames.foldMarkerOpen | ||
87 | : editorClassNames.foldMarkerClosed, | ||
88 | ].join(' '); | ||
89 | button.ariaPressed = open ? 'true' : 'false'; | ||
90 | button.ariaLabel = 'Fold lines'; | ||
91 | return button; | ||
92 | }, | ||
93 | }), | ||
94 | keymap.of([ | ||
95 | { key: 'Mod-Shift-f', run: () => store.formatText() }, | ||
96 | ...closeBracketsKeymap, | ||
97 | ...completionKeymap, | ||
98 | ...foldKeymap, | ||
99 | ...historyKeymap, | ||
100 | indentWithTab, | ||
101 | // Override keys in `lintKeymap` to go through the `EditorStore`. | ||
102 | { key: 'Mod-Shift-m', run: () => store.lintPanel.open() }, | ||
103 | ...lintKeymap, | ||
104 | // Override keys in `searchKeymap` to go through the `EditorStore`. | ||
105 | { | ||
106 | key: 'Mod-f', | ||
107 | run: () => store.searchPanel.open(), | ||
108 | scope: 'editor search-panel', | ||
109 | }, | ||
110 | { | ||
111 | key: 'Escape', | ||
112 | run: () => store.searchPanel.close(), | ||
113 | scope: 'editor search-panel', | ||
114 | }, | ||
115 | ...searchKeymap, | ||
116 | ...defaultKeymap, | ||
117 | ]), | ||
118 | problemLanguageSupport(), | ||
119 | ], | ||
120 | }); | ||
121 | } | ||
diff --git a/subprojects/frontend/src/editor/editorClassNames.ts b/subprojects/frontend/src/editor/editorClassNames.ts new file mode 100644 index 00000000..5584e8c2 --- /dev/null +++ b/subprojects/frontend/src/editor/editorClassNames.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | const PREFIX = 'problem-editor-'; | ||
2 | |||
3 | const editorClassNames = { | ||
4 | foldPlaceholder: `${PREFIX}fold-placeholder`, | ||
5 | foldMarker: `${PREFIX}fold-marker`, | ||
6 | foldMarkerClosed: `${PREFIX}fold-marker-closed`, | ||
7 | foldMarkerOpen: `${PREFIX}fold-marker-open`, | ||
8 | }; | ||
9 | |||
10 | export default editorClassNames; | ||
diff --git a/subprojects/frontend/src/language/problemLanguageSupport.ts b/subprojects/frontend/src/language/problemLanguageSupport.ts index 246135d8..07a884e7 100644 --- a/subprojects/frontend/src/language/problemLanguageSupport.ts +++ b/subprojects/frontend/src/language/problemLanguageSupport.ts | |||
@@ -28,18 +28,18 @@ const parserWithMetadata = parser.configure({ | |||
28 | BlockComment: t.blockComment, | 28 | BlockComment: t.blockComment, |
29 | 'problem class enum pred rule indiv scope': t.definitionKeyword, | 29 | 'problem class enum pred rule indiv scope': t.definitionKeyword, |
30 | 'abstract extends refers contains opposite error default': t.modifier, | 30 | 'abstract extends refers contains opposite error default': t.modifier, |
31 | 'true false unknown error': t.keyword, | 31 | 'true false unknown error': t.operatorKeyword, |
32 | 'may must current count': t.operatorKeyword, | 32 | 'may must current count': t.operatorKeyword, |
33 | 'new delete': t.operatorKeyword, | 33 | 'new delete': t.keyword, |
34 | NotOp: t.operator, | 34 | NotOp: t.operator, |
35 | UnknownOp: t.operator, | 35 | UnknownOp: t.operator, |
36 | OrOp: t.punctuation, | 36 | OrOp: t.separator, |
37 | StarArgument: t.keyword, | 37 | StarArgument: t.keyword, |
38 | 'IntMult StarMult Real': t.number, | 38 | 'IntMult StarMult Real': t.number, |
39 | StarMult: t.number, | 39 | StarMult: t.number, |
40 | String: t.string, | 40 | String: t.string, |
41 | 'RelationName/QualifiedName': t.typeName, | 41 | 'RelationName/QualifiedName': t.typeName, |
42 | 'RuleName/QualifiedName': t.macroName, | 42 | 'RuleName/QualifiedName': t.typeName, |
43 | 'IndividualNodeName/QualifiedName': t.atom, | 43 | 'IndividualNodeName/QualifiedName': t.atom, |
44 | 'VariableName/QualifiedName': t.variableName, | 44 | 'VariableName/QualifiedName': t.variableName, |
45 | '{ }': t.brace, | 45 | '{ }': t.brace, |
diff --git a/subprojects/frontend/src/theme/EditorTheme.ts b/subprojects/frontend/src/theme/EditorTheme.ts deleted file mode 100644 index a16b4c3b..00000000 --- a/subprojects/frontend/src/theme/EditorTheme.ts +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | enum EditorTheme { | ||
2 | Light, | ||
3 | Dark, | ||
4 | Default = EditorTheme.Dark, | ||
5 | } | ||
6 | |||
7 | export default EditorTheme; | ||
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 9a8fdd44..dd4f5bb8 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -11,13 +11,17 @@ import React, { type ReactNode } from 'react'; | |||
11 | 11 | ||
12 | import { useRootStore } from '../RootStore'; | 12 | import { useRootStore } from '../RootStore'; |
13 | 13 | ||
14 | import EditorTheme from './EditorTheme'; | 14 | interface OuterPalette { |
15 | background: string; | ||
16 | border: string; | ||
17 | } | ||
15 | 18 | ||
16 | interface HighlightStyles { | 19 | interface HighlightPalette { |
17 | number: string; | 20 | number: string; |
18 | parameter: string; | 21 | parameter: string; |
19 | comment: string; | 22 | comment: string; |
20 | activeLine: string; | 23 | activeLine: string; |
24 | selection: string; | ||
21 | occurences: { | 25 | occurences: { |
22 | read: string; | 26 | read: string; |
23 | write: string; | 27 | write: string; |
@@ -26,19 +30,17 @@ interface HighlightStyles { | |||
26 | 30 | ||
27 | declare module '@mui/material/styles' { | 31 | declare module '@mui/material/styles' { |
28 | interface Palette { | 32 | interface Palette { |
29 | divider2: string; | 33 | outer: OuterPalette; |
30 | selection: Palette['primary']; | 34 | highlight: HighlightPalette; |
31 | highlight: HighlightStyles; | ||
32 | } | 35 | } |
33 | 36 | ||
34 | interface PaletteOptions { | 37 | interface PaletteOptions { |
35 | divider2: string; | 38 | outer: OuterPalette; |
36 | selection: PaletteOptions['primary']; | 39 | highlight: HighlightPalette; |
37 | highlight: HighlightStyles; | ||
38 | } | 40 | } |
39 | } | 41 | } |
40 | 42 | ||
41 | function getMUIThemeOptions(currentTheme: EditorTheme): ThemeOptions { | 43 | function getMUIThemeOptions(darkMode: boolean): ThemeOptions { |
42 | const components: Components = { | 44 | const components: Components = { |
43 | MuiButton: { | 45 | MuiButton: { |
44 | styleOverrides: { | 46 | styleOverrides: { |
@@ -67,32 +69,8 @@ function getMUIThemeOptions(currentTheme: EditorTheme): ThemeOptions { | |||
67 | }, | 69 | }, |
68 | }; | 70 | }; |
69 | 71 | ||
70 | switch (currentTheme) { | 72 | return darkMode |
71 | case EditorTheme.Light: | 73 | ? { |
72 | return { | ||
73 | components, | ||
74 | palette: { | ||
75 | mode: 'light', | ||
76 | primary: { main: '#0097a7' }, | ||
77 | selection: { | ||
78 | main: '#c8e4fb', | ||
79 | contrastText: '#000', | ||
80 | }, | ||
81 | divider2: '#d7d7d7', | ||
82 | highlight: { | ||
83 | number: '#1976d2', | ||
84 | parameter: '#6a3e3e', | ||
85 | comment: alpha('#000', 0.38), | ||
86 | activeLine: '#f5f5f5', | ||
87 | occurences: { | ||
88 | read: '#ceccf7', | ||
89 | write: '#f0d8a8', | ||
90 | }, | ||
91 | }, | ||
92 | }, | ||
93 | }; | ||
94 | case EditorTheme.Dark: | ||
95 | return { | ||
96 | components, | 74 | components, |
97 | palette: { | 75 | palette: { |
98 | mode: 'dark', | 76 | mode: 'dark', |
@@ -111,34 +89,53 @@ function getMUIThemeOptions(currentTheme: EditorTheme): ThemeOptions { | |||
111 | disabled: '#4b5263', | 89 | disabled: '#4b5263', |
112 | }, | 90 | }, |
113 | divider: alpha('#abb2bf', 0.16), | 91 | divider: alpha('#abb2bf', 0.16), |
114 | divider2: '#181a1f', | 92 | outer: { |
115 | selection: { | 93 | background: '#21252b', |
116 | main: '#3e4453', | 94 | border: '#181a1f', |
117 | contrastText: '#fff', | ||
118 | }, | 95 | }, |
119 | highlight: { | 96 | highlight: { |
120 | number: '#6188a6', | 97 | number: '#6188a6', |
121 | parameter: '#c8ae9d', | 98 | parameter: '#c8ae9d', |
122 | comment: '#6b717d', | 99 | comment: '#6b717d', |
123 | activeLine: '#21252b', | 100 | activeLine: '#21252b', |
101 | selection: '#3e4453', | ||
124 | occurences: { | 102 | occurences: { |
125 | read: 'rgba(255, 255, 255, 0.15)', | 103 | read: 'rgba(255, 255, 255, 0.15)', |
126 | write: 'rgba(255, 255, 128, 0.4)', | 104 | write: 'rgba(255, 255, 128, 0.4)', |
127 | }, | 105 | }, |
128 | }, | 106 | }, |
129 | }, | 107 | }, |
108 | } | ||
109 | : { | ||
110 | components, | ||
111 | palette: { | ||
112 | mode: 'light', | ||
113 | primary: { main: '#0097a7' }, | ||
114 | outer: { | ||
115 | background: '#f5f5f5', | ||
116 | border: '#d7d7d7', | ||
117 | }, | ||
118 | highlight: { | ||
119 | number: '#1976d2', | ||
120 | parameter: '#6a3e3e', | ||
121 | comment: alpha('#000', 0.38), | ||
122 | activeLine: '#f5f5f5', | ||
123 | selection: '#c8e4fb', | ||
124 | occurences: { | ||
125 | read: '#ceccf7', | ||
126 | write: '#f0d8a8', | ||
127 | }, | ||
128 | }, | ||
129 | }, | ||
130 | }; | 130 | }; |
131 | default: | ||
132 | throw new Error(`Unknown theme: ${currentTheme}`); | ||
133 | } | ||
134 | } | 131 | } |
135 | 132 | ||
136 | function ThemeProvider({ children }: { children?: ReactNode }) { | 133 | function ThemeProvider({ children }: { children?: ReactNode }) { |
137 | const { | 134 | const { |
138 | themeStore: { currentTheme }, | 135 | themeStore: { darkMode }, |
139 | } = useRootStore(); | 136 | } = useRootStore(); |
140 | 137 | ||
141 | const themeOptions = getMUIThemeOptions(currentTheme); | 138 | const themeOptions = getMUIThemeOptions(darkMode); |
142 | const theme = responsiveFontSizes(createTheme(themeOptions)); | 139 | const theme = responsiveFontSizes(createTheme(themeOptions)); |
143 | 140 | ||
144 | return ( | 141 | return ( |
diff --git a/subprojects/frontend/src/theme/ThemeStore.ts b/subprojects/frontend/src/theme/ThemeStore.ts index ded1f29a..11391b06 100644 --- a/subprojects/frontend/src/theme/ThemeStore.ts +++ b/subprojects/frontend/src/theme/ThemeStore.ts | |||
@@ -1,28 +1,16 @@ | |||
1 | import { makeAutoObservable } from 'mobx'; | 1 | import { action, makeObservable, observable } from 'mobx'; |
2 | |||
3 | import EditorTheme from './EditorTheme'; | ||
4 | 2 | ||
5 | export default class ThemeStore { | 3 | export default class ThemeStore { |
6 | currentTheme: EditorTheme = EditorTheme.Default; | 4 | darkMode = true; |
7 | 5 | ||
8 | constructor() { | 6 | constructor() { |
9 | makeAutoObservable(this); | 7 | makeObservable(this, { |
8 | darkMode: observable, | ||
9 | toggleDarkMode: action, | ||
10 | }); | ||
10 | } | 11 | } |
11 | 12 | ||
12 | toggleDarkMode(): void { | 13 | toggleDarkMode(): void { |
13 | switch (this.currentTheme) { | 14 | this.darkMode = !this.darkMode; |
14 | case EditorTheme.Light: | ||
15 | this.currentTheme = EditorTheme.Dark; | ||
16 | break; | ||
17 | case EditorTheme.Dark: | ||
18 | this.currentTheme = EditorTheme.Light; | ||
19 | break; | ||
20 | default: | ||
21 | throw new Error(`Unknown theme: ${this.currentTheme}`); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | get darkMode(): boolean { | ||
26 | return this.currentTheme === EditorTheme.Dark; | ||
27 | } | 15 | } |
28 | } | 16 | } |
diff --git a/subprojects/frontend/tsconfig.base.json b/subprojects/frontend/tsconfig.base.json index e33e330e..9cc8ace4 100644 --- a/subprojects/frontend/tsconfig.base.json +++ b/subprojects/frontend/tsconfig.base.json | |||
@@ -5,6 +5,7 @@ | |||
5 | "moduleResolution": "Node", | 5 | "moduleResolution": "Node", |
6 | "esModuleInterop": true, | 6 | "esModuleInterop": true, |
7 | "allowSyntheticDefaultImports": true, | 7 | "allowSyntheticDefaultImports": true, |
8 | "useDefineForClassFields": true, | ||
8 | "strict": true, | 9 | "strict": true, |
9 | "noImplicitOverride": true, | 10 | "noImplicitOverride": true, |
10 | "noImplicitReturns": true, | 11 | "noImplicitReturns": true, |
@@ -388,14 +388,14 @@ __metadata: | |||
388 | languageName: node | 388 | languageName: node |
389 | linkType: hard | 389 | linkType: hard |
390 | 390 | ||
391 | "@codemirror/search@npm:^6.0.1": | 391 | "@codemirror/search@npm:^6.1.0": |
392 | version: 6.0.1 | 392 | version: 6.1.0 |
393 | resolution: "@codemirror/search@npm:6.0.1" | 393 | resolution: "@codemirror/search@npm:6.1.0" |
394 | dependencies: | 394 | dependencies: |
395 | "@codemirror/state": ^6.0.0 | 395 | "@codemirror/state": ^6.0.0 |
396 | "@codemirror/view": ^6.0.0 | 396 | "@codemirror/view": ^6.0.0 |
397 | crelt: ^1.0.5 | 397 | crelt: ^1.0.5 |
398 | checksum: e73536db38a4ee0f5a2c2044ad4754f8d4098900f36ca8deb42b3548b39be7d6be2a475fc41b75cd188f25c9613fe93b20177a03f79e2eac5ec0fa71f56c552e | 398 | checksum: 3cf85eec96236f856ba947241a0587e46bbfd3c88f5a90bd4c5f0bbb867055732b8cf6dc146e5e0c3f4dfd87540e97908fdae033f991c55f6fa9179059cb80fc |
399 | languageName: node | 399 | languageName: node |
400 | linkType: hard | 400 | linkType: hard |
401 | 401 | ||
@@ -718,24 +718,31 @@ __metadata: | |||
718 | languageName: node | 718 | languageName: node |
719 | linkType: hard | 719 | linkType: hard |
720 | 720 | ||
721 | "@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.2.2": | 721 | "@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.2.3": |
722 | version: 1.2.2 | 722 | version: 1.2.3 |
723 | resolution: "@lezer/lr@npm:1.2.2" | 723 | resolution: "@lezer/lr@npm:1.2.3" |
724 | dependencies: | 724 | dependencies: |
725 | "@lezer/common": ^1.0.0 | 725 | "@lezer/common": ^1.0.0 |
726 | checksum: fa951958ad9d0012acb09c0fe4c381fe97f6d2ab56e89932272c9d444775232a9b10f4be577068b609a60ae990d3e2714e8f5f9030d18260531f746af857c266 | 726 | checksum: 118db077d32f6eb9d6b219c766d7e5715c6c004d89d76ed0137ea2a364aeca67f21dc5924ea67a649b5de1d13bfbb85571923b38536e6995820eb0a5c8844594 |
727 | languageName: node | 727 | languageName: node |
728 | linkType: hard | 728 | linkType: hard |
729 | 729 | ||
730 | "@mui/base@npm:5.0.0-alpha.92": | 730 | "@material-icons/svg@npm:^1.0.32": |
731 | version: 5.0.0-alpha.92 | 731 | version: 1.0.32 |
732 | resolution: "@mui/base@npm:5.0.0-alpha.92" | 732 | resolution: "@material-icons/svg@npm:1.0.32" |
733 | checksum: ed3827a61834d92e70354b450212ff8c7a84ddb9ca148bf9bf096d265903da96f835edf1a7d5d83218951105592a42de70e6ba4c7f46013968e3b63a5a5b5185 | ||
734 | languageName: node | ||
735 | linkType: hard | ||
736 | |||
737 | "@mui/base@npm:5.0.0-alpha.93": | ||
738 | version: 5.0.0-alpha.93 | ||
739 | resolution: "@mui/base@npm:5.0.0-alpha.93" | ||
733 | dependencies: | 740 | dependencies: |
734 | "@babel/runtime": ^7.17.2 | 741 | "@babel/runtime": ^7.17.2 |
735 | "@emotion/is-prop-valid": ^1.1.3 | 742 | "@emotion/is-prop-valid": ^1.1.3 |
736 | "@mui/types": ^7.1.5 | 743 | "@mui/types": ^7.1.5 |
737 | "@mui/utils": ^5.9.3 | 744 | "@mui/utils": ^5.9.3 |
738 | "@popperjs/core": ^2.11.5 | 745 | "@popperjs/core": ^2.11.6 |
739 | clsx: ^1.2.1 | 746 | clsx: ^1.2.1 |
740 | prop-types: ^15.8.1 | 747 | prop-types: ^15.8.1 |
741 | react-is: ^18.2.0 | 748 | react-is: ^18.2.0 |
@@ -746,7 +753,14 @@ __metadata: | |||
746 | peerDependenciesMeta: | 753 | peerDependenciesMeta: |
747 | "@types/react": | 754 | "@types/react": |
748 | optional: true | 755 | optional: true |
749 | checksum: ecb4ed58164525125bc17fbfc8e7cbb8270746ab9cce001fada2116b80d8857c42d5ea8e43de6bc313bae2a78ff9e0180e844cde7c868706e023ac4df759d23b | 756 | checksum: 8e04ac3d7e453d8acea73884fea899db4561218ba48644b04a11dfb905dbf630a84847a28ca62bc03d678c8db749e76e150d9cf99d1fbb5ff892e6b734527c29 |
757 | languageName: node | ||
758 | linkType: hard | ||
759 | |||
760 | "@mui/core-downloads-tracker@npm:^5.10.1": | ||
761 | version: 5.10.1 | ||
762 | resolution: "@mui/core-downloads-tracker@npm:5.10.1" | ||
763 | checksum: 275fbabd9beb6d4cbbe5d6cf2a7621fb827bb7ad8be2fdd218ac3b1cc7c2ee9f69cbb71022b8a6f9ecaadbc21225015b33232fc3827dabe8db5c12fbf642cfc9 | ||
750 | languageName: node | 764 | languageName: node |
751 | linkType: hard | 765 | linkType: hard |
752 | 766 | ||
@@ -766,13 +780,14 @@ __metadata: | |||
766 | languageName: node | 780 | languageName: node |
767 | linkType: hard | 781 | linkType: hard |
768 | 782 | ||
769 | "@mui/material@npm:5.10.0": | 783 | "@mui/material@npm:5.10.1": |
770 | version: 5.10.0 | 784 | version: 5.10.1 |
771 | resolution: "@mui/material@npm:5.10.0" | 785 | resolution: "@mui/material@npm:5.10.1" |
772 | dependencies: | 786 | dependencies: |
773 | "@babel/runtime": ^7.17.2 | 787 | "@babel/runtime": ^7.17.2 |
774 | "@mui/base": 5.0.0-alpha.92 | 788 | "@mui/base": 5.0.0-alpha.93 |
775 | "@mui/system": ^5.10.0 | 789 | "@mui/core-downloads-tracker": ^5.10.1 |
790 | "@mui/system": ^5.10.1 | ||
776 | "@mui/types": ^7.1.5 | 791 | "@mui/types": ^7.1.5 |
777 | "@mui/utils": ^5.9.3 | 792 | "@mui/utils": ^5.9.3 |
778 | "@types/react-transition-group": ^4.4.5 | 793 | "@types/react-transition-group": ^4.4.5 |
@@ -794,7 +809,7 @@ __metadata: | |||
794 | optional: true | 809 | optional: true |
795 | "@types/react": | 810 | "@types/react": |
796 | optional: true | 811 | optional: true |
797 | checksum: c31cdefa094ab9f802d60519cf1884e8c5a8f162f6e567d32072d86d4bfa8807a8c1e4fe52614d0853ebdd4da5daccdfdce3d4cf79b8f29e8b67864f1d326a7b | 812 | checksum: 47c8157757df28863f4788f551ddb55e8268a246b2691fe5f0351d83040d874314a3b483d33359824f918abb861e3c6dca91562a5999ebda456c743890e6978a |
798 | languageName: node | 813 | languageName: node |
799 | linkType: hard | 814 | linkType: hard |
800 | 815 | ||
@@ -815,9 +830,9 @@ __metadata: | |||
815 | languageName: node | 830 | languageName: node |
816 | linkType: hard | 831 | linkType: hard |
817 | 832 | ||
818 | "@mui/styled-engine@npm:^5.10.0": | 833 | "@mui/styled-engine@npm:^5.10.1": |
819 | version: 5.10.0 | 834 | version: 5.10.1 |
820 | resolution: "@mui/styled-engine@npm:5.10.0" | 835 | resolution: "@mui/styled-engine@npm:5.10.1" |
821 | dependencies: | 836 | dependencies: |
822 | "@babel/runtime": ^7.17.2 | 837 | "@babel/runtime": ^7.17.2 |
823 | "@emotion/cache": ^11.9.3 | 838 | "@emotion/cache": ^11.9.3 |
@@ -832,17 +847,17 @@ __metadata: | |||
832 | optional: true | 847 | optional: true |
833 | "@emotion/styled": | 848 | "@emotion/styled": |
834 | optional: true | 849 | optional: true |
835 | checksum: 7fc41cdb72e3b660ab3dafbb1e3101f86a9a8c133443291254ad4e158638e1253b4ecd4cd55c48c5c1364ca31dbe93c8495ed125bba9ba305f6d2dd175ff0285 | 850 | checksum: 27c4003bdb1f8a76f30ed6c458789631a93efee832631edeb63940ca3ede70f8aee8e39987b1da1b8717fcbd907d00554d2b5fecc83266eee70c3637c9b66712 |
836 | languageName: node | 851 | languageName: node |
837 | linkType: hard | 852 | linkType: hard |
838 | 853 | ||
839 | "@mui/system@npm:^5.10.0": | 854 | "@mui/system@npm:^5.10.1": |
840 | version: 5.10.0 | 855 | version: 5.10.1 |
841 | resolution: "@mui/system@npm:5.10.0" | 856 | resolution: "@mui/system@npm:5.10.1" |
842 | dependencies: | 857 | dependencies: |
843 | "@babel/runtime": ^7.17.2 | 858 | "@babel/runtime": ^7.17.2 |
844 | "@mui/private-theming": ^5.9.3 | 859 | "@mui/private-theming": ^5.9.3 |
845 | "@mui/styled-engine": ^5.10.0 | 860 | "@mui/styled-engine": ^5.10.1 |
846 | "@mui/types": ^7.1.5 | 861 | "@mui/types": ^7.1.5 |
847 | "@mui/utils": ^5.9.3 | 862 | "@mui/utils": ^5.9.3 |
848 | clsx: ^1.2.1 | 863 | clsx: ^1.2.1 |
@@ -860,7 +875,7 @@ __metadata: | |||
860 | optional: true | 875 | optional: true |
861 | "@types/react": | 876 | "@types/react": |
862 | optional: true | 877 | optional: true |
863 | checksum: be86853ca79ed8a889d37fa02780c0dfe477ff5ffda4cabba8bf5865186b578cbd50646ea902c3f5712baf83d4b11fbd21428f643b42d1839d671cbedddfc546 | 878 | checksum: 0acb163dec856af3813ff043b4d8441d44431add7cb234cf16d386b2b70e30cba6261b51ac936f886d746f4b3ab3475b58e33e09f76cd92a6f6aadd61dca3927 |
864 | languageName: node | 879 | languageName: node |
865 | linkType: hard | 880 | linkType: hard |
866 | 881 | ||
@@ -952,10 +967,10 @@ __metadata: | |||
952 | languageName: node | 967 | languageName: node |
953 | linkType: hard | 968 | linkType: hard |
954 | 969 | ||
955 | "@popperjs/core@npm:^2.11.5": | 970 | "@popperjs/core@npm:^2.11.6": |
956 | version: 2.11.5 | 971 | version: 2.11.6 |
957 | resolution: "@popperjs/core@npm:2.11.5" | 972 | resolution: "@popperjs/core@npm:2.11.6" |
958 | checksum: fd7f9dca3fb716d7426332b6ee283f88d2724c0ab342fb678865a640bad403dfb9eeebd8204a406986162f7e2b33394f104320008b74d0e9066d7322f70ea35d | 973 | checksum: 47fb328cec1924559d759b48235c78574f2d71a8a6c4c03edb6de5d7074078371633b91e39bbf3f901b32aa8af9b9d8f82834856d2f5737a23475036b16817f0 |
959 | languageName: node | 974 | languageName: node |
960 | linkType: hard | 975 | linkType: hard |
961 | 976 | ||
@@ -967,7 +982,7 @@ __metadata: | |||
967 | "@codemirror/commands": ^6.0.1 | 982 | "@codemirror/commands": ^6.0.1 |
968 | "@codemirror/language": ^6.2.1 | 983 | "@codemirror/language": ^6.2.1 |
969 | "@codemirror/lint": ^6.0.0 | 984 | "@codemirror/lint": ^6.0.0 |
970 | "@codemirror/search": ^6.0.1 | 985 | "@codemirror/search": ^6.1.0 |
971 | "@codemirror/state": ^6.1.1 | 986 | "@codemirror/state": ^6.1.1 |
972 | "@codemirror/view": ^6.2.0 | 987 | "@codemirror/view": ^6.2.0 |
973 | "@emotion/react": ^11.10.0 | 988 | "@emotion/react": ^11.10.0 |
@@ -978,16 +993,17 @@ __metadata: | |||
978 | "@lezer/common": ^1.0.0 | 993 | "@lezer/common": ^1.0.0 |
979 | "@lezer/generator": ^1.1.1 | 994 | "@lezer/generator": ^1.1.1 |
980 | "@lezer/highlight": ^1.0.0 | 995 | "@lezer/highlight": ^1.0.0 |
981 | "@lezer/lr": ^1.2.2 | 996 | "@lezer/lr": ^1.2.3 |
997 | "@material-icons/svg": ^1.0.32 | ||
982 | "@mui/icons-material": 5.8.4 | 998 | "@mui/icons-material": 5.8.4 |
983 | "@mui/material": 5.10.0 | 999 | "@mui/material": 5.10.1 |
984 | "@types/eslint": ^8.4.5 | 1000 | "@types/eslint": ^8.4.5 |
985 | "@types/node": ^18.7.4 | 1001 | "@types/node": ^18.7.6 |
986 | "@types/prettier": ^2.7.0 | 1002 | "@types/prettier": ^2.7.0 |
987 | "@types/react": ^18.0.17 | 1003 | "@types/react": ^18.0.17 |
988 | "@types/react-dom": ^18.0.6 | 1004 | "@types/react-dom": ^18.0.6 |
989 | "@typescript-eslint/eslint-plugin": ^5.33.0 | 1005 | "@typescript-eslint/eslint-plugin": ^5.33.1 |
990 | "@typescript-eslint/parser": ^5.33.0 | 1006 | "@typescript-eslint/parser": ^5.33.1 |
991 | "@vitejs/plugin-react": ^2.0.1 | 1007 | "@vitejs/plugin-react": ^2.0.1 |
992 | ansi-styles: ^6.1.0 | 1008 | ansi-styles: ^6.1.0 |
993 | cross-env: ^7.0.3 | 1009 | cross-env: ^7.0.3 |
@@ -1011,7 +1027,7 @@ __metadata: | |||
1011 | react: ^18.2.0 | 1027 | react: ^18.2.0 |
1012 | react-dom: ^18.2.0 | 1028 | react-dom: ^18.2.0 |
1013 | typescript: ~4.7.4 | 1029 | typescript: ~4.7.4 |
1014 | vite: ^3.0.7 | 1030 | vite: ^3.0.8 |
1015 | vite-plugin-inject-preload: ^1.0.1 | 1031 | vite-plugin-inject-preload: ^1.0.1 |
1016 | zod: ^3.18.0 | 1032 | zod: ^3.18.0 |
1017 | languageName: unknown | 1033 | languageName: unknown |
@@ -1064,10 +1080,10 @@ __metadata: | |||
1064 | languageName: node | 1080 | languageName: node |
1065 | linkType: hard | 1081 | linkType: hard |
1066 | 1082 | ||
1067 | "@types/node@npm:^18.7.4": | 1083 | "@types/node@npm:^18.7.6": |
1068 | version: 18.7.4 | 1084 | version: 18.7.6 |
1069 | resolution: "@types/node@npm:18.7.4" | 1085 | resolution: "@types/node@npm:18.7.6" |
1070 | checksum: 051d2147e4d8129fceb63ee9384259b2f224dbc4e4b0c46d96a6b61cbaad4e3fe4060950e7f4fc3d5692b1e6ea47e68ad03b61155754bfa169593747cfe3f8f4 | 1086 | checksum: 5122988c325eda8d1f5cbe4494916036aae1758f9d5bb2d8139a800b8bad1540fbb167cd3c759da9a5cb4600cd3507609ac7969747113c1549a3e4320a17b1a9 |
1071 | languageName: node | 1087 | languageName: node |
1072 | linkType: hard | 1088 | linkType: hard |
1073 | 1089 | ||
@@ -1137,13 +1153,13 @@ __metadata: | |||
1137 | languageName: node | 1153 | languageName: node |
1138 | linkType: hard | 1154 | linkType: hard |
1139 | 1155 | ||
1140 | "@typescript-eslint/eslint-plugin@npm:^5.33.0": | 1156 | "@typescript-eslint/eslint-plugin@npm:^5.33.1": |
1141 | version: 5.33.0 | 1157 | version: 5.33.1 |
1142 | resolution: "@typescript-eslint/eslint-plugin@npm:5.33.0" | 1158 | resolution: "@typescript-eslint/eslint-plugin@npm:5.33.1" |
1143 | dependencies: | 1159 | dependencies: |
1144 | "@typescript-eslint/scope-manager": 5.33.0 | 1160 | "@typescript-eslint/scope-manager": 5.33.1 |
1145 | "@typescript-eslint/type-utils": 5.33.0 | 1161 | "@typescript-eslint/type-utils": 5.33.1 |
1146 | "@typescript-eslint/utils": 5.33.0 | 1162 | "@typescript-eslint/utils": 5.33.1 |
1147 | debug: ^4.3.4 | 1163 | debug: ^4.3.4 |
1148 | functional-red-black-tree: ^1.0.1 | 1164 | functional-red-black-tree: ^1.0.1 |
1149 | ignore: ^5.2.0 | 1165 | ignore: ^5.2.0 |
@@ -1156,42 +1172,42 @@ __metadata: | |||
1156 | peerDependenciesMeta: | 1172 | peerDependenciesMeta: |
1157 | typescript: | 1173 | typescript: |
1158 | optional: true | 1174 | optional: true |
1159 | checksum: d408f3f474b34fefde8ee65d98deb126949fd7d8e211a7f95c5cc2b507dedbf8eb239f3895e0c37aa6338989531e37c5f35c2e0de36a126c52f0846e89605487 | 1175 | checksum: d9b6b038f70e4959ad211c84f50a38de2d00b54f0636ad76eea414fb070fa616933690da80de6668e62c8fbbeb227086322001b7d7ad1924421a232547c97936 |
1160 | languageName: node | 1176 | languageName: node |
1161 | linkType: hard | 1177 | linkType: hard |
1162 | 1178 | ||
1163 | "@typescript-eslint/parser@npm:^5.33.0": | 1179 | "@typescript-eslint/parser@npm:^5.33.1": |
1164 | version: 5.33.0 | 1180 | version: 5.33.1 |
1165 | resolution: "@typescript-eslint/parser@npm:5.33.0" | 1181 | resolution: "@typescript-eslint/parser@npm:5.33.1" |
1166 | dependencies: | 1182 | dependencies: |
1167 | "@typescript-eslint/scope-manager": 5.33.0 | 1183 | "@typescript-eslint/scope-manager": 5.33.1 |
1168 | "@typescript-eslint/types": 5.33.0 | 1184 | "@typescript-eslint/types": 5.33.1 |
1169 | "@typescript-eslint/typescript-estree": 5.33.0 | 1185 | "@typescript-eslint/typescript-estree": 5.33.1 |
1170 | debug: ^4.3.4 | 1186 | debug: ^4.3.4 |
1171 | peerDependencies: | 1187 | peerDependencies: |
1172 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 | 1188 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 |
1173 | peerDependenciesMeta: | 1189 | peerDependenciesMeta: |
1174 | typescript: | 1190 | typescript: |
1175 | optional: true | 1191 | optional: true |
1176 | checksum: 2617aba987a70ee6b16ecc6afa6d245422df33a9d056018ff2e316159e667a0ab9d9c15fcea95e0ba65832661e71cc2753a221e77f0b0fab278e52c4497b8278 | 1192 | checksum: fb3a4e000ce6d9583656fc3b3fb80f127a0ec1b7c3872ea469164516d993a588859ded4ec1338e6bbf2151168380d8aa29ec31027af23b50f5107949f8e7b438 |
1177 | languageName: node | 1193 | languageName: node |
1178 | linkType: hard | 1194 | linkType: hard |
1179 | 1195 | ||
1180 | "@typescript-eslint/scope-manager@npm:5.33.0": | 1196 | "@typescript-eslint/scope-manager@npm:5.33.1": |
1181 | version: 5.33.0 | 1197 | version: 5.33.1 |
1182 | resolution: "@typescript-eslint/scope-manager@npm:5.33.0" | 1198 | resolution: "@typescript-eslint/scope-manager@npm:5.33.1" |
1183 | dependencies: | 1199 | dependencies: |
1184 | "@typescript-eslint/types": 5.33.0 | 1200 | "@typescript-eslint/types": 5.33.1 |
1185 | "@typescript-eslint/visitor-keys": 5.33.0 | 1201 | "@typescript-eslint/visitor-keys": 5.33.1 |
1186 | checksum: b2cbea9abd528d01a5acb2d68a2a5be51ec6827760d3869bdd70920cf6c3a4f9f96d87c77177f8313009d9db71253e4a75f8393f38651e2abaf91ef28e60fb9d | 1202 | checksum: b9918d8320ea59081d19070ce952b56984e72fb2c113215e5e6a0f97deac9aae5aa67ec7a07cddb010c0f75cdf8df096ab45e9241e4b7b611acfa6d4cdfb6516 |
1187 | languageName: node | 1203 | languageName: node |
1188 | linkType: hard | 1204 | linkType: hard |
1189 | 1205 | ||
1190 | "@typescript-eslint/type-utils@npm:5.33.0": | 1206 | "@typescript-eslint/type-utils@npm:5.33.1": |
1191 | version: 5.33.0 | 1207 | version: 5.33.1 |
1192 | resolution: "@typescript-eslint/type-utils@npm:5.33.0" | 1208 | resolution: "@typescript-eslint/type-utils@npm:5.33.1" |
1193 | dependencies: | 1209 | dependencies: |
1194 | "@typescript-eslint/utils": 5.33.0 | 1210 | "@typescript-eslint/utils": 5.33.1 |
1195 | debug: ^4.3.4 | 1211 | debug: ^4.3.4 |
1196 | tsutils: ^3.21.0 | 1212 | tsutils: ^3.21.0 |
1197 | peerDependencies: | 1213 | peerDependencies: |
@@ -1199,23 +1215,23 @@ __metadata: | |||
1199 | peerDependenciesMeta: | 1215 | peerDependenciesMeta: |
1200 | typescript: | 1216 | typescript: |
1201 | optional: true | 1217 | optional: true |
1202 | checksum: a1d1ffb42fe96bfc2339cc2875e218aa82fa9391be04c1a266bb11da1eca6835555687e81cde75477c60e6702049cd4dde7d2638e7e9b9d8cf4b7b2242353a6e | 1218 | checksum: ddf88835bc87b3ad946aaeb29b770a49a8e1c3c5e294ee9cb93b1936f432a1016efb97803f197eea1be61545cbc79b5526cc05e9339ca9beada22fc83801ddea |
1203 | languageName: node | 1219 | languageName: node |
1204 | linkType: hard | 1220 | linkType: hard |
1205 | 1221 | ||
1206 | "@typescript-eslint/types@npm:5.33.0": | 1222 | "@typescript-eslint/types@npm:5.33.1": |
1207 | version: 5.33.0 | 1223 | version: 5.33.1 |
1208 | resolution: "@typescript-eslint/types@npm:5.33.0" | 1224 | resolution: "@typescript-eslint/types@npm:5.33.1" |
1209 | checksum: 8bbddda84cb3adf5c659b0d42547a2d6ab87f4eea574aca5dd63a3bd85169f32796ecbddad3b27f18a609070f6b1d18a54018d488bad746ae0f6ea5c02206109 | 1225 | checksum: 122891bd4ab4b930b1d33f3ce43a010825c1e61b9879520a0f3dc34cf92df71e2a873410845ab8d746333511c455c115eaafdec149298a161cef713829dfdb77 |
1210 | languageName: node | 1226 | languageName: node |
1211 | linkType: hard | 1227 | linkType: hard |
1212 | 1228 | ||
1213 | "@typescript-eslint/typescript-estree@npm:5.33.0": | 1229 | "@typescript-eslint/typescript-estree@npm:5.33.1": |
1214 | version: 5.33.0 | 1230 | version: 5.33.1 |
1215 | resolution: "@typescript-eslint/typescript-estree@npm:5.33.0" | 1231 | resolution: "@typescript-eslint/typescript-estree@npm:5.33.1" |
1216 | dependencies: | 1232 | dependencies: |
1217 | "@typescript-eslint/types": 5.33.0 | 1233 | "@typescript-eslint/types": 5.33.1 |
1218 | "@typescript-eslint/visitor-keys": 5.33.0 | 1234 | "@typescript-eslint/visitor-keys": 5.33.1 |
1219 | debug: ^4.3.4 | 1235 | debug: ^4.3.4 |
1220 | globby: ^11.1.0 | 1236 | globby: ^11.1.0 |
1221 | is-glob: ^4.0.3 | 1237 | is-glob: ^4.0.3 |
@@ -1224,33 +1240,33 @@ __metadata: | |||
1224 | peerDependenciesMeta: | 1240 | peerDependenciesMeta: |
1225 | typescript: | 1241 | typescript: |
1226 | optional: true | 1242 | optional: true |
1227 | checksum: 26f9005cdfb14654125a33d90d872b926820e560dff8970c4629fd5f6f47ad2a31e4c63161564d21bb42a8fc3ced0033994854ee37336ae07d90ccf6300d702b | 1243 | checksum: 1418e409b141c2f012bc2dd5c40d95dfd8aa572dd3e9523ed23e4371e4459d10ecd074fda75dc770ce980686b25ffc44725eebf165c494818ed4131d1ac0239f |
1228 | languageName: node | 1244 | languageName: node |
1229 | linkType: hard | 1245 | linkType: hard |
1230 | 1246 | ||
1231 | "@typescript-eslint/utils@npm:5.33.0": | 1247 | "@typescript-eslint/utils@npm:5.33.1": |
1232 | version: 5.33.0 | 1248 | version: 5.33.1 |
1233 | resolution: "@typescript-eslint/utils@npm:5.33.0" | 1249 | resolution: "@typescript-eslint/utils@npm:5.33.1" |
1234 | dependencies: | 1250 | dependencies: |
1235 | "@types/json-schema": ^7.0.9 | 1251 | "@types/json-schema": ^7.0.9 |
1236 | "@typescript-eslint/scope-manager": 5.33.0 | 1252 | "@typescript-eslint/scope-manager": 5.33.1 |
1237 | "@typescript-eslint/types": 5.33.0 | 1253 | "@typescript-eslint/types": 5.33.1 |
1238 | "@typescript-eslint/typescript-estree": 5.33.0 | 1254 | "@typescript-eslint/typescript-estree": 5.33.1 |
1239 | eslint-scope: ^5.1.1 | 1255 | eslint-scope: ^5.1.1 |
1240 | eslint-utils: ^3.0.0 | 1256 | eslint-utils: ^3.0.0 |
1241 | peerDependencies: | 1257 | peerDependencies: |
1242 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 | 1258 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 |
1243 | checksum: 6ce5ee5eabeb6d73538b24e6487f811ecb0ef3467bd366cbd15bf30d904bdedb73fc6f48cf2e2e742dda462b42999ea505e8b59255545825ec9db86f3d423ea7 | 1259 | checksum: c550504d62fc72f29bf3d7a651bd3a81f49fb1fccaf47583721c2ab1abd2ef78a1e4bc392cb4be4a61a45a4f24fc14a59d67b98aac8a16a207a7cace86538cab |
1244 | languageName: node | 1260 | languageName: node |
1245 | linkType: hard | 1261 | linkType: hard |
1246 | 1262 | ||
1247 | "@typescript-eslint/visitor-keys@npm:5.33.0": | 1263 | "@typescript-eslint/visitor-keys@npm:5.33.1": |
1248 | version: 5.33.0 | 1264 | version: 5.33.1 |
1249 | resolution: "@typescript-eslint/visitor-keys@npm:5.33.0" | 1265 | resolution: "@typescript-eslint/visitor-keys@npm:5.33.1" |
1250 | dependencies: | 1266 | dependencies: |
1251 | "@typescript-eslint/types": 5.33.0 | 1267 | "@typescript-eslint/types": 5.33.1 |
1252 | eslint-visitor-keys: ^3.3.0 | 1268 | eslint-visitor-keys: ^3.3.0 |
1253 | checksum: d7e3653de6bac6841e6fcc54226b93ad6bdca4aa76ebe7d83459c016c3eebcc50d4f65ee713174bc267d765295b642d1927a778c5de707b8389e3fcc052aa4a1 | 1269 | checksum: 0d32a433450f61e97b5fa6b1e167f06ed395c200b16b4dbd4490a1c4941de420689b622f8a2486f5398806fb24f57b9fab901b4cbc8fdb8853f568264b3a182a |
1254 | languageName: node | 1270 | languageName: node |
1255 | linkType: hard | 1271 | linkType: hard |
1256 | 1272 | ||
@@ -4705,9 +4721,9 @@ __metadata: | |||
4705 | languageName: node | 4721 | languageName: node |
4706 | linkType: hard | 4722 | linkType: hard |
4707 | 4723 | ||
4708 | "vite@npm:^3.0.7": | 4724 | "vite@npm:^3.0.8": |
4709 | version: 3.0.7 | 4725 | version: 3.0.8 |
4710 | resolution: "vite@npm:3.0.7" | 4726 | resolution: "vite@npm:3.0.8" |
4711 | dependencies: | 4727 | dependencies: |
4712 | esbuild: ^0.14.47 | 4728 | esbuild: ^0.14.47 |
4713 | fsevents: ~2.3.2 | 4729 | fsevents: ~2.3.2 |
@@ -4733,7 +4749,7 @@ __metadata: | |||
4733 | optional: true | 4749 | optional: true |
4734 | bin: | 4750 | bin: |
4735 | vite: bin/vite.js | 4751 | vite: bin/vite.js |
4736 | checksum: 3cdcb68e16433b9addee61c117c379692e4d12d44ee7011da99ed9516b8c37ce76f1bb1728eab0e3a28957c9d2dc839024e1607c2ad03f38089e3be55d7dd456 | 4752 | checksum: ec3f57d52f2bf28f2f89898053c2156f025a108a95e9308ce6580f43d8fdaae866f7988afa8207a8c8509069d3a0b50ee79b9a8050590a825f4b7771646c2755 |
4737 | languageName: node | 4753 | languageName: node |
4738 | linkType: hard | 4754 | linkType: hard |
4739 | 4755 | ||