From b834db0fd424e7ab02fcd5e509d855f2d97863bd Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 2 Oct 2021 02:11:31 +0200 Subject: perf(web): split off CodeMirror chunks Also optimizes statis asset caching. --- language-web/src/main/js/App.tsx | 6 +-- language-web/src/main/js/editor/Editor.tsx | 20 ------- language-web/src/main/js/editor/EditorArea.tsx | 42 +++++++++++++++ language-web/src/main/js/editor/EditorStore.ts | 75 +++++++++++++++++++++----- language-web/src/main/js/editor/editor.ts | 18 +++++++ language-web/src/main/js/theme/ThemeStore.ts | 4 +- 6 files changed, 127 insertions(+), 38 deletions(-) delete mode 100644 language-web/src/main/js/editor/Editor.tsx create mode 100644 language-web/src/main/js/editor/EditorArea.tsx create mode 100644 language-web/src/main/js/editor/editor.ts (limited to 'language-web/src/main/js') 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'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; import { makeStyles } from './makeStyles'; -import { Editor } from './editor/Editor'; +import { EditorArea } from './editor/EditorArea'; import { EditorButtons } from './editor/EditorButtons'; const useStyles = makeStyles()((theme) => ({ container: { - maxHeight: '100vh', + height: '100vh', }, menuButton: { marginRight: theme.spacing(2), @@ -85,7 +85,7 @@ export const App = (): JSX.Element => { flexShrink={1} className={cx(classes.editorBox)} > - + ); 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 @@ -import { observer } from 'mobx-react-lite'; -import React from 'react'; -import { Controlled as CodeMirror } from 'react-codemirror2'; - -import { useRootStore } from '../RootStore'; - -export const Editor = observer(() => { - const { editorStore } = useRootStore(); - - return ( - editorStore.editorDidMount(editor)} - editorWillUnmount={() => editorStore.editorWillUnmount()} - onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} - onChange={() => editorStore.reportChanged()} - /> - ); -}); 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 @@ +import { observer } from 'mobx-react-lite'; +import React, { useRef } from 'react'; + +import { useRootStore } from '../RootStore'; + +export const EditorArea = observer(() => { + const { editorStore } = useRootStore(); + const { CodeMirror } = editorStore.chunk || {}; + const fallbackTextarea = useRef(null); + + if (!CodeMirror) { + return ( + + ); + } + + const textarea = fallbackTextarea.current; + if (textarea) { + editorStore.setInitialSelection( + textarea.selectionStart, + textarea.selectionEnd, + document.activeElement === textarea, + ); + } + + return ( + editorStore.editorDidMount(editor)} + editorWillUnmount={() => editorStore.editorWillUnmount()} + onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} + onChange={() => editorStore.reportChanged()} + /> + ); +}); 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 @@ -import { Editor, EditorConfiguration } from 'codemirror'; -import 'codemirror/addon/selection/active-line'; +import type { Editor, EditorConfiguration } from 'codemirror'; import { createAtom, makeAutoObservable, observable, + runInAction, } from 'mobx'; -import 'mode-problem'; -import { - IXtextOptions, - IXtextServices, - createServices, - removeServices, -} from 'xtext/xtext-codemirror'; +import type { IXtextOptions, IXtextServices } from 'xtext/xtext-codemirror'; -import { ThemeStore } from '../theme/ThemeStore'; +import type { IEditorChunk } from './editor'; +import type { ThemeStore } from '../theme/ThemeStore'; const xtextLang = 'problem'; @@ -33,6 +28,8 @@ export class EditorStore { atom; + chunk?: IEditorChunk; + editor?: Editor; xtextServices?: IXtextServices; @@ -41,15 +38,56 @@ export class EditorStore { showLineNumbers = false; + initialSelection!: { start: number, end: number, focused: boolean }; + constructor(themeStore: ThemeStore) { this.themeStore = themeStore; this.atom = createAtom('EditorStore'); + this.resetInitialSelection(); makeAutoObservable(this, { themeStore: false, atom: false, + chunk: observable.ref, editor: observable.ref, xtextServices: observable.ref, + initialSelection: false, }); + import('./editor').then(({ editorChunk }) => { + runInAction(() => { + this.chunk = editorChunk; + }); + }).catch((error) => { + console.warn('Error while loading editor', error); + }); + } + + setInitialSelection(start: number, end: number, focused: boolean): void { + this.initialSelection = { start, end, focused }; + this.applyInitialSelectionToEditor(); + } + + private resetInitialSelection(): void { + this.initialSelection = { + start: 0, + end: 0, + focused: false, + }; + } + + private applyInitialSelectionToEditor(): void { + if (this.editor) { + const { start, end, focused } = this.initialSelection; + const doc = this.editor.getDoc(); + const startPos = doc.posFromIndex(start); + const endPos = doc.posFromIndex(end); + doc.setSelection(startPos, endPos, { + scroll: true, + }); + if (focused) { + this.editor.focus(); + } + this.resetInitialSelection(); + } } /** @@ -61,16 +99,23 @@ export class EditorStore { * @param newEditor The new CodeMirror instance */ editorDidMount(newEditor: Editor): void { + if (!this.chunk) { + throw new Error('Editor not loaded yet'); + } if (this.editor) { throw new Error('CoreMirror editor mounted before unmounting'); } this.editor = newEditor; - this.xtextServices = createServices(newEditor, xtextOptions); + this.xtextServices = this.chunk.createServices(newEditor, xtextOptions); + this.applyInitialSelectionToEditor(); } editorWillUnmount(): void { + if (!this.chunk) { + throw new Error('Editor not loaded yet'); + } if (this.editor) { - removeServices(this.editor); + this.chunk.removeServices(this.editor); } delete this.editor; delete this.xtextServices; @@ -93,10 +138,14 @@ export class EditorStore { this.atom.reportObserved(); } + get codeMirrorTheme(): string { + return `problem-${this.themeStore.className}`; + } + get codeMirrorOptions(): EditorConfiguration { return { ...codeMirrorGlobalOptions, - theme: this.themeStore.codeMirrorTheme, + theme: this.codeMirrorTheme, lineNumbers: this.showLineNumbers, }; } 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 @@ +import 'codemirror/addon/selection/active-line'; +import 'mode-problem'; +import { Controlled } from 'react-codemirror2'; +import { createServices, removeServices } from 'xtext/xtext-codemirror'; + +export interface IEditorChunk { + CodeMirror: typeof Controlled; + + createServices: typeof createServices; + + removeServices: typeof removeServices; +} + +export const editorChunk: IEditorChunk = { + CodeMirror: Controlled, + createServices, + removeServices, +}; 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 { return responsiveFontSizes(materialUiTheme); } - get codeMirrorTheme(): string { - return `problem-${this.currentThemeData.className}`; + get className(): string { + return this.currentThemeData.className; } } -- cgit v1.2.3-70-g09d2