diff options
Diffstat (limited to 'language-web/src/main/js')
-rw-r--r-- | language-web/src/main/js/App.tsx | 6 | ||||
-rw-r--r-- | language-web/src/main/js/editor/Editor.tsx | 20 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorArea.tsx | 42 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorStore.ts | 75 | ||||
-rw-r--r-- | language-web/src/main/js/editor/editor.ts | 18 | ||||
-rw-r--r-- | language-web/src/main/js/theme/ThemeStore.ts | 4 |
6 files changed, 127 insertions, 38 deletions
diff --git a/language-web/src/main/js/App.tsx b/language-web/src/main/js/App.tsx index 17d4f339..5cd157fa 100644 --- a/language-web/src/main/js/App.tsx +++ b/language-web/src/main/js/App.tsx | |||
@@ -9,12 +9,12 @@ import MenuIcon from '@material-ui/icons/Menu'; | |||
9 | import PlayArrowIcon from '@material-ui/icons/PlayArrow'; | 9 | import PlayArrowIcon from '@material-ui/icons/PlayArrow'; |
10 | 10 | ||
11 | import { makeStyles } from './makeStyles'; | 11 | import { makeStyles } from './makeStyles'; |
12 | import { Editor } from './editor/Editor'; | 12 | import { EditorArea } from './editor/EditorArea'; |
13 | import { EditorButtons } from './editor/EditorButtons'; | 13 | import { EditorButtons } from './editor/EditorButtons'; |
14 | 14 | ||
15 | const useStyles = makeStyles()((theme) => ({ | 15 | const useStyles = makeStyles()((theme) => ({ |
16 | container: { | 16 | container: { |
17 | maxHeight: '100vh', | 17 | height: '100vh', |
18 | }, | 18 | }, |
19 | menuButton: { | 19 | menuButton: { |
20 | marginRight: theme.spacing(2), | 20 | marginRight: theme.spacing(2), |
@@ -85,7 +85,7 @@ export const App = (): JSX.Element => { | |||
85 | flexShrink={1} | 85 | flexShrink={1} |
86 | className={cx(classes.editorBox)} | 86 | className={cx(classes.editorBox)} |
87 | > | 87 | > |
88 | <Editor /> | 88 | <EditorArea /> |
89 | </Box> | 89 | </Box> |
90 | </Box> | 90 | </Box> |
91 | ); | 91 | ); |
diff --git a/language-web/src/main/js/editor/Editor.tsx b/language-web/src/main/js/editor/Editor.tsx deleted file mode 100644 index 9badb6a3..00000000 --- a/language-web/src/main/js/editor/Editor.tsx +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | ||
2 | import React from 'react'; | ||
3 | import { Controlled as CodeMirror } from 'react-codemirror2'; | ||
4 | |||
5 | import { useRootStore } from '../RootStore'; | ||
6 | |||
7 | export const Editor = observer(() => { | ||
8 | const { editorStore } = useRootStore(); | ||
9 | |||
10 | return ( | ||
11 | <CodeMirror | ||
12 | value={editorStore.value} | ||
13 | options={editorStore.codeMirrorOptions} | ||
14 | editorDidMount={(editor) => editorStore.editorDidMount(editor)} | ||
15 | editorWillUnmount={() => editorStore.editorWillUnmount()} | ||
16 | onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} | ||
17 | onChange={() => editorStore.reportChanged()} | ||
18 | /> | ||
19 | ); | ||
20 | }); | ||
diff --git a/language-web/src/main/js/editor/EditorArea.tsx b/language-web/src/main/js/editor/EditorArea.tsx new file mode 100644 index 00000000..f07a0ad8 --- /dev/null +++ b/language-web/src/main/js/editor/EditorArea.tsx | |||
@@ -0,0 +1,42 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | ||
2 | import React, { useRef } from 'react'; | ||
3 | |||
4 | import { useRootStore } from '../RootStore'; | ||
5 | |||
6 | export const EditorArea = observer(() => { | ||
7 | const { editorStore } = useRootStore(); | ||
8 | const { CodeMirror } = editorStore.chunk || {}; | ||
9 | const fallbackTextarea = useRef<HTMLTextAreaElement>(null); | ||
10 | |||
11 | if (!CodeMirror) { | ||
12 | return ( | ||
13 | <textarea | ||
14 | value={editorStore.value} | ||
15 | onChange={(e) => editorStore.updateValue(e.target.value)} | ||
16 | ref={fallbackTextarea} | ||
17 | className={`problem-fallback-editor cm-s-${editorStore.codeMirrorTheme}`} | ||
18 | > | ||
19 | </textarea> | ||
20 | ); | ||
21 | } | ||
22 | |||
23 | const textarea = fallbackTextarea.current; | ||
24 | if (textarea) { | ||
25 | editorStore.setInitialSelection( | ||
26 | textarea.selectionStart, | ||
27 | textarea.selectionEnd, | ||
28 | document.activeElement === textarea, | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | return ( | ||
33 | <CodeMirror | ||
34 | value={editorStore.value} | ||
35 | options={editorStore.codeMirrorOptions} | ||
36 | editorDidMount={(editor) => editorStore.editorDidMount(editor)} | ||
37 | editorWillUnmount={() => editorStore.editorWillUnmount()} | ||
38 | onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} | ||
39 | onChange={() => editorStore.reportChanged()} | ||
40 | /> | ||
41 | ); | ||
42 | }); | ||
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts index 5da45ac1..1ac2e79f 100644 --- a/language-web/src/main/js/editor/EditorStore.ts +++ b/language-web/src/main/js/editor/EditorStore.ts | |||
@@ -1,19 +1,14 @@ | |||
1 | import { Editor, EditorConfiguration } from 'codemirror'; | 1 | import type { Editor, EditorConfiguration } from 'codemirror'; |
2 | import 'codemirror/addon/selection/active-line'; | ||
3 | import { | 2 | import { |
4 | createAtom, | 3 | createAtom, |
5 | makeAutoObservable, | 4 | makeAutoObservable, |
6 | observable, | 5 | observable, |
6 | runInAction, | ||
7 | } from 'mobx'; | 7 | } from 'mobx'; |
8 | import 'mode-problem'; | 8 | import type { IXtextOptions, IXtextServices } from 'xtext/xtext-codemirror'; |
9 | import { | ||
10 | IXtextOptions, | ||
11 | IXtextServices, | ||
12 | createServices, | ||
13 | removeServices, | ||
14 | } from 'xtext/xtext-codemirror'; | ||
15 | 9 | ||
16 | import { ThemeStore } from '../theme/ThemeStore'; | 10 | import type { IEditorChunk } from './editor'; |
11 | import type { ThemeStore } from '../theme/ThemeStore'; | ||
17 | 12 | ||
18 | const xtextLang = 'problem'; | 13 | const xtextLang = 'problem'; |
19 | 14 | ||
@@ -33,6 +28,8 @@ export class EditorStore { | |||
33 | 28 | ||
34 | atom; | 29 | atom; |
35 | 30 | ||
31 | chunk?: IEditorChunk; | ||
32 | |||
36 | editor?: Editor; | 33 | editor?: Editor; |
37 | 34 | ||
38 | xtextServices?: IXtextServices; | 35 | xtextServices?: IXtextServices; |
@@ -41,15 +38,56 @@ export class EditorStore { | |||
41 | 38 | ||
42 | showLineNumbers = false; | 39 | showLineNumbers = false; |
43 | 40 | ||
41 | initialSelection!: { start: number, end: number, focused: boolean }; | ||
42 | |||
44 | constructor(themeStore: ThemeStore) { | 43 | constructor(themeStore: ThemeStore) { |
45 | this.themeStore = themeStore; | 44 | this.themeStore = themeStore; |
46 | this.atom = createAtom('EditorStore'); | 45 | this.atom = createAtom('EditorStore'); |
46 | this.resetInitialSelection(); | ||
47 | makeAutoObservable(this, { | 47 | makeAutoObservable(this, { |
48 | themeStore: false, | 48 | themeStore: false, |
49 | atom: false, | 49 | atom: false, |
50 | chunk: observable.ref, | ||
50 | editor: observable.ref, | 51 | editor: observable.ref, |
51 | xtextServices: observable.ref, | 52 | xtextServices: observable.ref, |
53 | initialSelection: false, | ||
52 | }); | 54 | }); |
55 | import('./editor').then(({ editorChunk }) => { | ||
56 | runInAction(() => { | ||
57 | this.chunk = editorChunk; | ||
58 | }); | ||
59 | }).catch((error) => { | ||
60 | console.warn('Error while loading editor', error); | ||
61 | }); | ||
62 | } | ||
63 | |||
64 | setInitialSelection(start: number, end: number, focused: boolean): void { | ||
65 | this.initialSelection = { start, end, focused }; | ||
66 | this.applyInitialSelectionToEditor(); | ||
67 | } | ||
68 | |||
69 | private resetInitialSelection(): void { | ||
70 | this.initialSelection = { | ||
71 | start: 0, | ||
72 | end: 0, | ||
73 | focused: false, | ||
74 | }; | ||
75 | } | ||
76 | |||
77 | private applyInitialSelectionToEditor(): void { | ||
78 | if (this.editor) { | ||
79 | const { start, end, focused } = this.initialSelection; | ||
80 | const doc = this.editor.getDoc(); | ||
81 | const startPos = doc.posFromIndex(start); | ||
82 | const endPos = doc.posFromIndex(end); | ||
83 | doc.setSelection(startPos, endPos, { | ||
84 | scroll: true, | ||
85 | }); | ||
86 | if (focused) { | ||
87 | this.editor.focus(); | ||
88 | } | ||
89 | this.resetInitialSelection(); | ||
90 | } | ||
53 | } | 91 | } |
54 | 92 | ||
55 | /** | 93 | /** |
@@ -61,16 +99,23 @@ export class EditorStore { | |||
61 | * @param newEditor The new CodeMirror instance | 99 | * @param newEditor The new CodeMirror instance |
62 | */ | 100 | */ |
63 | editorDidMount(newEditor: Editor): void { | 101 | editorDidMount(newEditor: Editor): void { |
102 | if (!this.chunk) { | ||
103 | throw new Error('Editor not loaded yet'); | ||
104 | } | ||
64 | if (this.editor) { | 105 | if (this.editor) { |
65 | throw new Error('CoreMirror editor mounted before unmounting'); | 106 | throw new Error('CoreMirror editor mounted before unmounting'); |
66 | } | 107 | } |
67 | this.editor = newEditor; | 108 | this.editor = newEditor; |
68 | this.xtextServices = createServices(newEditor, xtextOptions); | 109 | this.xtextServices = this.chunk.createServices(newEditor, xtextOptions); |
110 | this.applyInitialSelectionToEditor(); | ||
69 | } | 111 | } |
70 | 112 | ||
71 | editorWillUnmount(): void { | 113 | editorWillUnmount(): void { |
114 | if (!this.chunk) { | ||
115 | throw new Error('Editor not loaded yet'); | ||
116 | } | ||
72 | if (this.editor) { | 117 | if (this.editor) { |
73 | removeServices(this.editor); | 118 | this.chunk.removeServices(this.editor); |
74 | } | 119 | } |
75 | delete this.editor; | 120 | delete this.editor; |
76 | delete this.xtextServices; | 121 | delete this.xtextServices; |
@@ -93,10 +138,14 @@ export class EditorStore { | |||
93 | this.atom.reportObserved(); | 138 | this.atom.reportObserved(); |
94 | } | 139 | } |
95 | 140 | ||
141 | get codeMirrorTheme(): string { | ||
142 | return `problem-${this.themeStore.className}`; | ||
143 | } | ||
144 | |||
96 | get codeMirrorOptions(): EditorConfiguration { | 145 | get codeMirrorOptions(): EditorConfiguration { |
97 | return { | 146 | return { |
98 | ...codeMirrorGlobalOptions, | 147 | ...codeMirrorGlobalOptions, |
99 | theme: this.themeStore.codeMirrorTheme, | 148 | theme: this.codeMirrorTheme, |
100 | lineNumbers: this.showLineNumbers, | 149 | lineNumbers: this.showLineNumbers, |
101 | }; | 150 | }; |
102 | } | 151 | } |
diff --git a/language-web/src/main/js/editor/editor.ts b/language-web/src/main/js/editor/editor.ts new file mode 100644 index 00000000..fbf8796b --- /dev/null +++ b/language-web/src/main/js/editor/editor.ts | |||
@@ -0,0 +1,18 @@ | |||
1 | import 'codemirror/addon/selection/active-line'; | ||
2 | import 'mode-problem'; | ||
3 | import { Controlled } from 'react-codemirror2'; | ||
4 | import { createServices, removeServices } from 'xtext/xtext-codemirror'; | ||
5 | |||
6 | export interface IEditorChunk { | ||
7 | CodeMirror: typeof Controlled; | ||
8 | |||
9 | createServices: typeof createServices; | ||
10 | |||
11 | removeServices: typeof removeServices; | ||
12 | } | ||
13 | |||
14 | export const editorChunk: IEditorChunk = { | ||
15 | CodeMirror: Controlled, | ||
16 | createServices, | ||
17 | removeServices, | ||
18 | }; | ||
diff --git a/language-web/src/main/js/theme/ThemeStore.ts b/language-web/src/main/js/theme/ThemeStore.ts index 0e4aeb23..2644a96a 100644 --- a/language-web/src/main/js/theme/ThemeStore.ts +++ b/language-web/src/main/js/theme/ThemeStore.ts | |||
@@ -51,7 +51,7 @@ export class ThemeStore { | |||
51 | return responsiveFontSizes(materialUiTheme); | 51 | return responsiveFontSizes(materialUiTheme); |
52 | } | 52 | } |
53 | 53 | ||
54 | get codeMirrorTheme(): string { | 54 | get className(): string { |
55 | return `problem-${this.currentThemeData.className}`; | 55 | return this.currentThemeData.className; |
56 | } | 56 | } |
57 | } | 57 | } |