aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main')
-rw-r--r--language-web/src/main/css/index.scss105
-rw-r--r--language-web/src/main/css/themeVariables.module.scss9
-rw-r--r--language-web/src/main/css/themes.scss36
-rw-r--r--language-web/src/main/js/App.tsx (renamed from language-web/src/main/js/App.jsx)58
-rw-r--r--language-web/src/main/js/Navbar.jsx37
-rw-r--r--language-web/src/main/js/RootStore.jsx28
-rw-r--r--language-web/src/main/js/RootStore.tsx31
-rw-r--r--language-web/src/main/js/editor/Editor.jsx52
-rw-r--r--language-web/src/main/js/editor/Editor.tsx20
-rw-r--r--language-web/src/main/js/editor/EditorButtons.tsx (renamed from language-web/src/main/js/editor/EditorButtons.jsx)48
-rw-r--r--language-web/src/main/js/editor/EditorStore.jsx87
-rw-r--r--language-web/src/main/js/editor/EditorStore.ts139
-rw-r--r--language-web/src/main/js/global.d.ts5
-rw-r--r--language-web/src/main/js/index.tsx (renamed from language-web/src/main/js/index.jsx)54
-rw-r--r--language-web/src/main/js/makeStyles.ts4
-rw-r--r--language-web/src/main/js/theme/EditorTheme.ts47
-rw-r--r--language-web/src/main/js/theme/ThemeProvider.tsx15
-rw-r--r--language-web/src/main/js/theme/ThemeStore.ts53
-rw-r--r--language-web/src/main/js/xtext/xtext-codemirror.d.ts43
-rw-r--r--language-web/src/main/js/xtext/xtext-codemirror.js3
20 files changed, 561 insertions, 313 deletions
diff --git a/language-web/src/main/css/index.scss b/language-web/src/main/css/index.scss
index 3ed91824..21a9c05b 100644
--- a/language-web/src/main/css/index.scss
+++ b/language-web/src/main/css/index.scss
@@ -1,3 +1,4 @@
1@use 'sass:map';
1@use '@fontsource/roboto/scss/mixins' as Roboto; 2@use '@fontsource/roboto/scss/mixins' as Roboto;
2@use '@fontsource/jetbrains-mono/scss/mixins' as JetbrainsMono; 3@use '@fontsource/jetbrains-mono/scss/mixins' as JetbrainsMono;
3 4
@@ -5,14 +6,16 @@
5@import 'codemirror/addon/hint/show-hint'; 6@import 'codemirror/addon/hint/show-hint';
6@import 'codemirror/theme/material-darker'; 7@import 'codemirror/theme/material-darker';
7 8
8$robotoWeights: 300, 400, 500, 700; 9@import './themes';
9@each $weight in $robotoWeights { 10
11$fontWeights: 300, 400, 500, 700;
12@each $weight in $fontWeights {
10 @include Roboto.fontFace($fontName: 'Roboto', $weight: $weight); 13 @include Roboto.fontFace($fontName: 'Roboto', $weight: $weight);
11 @include Roboto.fontFace($fontName: 'Roboto', $weight: $weight, $style: italic); 14 @include Roboto.fontFace($fontName: 'Roboto', $weight: $weight, $style: italic);
12} 15}
13 16
14$jetbrainsMonoWeights: 400, 700; 17$monoFontWeights: 400, 700;
15@each $weight in $jetbrainsMonoWeights { 18@each $weight in $monoFontWeights {
16 @include JetbrainsMono.fontFace($fontName: 'JetBrains Mono', $weight: $weight); 19 @include JetbrainsMono.fontFace($fontName: 'JetBrains Mono', $weight: $weight);
17 @include JetbrainsMono.fontFace($fontName: 'JetBrains Mono', $weight: $weight, $style: italic); 20 @include JetbrainsMono.fontFace($fontName: 'JetBrains Mono', $weight: $weight, $style: italic);
18} 21}
@@ -35,8 +38,78 @@ body {
35 text-rendering: optimizeLegibility; 38 text-rendering: optimizeLegibility;
36} 39}
37 40
41@each $themeName, $theme in $themes {
42 .cm-s-problem-#{$themeName} {
43 &.CodeMirror {
44 background: map.get($theme, 'background');
45 color: map.get($theme, 'foreground');
46 }
47
48 .CodeMirror-gutters {
49 background: map.get($theme, 'background');
50 border: none;
51 }
52
53 .CodeMirror-cursor {
54 border-left: 1px solid map.get($theme, 'cursor');
55 }
56
57 div.CodeMirror-selected,
58 &.CodeMirror-focused div.CodeMirror-selected,
59 .CodeMirror-line::selection,
60 .CodeMirror-line > span::selection,
61 .CodeMirror-line > span > span::selection {
62 background: map.get($theme, 'selection');
63 }
64
65 .CodeMirror-guttermarker,
66 .CodeMirror-guttermarker-subtle,
67 .CodeMirror-linenumber {
68 color: map.get($theme, 'lineNumber');
69 }
70
71 .CodeMirror-activeline-background {
72 background: map.get($theme, 'currentLine');
73 }
74
75 .CodeMirror-activeline-gutter {
76 background: map.get($theme, 'currentLine');
77
78 .CodeMirror-guttermarker,
79 .CodeMirror-guttermarker-subtle,
80 .CodeMirror-linenumber {
81 color: map.get($theme, 'foreground');
82 }
83 }
84
85 .cm-keyword {
86 color: map.get($theme, 'keyword');
87 }
88
89 .cm-number {
90 color: map.get($theme, 'number');
91 }
92
93 .cm-lparen, .cm-rparen {
94 color: map.get($theme, 'delimiter');
95 }
96
97 .problem-predicate, .problem-class, .problem-reference, .problem-enum {
98 color: map.get($theme, 'predicate');
99 }
100
101 .problem-unique-node {
102 color: map.get($theme, 'uniqueNode');
103 }
104
105 .problem-variable {
106 color: map.get($theme, 'variable');
107 }
108 }
109}
110
38.CodeMirror-hints { 111.CodeMirror-hints {
39 background: #333333; 112 background: #333;
40 border: 0; 113 border: 0;
41 border-radius: 4px; 114 border-radius: 4px;
42 box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 115 box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
@@ -107,38 +180,18 @@ li.CodeMirror-hint-active {
107 background: rgba(128, 203, 196, 0.2); 180 background: rgba(128, 203, 196, 0.2);
108} 181}
109 182
183
110.xtext-marker_write { 184.xtext-marker_write {
111 background: rgba(255, 229, 100, 0.2); 185 background: rgba(255, 229, 100, 0.2);
112} 186}
113 187
114.problem-class, .problem-enum {
115 @extend .cm-type;
116}
117
118.problem-abstract { 188.problem-abstract {
119 font-style: italic; 189 font-style: italic;
120} 190}
121 191
122.problem-reference {
123 @extend .cm-def;
124}
125
126.problem-containment { 192.problem-containment {
127 font-weight: 700; 193 font-weight: 700;
128} 194}
129
130.cm-quoted-name, .problem-enum-node {
131 @extend .cm-atom;
132}
133
134.problem-new-node { 195.problem-new-node {
135 font-style: italic; 196 font-style: italic;
136} 197}
137
138.problem-variable {
139 @extend .cm-variable;
140}
141
142.problem-singleton-variable {
143 opacity: 0.6;
144}
diff --git a/language-web/src/main/css/themeVariables.module.scss b/language-web/src/main/css/themeVariables.module.scss
new file mode 100644
index 00000000..85af4219
--- /dev/null
+++ b/language-web/src/main/css/themeVariables.module.scss
@@ -0,0 +1,9 @@
1@import './themes';
2
3:export {
4 @each $themeName, $theme in $themes {
5 @each $variable, $value in $theme {
6 #{$themeName}--#{$variable}: $value,
7 }
8 }
9}
diff --git a/language-web/src/main/css/themes.scss b/language-web/src/main/css/themes.scss
new file mode 100644
index 00000000..0118290d
--- /dev/null
+++ b/language-web/src/main/css/themes.scss
@@ -0,0 +1,36 @@
1$themes: (
2 'dark': (
3 'foreground': #abb2bf,
4 'background': #282c34,
5 'paper': #21252b,
6 'primary': #56b6c2,
7 'secondary': #ff5370,
8 'keyword': #56b6c2,
9 'predicate': #d6e9ff,
10 'variable': #c8ae9d,
11 'uniqueNode': #d6e9ff,
12 'number': #6e88a6,
13 'delimiter': #6f7682,
14 'cursor': #56b6c2,
15 'selection': #3e4452,
16 'currentLine': #2c323c,
17 'lineNumber': #5c6340,
18 ),
19 'light': (
20 'foreground': #abb2bf,
21 'background': #282c34,
22 'paper': #21252b,
23 'primary': #56b6c2,
24 'secondary': #ff5370,
25 'keyword': #56b6c2,
26 'predicate': #d6e9ff,
27 'variable': #c8ae9d,
28 'uniqueNode': #d6e9ff,
29 'number': #6e88a6,
30 'delimiter': #6f7682,
31 'cursor': #f3efe7,
32 'selection': #3e4452,
33 'currentLine': #2c323c,
34 'lineNumber': #5c6340,
35 ),
36);
diff --git a/language-web/src/main/js/App.jsx b/language-web/src/main/js/App.tsx
index a0920823..17d4f339 100644
--- a/language-web/src/main/js/App.jsx
+++ b/language-web/src/main/js/App.tsx
@@ -1,5 +1,4 @@
1import React from 'react'; 1import React from 'react';
2import { makeStyles } from '@material-ui/core/styles';
3import AppBar from '@material-ui/core/AppBar'; 2import AppBar from '@material-ui/core/AppBar';
4import Box from '@material-ui/core/Box'; 3import Box from '@material-ui/core/Box';
5import Button from '@material-ui/core/Button'; 4import Button from '@material-ui/core/Button';
@@ -9,10 +8,11 @@ import Typography from '@material-ui/core/Typography';
9import MenuIcon from '@material-ui/icons/Menu'; 8import MenuIcon from '@material-ui/icons/Menu';
10import PlayArrowIcon from '@material-ui/icons/PlayArrow'; 9import PlayArrowIcon from '@material-ui/icons/PlayArrow';
11 10
12import Editor from './editor/Editor'; 11import { makeStyles } from './makeStyles';
13import EditorButtons from './editor/EditorButtons'; 12import { Editor } from './editor/Editor';
13import { EditorButtons } from './editor/EditorButtons';
14 14
15const useStyles = makeStyles(theme => ({ 15const useStyles = makeStyles()((theme) => ({
16 container: { 16 container: {
17 maxHeight: '100vh', 17 maxHeight: '100vh',
18 }, 18 },
@@ -27,54 +27,54 @@ const useStyles = makeStyles(theme => ({
27 }, 27 },
28})); 28}));
29 29
30export default () => { 30export const App = (): JSX.Element => {
31 const classes = useStyles(); 31 const { classes, cx } = useStyles();
32 32
33 return ( 33 return (
34 <Box 34 <Box
35 display='flex' 35 display="flex"
36 flexDirection='column' 36 flexDirection="column"
37 className={classes.container} 37 className={cx(classes.container)}
38 > 38 >
39 <AppBar 39 <AppBar
40 position='static' 40 position="static"
41 color='inherit' 41 color="inherit"
42 > 42 >
43 <Toolbar> 43 <Toolbar>
44 <IconButton 44 <IconButton
45 edge='start' 45 edge="start"
46 className={classes.menuButton} 46 className={cx(classes.menuButton)}
47 color='inherit' 47 color="inherit"
48 aria-label='menu' 48 aria-label="menu"
49 > 49 >
50 <MenuIcon /> 50 <MenuIcon />
51 </IconButton> 51 </IconButton>
52 <Typography 52 <Typography
53 variant='h6' 53 variant="h6"
54 component='h1' 54 component="h1"
55 className={classes.title} 55 className={cx(classes.title)}
56 > 56 >
57 GraphSolver 57 GraphSolver
58 </Typography> 58 </Typography>
59 </Toolbar> 59 </Toolbar>
60 </AppBar> 60 </AppBar>
61 <Box 61 <Box
62 display='flex' 62 display="flex"
63 justifyContent='space-between' 63 justifyContent="space-between"
64 alignItems='center' 64 alignItems="center"
65 p={1} 65 p={1}
66 > 66 >
67 <Box 67 <Box
68 display='flex' 68 display="flex"
69 alignItems='center' 69 alignItems="center"
70 > 70 >
71 <EditorButtons/> 71 <EditorButtons />
72 </Box> 72 </Box>
73 <Box> 73 <Box>
74 <Button 74 <Button
75 variant='outlined' 75 variant="outlined"
76 color='primary' 76 color="primary"
77 startIcon={<PlayArrowIcon/>} 77 startIcon={<PlayArrowIcon />}
78 > 78 >
79 Generate 79 Generate
80 </Button> 80 </Button>
@@ -83,9 +83,9 @@ export default () => {
83 <Box 83 <Box
84 flexGrow={1} 84 flexGrow={1}
85 flexShrink={1} 85 flexShrink={1}
86 className={classes.editorBox} 86 className={cx(classes.editorBox)}
87 > 87 >
88 <Editor/> 88 <Editor />
89 </Box> 89 </Box>
90 </Box> 90 </Box>
91 ); 91 );
diff --git a/language-web/src/main/js/Navbar.jsx b/language-web/src/main/js/Navbar.jsx
deleted file mode 100644
index cf1bc54f..00000000
--- a/language-web/src/main/js/Navbar.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
1import React from 'react';
2import { Button, Navbar, Nav } from 'react-bootstrap';
3import { FaGithub, FaPlayCircle } from 'react-icons/fa';
4
5export default () => (
6 <Navbar
7 variant='secondary'
8 className='px-2'
9 >
10 <Navbar.Brand>GraphSolver</Navbar.Brand>
11 <Navbar.Toggle aria-controls='basic-navbar-nav'/>
12 <Navbar.Collapse id='basic-navbar-nav'>
13 <Nav className="me-auto">
14 <Nav.Link
15 href="https://github.com/viatra/VIATRA-Generator#readme"
16 >
17 About
18 </Nav.Link>
19 <Nav.Link
20 href="https://github.com/viatra/VIATRA-Generator/wiki"
21 >
22 Getting started
23 </Nav.Link>
24 <Nav.Link
25 href="https://github.com/viatra/VIATRA-Generator"
26 >
27 <FaGithub/> Github
28 </Nav.Link>
29 </Nav>
30 <Button
31 variant='success'
32 >
33 <FaPlayCircle/> Generate
34 </Button>
35 </Navbar.Collapse>
36 </Navbar>
37);
diff --git a/language-web/src/main/js/RootStore.jsx b/language-web/src/main/js/RootStore.jsx
deleted file mode 100644
index a437fdd0..00000000
--- a/language-web/src/main/js/RootStore.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
1import React, { createContext, useContext } from 'react';
2
3import EditorStore from './editor/EditorStore';
4
5export default class RootStore {
6 editorStore;
7
8 constructor() {
9 this.editorStore = new EditorStore();
10 }
11}
12
13const StoreContext = createContext(undefined);
14
15export const RootStoreProvider = ({ children, rootStore }) => (
16 <StoreContext.Provider value={rootStore}>
17 {children}
18 </StoreContext.Provider>
19);
20
21/** @returns {RootStore} */
22export const useRootStore = () => {
23 const rootStore = useContext(StoreContext);
24 if (!rootStore) {
25 throw new Error('useRootStore must be used within RootStoreProvider');
26 }
27 return rootStore;
28};
diff --git a/language-web/src/main/js/RootStore.tsx b/language-web/src/main/js/RootStore.tsx
new file mode 100644
index 00000000..88b8a445
--- /dev/null
+++ b/language-web/src/main/js/RootStore.tsx
@@ -0,0 +1,31 @@
1import React, { createContext, useContext } from 'react';
2
3import { EditorStore } from './editor/EditorStore';
4import { ThemeStore } from './theme/ThemeStore';
5
6export class RootStore {
7 editorStore;
8
9 themeStore;
10
11 constructor() {
12 this.themeStore = new ThemeStore();
13 this.editorStore = new EditorStore(this.themeStore);
14 }
15}
16
17const StoreContext = createContext<RootStore | undefined>(undefined);
18
19export const RootStoreProvider: React.FC<{ rootStore: RootStore }> = ({ children, rootStore }) => (
20 <StoreContext.Provider value={rootStore}>
21 {children}
22 </StoreContext.Provider>
23);
24
25export const useRootStore = (): RootStore => {
26 const rootStore = useContext(StoreContext);
27 if (!rootStore) {
28 throw new Error('useRootStore must be used within RootStoreProvider');
29 }
30 return rootStore;
31};
diff --git a/language-web/src/main/js/editor/Editor.jsx b/language-web/src/main/js/editor/Editor.jsx
deleted file mode 100644
index 98cf2715..00000000
--- a/language-web/src/main/js/editor/Editor.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
1import { observer } from 'mobx-react-lite';
2import 'mode-problem';
3import React, { useCallback } from 'react';
4import { Controlled as CodeMirror } from 'react-codemirror2';
5import { createServices, removeServices } from 'xtext/xtext-codemirror';
6
7import { useRootStore } from '../RootStore';
8
9export default observer(() => {
10 const editorStore = useRootStore().editorStore;
11
12 const codeMirrorOptions = {
13 mode: 'xtext/problem',
14 indentUnit: 2,
15 theme: 'material-darker',
16 lineNumbers: editorStore.showLineNumbers,
17 };
18
19 const xtextOptions = {
20 xtextLang: 'problem',
21 enableFormattingAction: true,
22 }
23
24 const editorDidMount = useCallback((editor) => {
25 createServices(editor, xtextOptions);
26 editorStore.updateEditor(editor);
27 }, [editorStore]);
28
29 const editorWillUnmount = useCallback((editor) => {
30 editorStore.editor = null;
31 removeServices(editor);
32 }, [editorStore]);
33
34 const onBeforeChange = useCallback((_editor, _data, value) => {
35 editorStore.updateValue(value);
36 }, [editorStore]);
37
38 const onChange = useCallback((_editor, _data, _value) => {
39 editorStore.reportChanged();
40 }, [editorStore]);
41
42 return (
43 <CodeMirror
44 value={editorStore.value}
45 options={codeMirrorOptions}
46 editorDidMount={editorDidMount}
47 editorWillUnmount={editorWillUnmount}
48 onBeforeChange={onBeforeChange}
49 onChange={onChange}
50 />
51 );
52});
diff --git a/language-web/src/main/js/editor/Editor.tsx b/language-web/src/main/js/editor/Editor.tsx
new file mode 100644
index 00000000..9badb6a3
--- /dev/null
+++ b/language-web/src/main/js/editor/Editor.tsx
@@ -0,0 +1,20 @@
1import { observer } from 'mobx-react-lite';
2import React from 'react';
3import { Controlled as CodeMirror } from 'react-codemirror2';
4
5import { useRootStore } from '../RootStore';
6
7export 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/EditorButtons.jsx b/language-web/src/main/js/editor/EditorButtons.tsx
index f67afdbf..d3825c07 100644
--- a/language-web/src/main/js/editor/EditorButtons.jsx
+++ b/language-web/src/main/js/editor/EditorButtons.tsx
@@ -1,69 +1,75 @@
1import { observer } from 'mobx-react-lite'; 1import { observer } from 'mobx-react-lite';
2import React from 'react'; 2import React from 'react';
3import { makeStyles } from '@material-ui/core/styles';
4import Button from '@material-ui/core/Button'; 3import Button from '@material-ui/core/Button';
5import ButtonGroup from '@material-ui/core/ButtonGroup'; 4import ButtonGroup from '@material-ui/core/ButtonGroup';
5import ToggleButton from '@material-ui/core/ToggleButton';
6import Divider from '@material-ui/core/Divider'; 6import Divider from '@material-ui/core/Divider';
7import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered'; 7import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered';
8import RedoIcon from '@material-ui/icons/Redo'; 8import RedoIcon from '@material-ui/icons/Redo';
9import UndoIcon from '@material-ui/icons/Undo'; 9import UndoIcon from '@material-ui/icons/Undo';
10import ToggleButton from '@material-ui/lab/ToggleButton';
11 10
11import { makeStyles } from '../makeStyles';
12import { useRootStore } from '../RootStore'; 12import { useRootStore } from '../RootStore';
13 13
14const useStyles = makeStyles(theme => ({ 14const useStyles = makeStyles()((theme) => ({
15 iconButton: { 15 iconButton: {
16 padding: 7, 16 padding: 7,
17 minWidth: 36,
18 border: 0, 17 border: 0,
19 color: theme.palette.text.primary, 18 color: theme.palette.text.primary,
20 '&.MuiButtonGroup-groupedTextHorizontal': { 19 '&, &.MuiButtonGroup-grouped': {
20 minWidth: 36,
21 },
22 '&.MuiButtonGroup-grouped:not(:last-of-type)': {
21 borderRight: 0, 23 borderRight: 0,
22 }, 24 },
23 }, 25 },
24 divider: { 26 divider: {
25 margin: theme.spacing(0.5), 27 margin: theme.spacing(0.5),
26 } 28 },
27})); 29}));
28 30
29export default observer(() => { 31export const EditorButtons = observer(() => {
30 const editorStore = useRootStore().editorStore; 32 const { editorStore } = useRootStore();
31 const classes = useStyles(); 33 const { classes, cx } = useStyles();
34
32 return ( 35 return (
33 <> 36 <>
34 <ButtonGroup 37 <ButtonGroup
35 variant='text' 38 variant="text"
36 > 39 >
37 <Button 40 <Button
38 disabled={!editorStore.canUndo} 41 disabled={!editorStore.canUndo}
39 onClick={() => editorStore.undo()} 42 onClick={() => editorStore.undo()}
40 className={classes.iconButton} 43 className={cx(classes.iconButton)}
41 aria-label='Undo' 44 color="inherit"
45 aria-label="Undo"
42 > 46 >
43 <UndoIcon fontSize='small'/> 47 <UndoIcon fontSize="small" />
44 </Button> 48 </Button>
45 <Button 49 <Button
46 disabled={!editorStore.canRedo} 50 disabled={!editorStore.canRedo}
47 onClick={() => editorStore.redo()} 51 onClick={() => editorStore.redo()}
48 className={classes.iconButton} 52 className={cx(classes.iconButton)}
49 aria-label='Redo' 53 color="inherit"
54 aria-label="Redo"
50 > 55 >
51 <RedoIcon fontSize='small'/> 56 <RedoIcon fontSize="small" />
52 </Button> 57 </Button>
53 </ButtonGroup> 58 </ButtonGroup>
54 <Divider 59 <Divider
55 flexItem 60 flexItem
56 orientation='vertical' 61 orientation="vertical"
57 className={classes.divider} 62 className={classes.divider}
58 /> 63 />
59 <ToggleButton 64 <ToggleButton
60 selected={editorStore.showLineNumbers} 65 selected={editorStore.showLineNumbers}
61 onChange={() => editorStore.toggleLineNumbers()} 66 onChange={() => editorStore.toggleLineNumbers()}
62 size='small' 67 size="small"
63 className={classes.iconButton} 68 className={cx(classes.iconButton)}
64 aria-label='Show line numbers' 69 aria-label="Show line numbers"
70 value="show-line-numbers"
65 > 71 >
66 <FormatListNumberedIcon fontSize='small'/> 72 <FormatListNumberedIcon fontSize="small" />
67 </ToggleButton> 73 </ToggleButton>
68 </> 74 </>
69 ); 75 );
diff --git a/language-web/src/main/js/editor/EditorStore.jsx b/language-web/src/main/js/editor/EditorStore.jsx
deleted file mode 100644
index b6f9bc0a..00000000
--- a/language-web/src/main/js/editor/EditorStore.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
1import CodeMirror from 'codemirror';
2import { createAtom, makeAutoObservable, observable } from 'mobx';
3
4export default class EditorStore {
5 atom;
6 /** @type {CodeMirror} */
7 editor = null;
8 /** @type {string} */
9 value = '';
10 /** @type {boolean} */
11 showLineNumbers = false;
12 /** @type {boolean} */
13 showLigatures = true;
14
15 constructor() {
16 this.atom = createAtom('EditorStore');
17 makeAutoObservable(this, {
18 atom: false,
19 editor: observable.ref,
20 });
21 }
22
23 /**
24 * Attaches a new CodeMirror instance.
25 *
26 * The store will node subscribe to any CodeMirror events. Instead,
27 * the editor component should subscribe to them and relay them to the store.
28 *
29 * @param {CodeMirror} newEditor The new CodeMirror instance
30 */
31 updateEditor(newEditor) {
32 this.editor = newEditor;
33 }
34
35 /**
36 * Updates the contents of the editor.
37 *
38 * @param {string} newValue The new contents of the editor
39 */
40 updateValue(newValue) {
41 this.value = newValue;
42 }
43
44 reportChanged() {
45 this.atom.reportChanged();
46 }
47
48 /**
49 * @returns {boolean} `true` if there is history to undo
50 */
51 get canUndo() {
52 this.atom.reportObserved();
53 if (!this.editor) {
54 return false;
55 }
56 const { undo: undoSize } = this.editor.historySize();
57 return undoSize > 0;
58 }
59
60 undo() {
61 this.editor.undo();
62 }
63
64 /**
65 * @returns {boolean} `true` if there is history to redo
66 */
67 get canRedo() {
68 this.atom.reportObserved();
69 if (!this.editor) {
70 return false;
71 }
72 const { redo: redoSize } = this.editor.historySize();
73 return redoSize > 0;
74 }
75
76 redo() {
77 this.editor.redo();
78 }
79
80 toggleLineNumbers() {
81 this.showLineNumbers = !this.showLineNumbers;
82 }
83
84 toggleLigatures() {
85 this.showLigatures = !this.showLigatures;
86 }
87}
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts
new file mode 100644
index 00000000..5da45ac1
--- /dev/null
+++ b/language-web/src/main/js/editor/EditorStore.ts
@@ -0,0 +1,139 @@
1import { Editor, EditorConfiguration } from 'codemirror';
2import 'codemirror/addon/selection/active-line';
3import {
4 createAtom,
5 makeAutoObservable,
6 observable,
7} from 'mobx';
8import 'mode-problem';
9import {
10 IXtextOptions,
11 IXtextServices,
12 createServices,
13 removeServices,
14} from 'xtext/xtext-codemirror';
15
16import { ThemeStore } from '../theme/ThemeStore';
17
18const xtextLang = 'problem';
19
20const xtextOptions: IXtextOptions = {
21 xtextLang,
22 enableFormattingAction: true,
23};
24
25const codeMirrorGlobalOptions: EditorConfiguration = {
26 mode: `xtext/${xtextLang}`,
27 indentUnit: 2,
28 styleActiveLine: true,
29};
30
31export class EditorStore {
32 themeStore;
33
34 atom;
35
36 editor?: Editor;
37
38 xtextServices?: IXtextServices;
39
40 value = '';
41
42 showLineNumbers = false;
43
44 constructor(themeStore: ThemeStore) {
45 this.themeStore = themeStore;
46 this.atom = createAtom('EditorStore');
47 makeAutoObservable(this, {
48 themeStore: false,
49 atom: false,
50 editor: observable.ref,
51 xtextServices: observable.ref,
52 });
53 }
54
55 /**
56 * Attaches a new CodeMirror instance and creates Xtext services.
57 *
58 * The store will not subscribe to any CodeMirror events. Instead,
59 * the editor component should subscribe to them and relay them to the store.
60 *
61 * @param newEditor The new CodeMirror instance
62 */
63 editorDidMount(newEditor: Editor): void {
64 if (this.editor) {
65 throw new Error('CoreMirror editor mounted before unmounting');
66 }
67 this.editor = newEditor;
68 this.xtextServices = createServices(newEditor, xtextOptions);
69 }
70
71 editorWillUnmount(): void {
72 if (this.editor) {
73 removeServices(this.editor);
74 }
75 delete this.editor;
76 delete this.xtextServices;
77 }
78
79 /**
80 * Updates the contents of the editor.
81 *
82 * @param newValue The new contents of the editor
83 */
84 updateValue(newValue: string): void {
85 this.value = newValue;
86 }
87
88 reportChanged(): void {
89 this.atom.reportChanged();
90 }
91
92 protected observeEditorChanges(): void {
93 this.atom.reportObserved();
94 }
95
96 get codeMirrorOptions(): EditorConfiguration {
97 return {
98 ...codeMirrorGlobalOptions,
99 theme: this.themeStore.codeMirrorTheme,
100 lineNumbers: this.showLineNumbers,
101 };
102 }
103
104 /**
105 * @returns `true` if there is history to undo
106 */
107 get canUndo(): boolean {
108 this.observeEditorChanges();
109 if (!this.editor) {
110 return false;
111 }
112 const { undo: undoSize } = this.editor.historySize();
113 return undoSize > 0;
114 }
115
116 undo(): void {
117 this.editor?.undo();
118 }
119
120 /**
121 * @returns `true` if there is history to redo
122 */
123 get canRedo(): boolean {
124 this.observeEditorChanges();
125 if (!this.editor) {
126 return false;
127 }
128 const { redo: redoSize } = this.editor.historySize();
129 return redoSize > 0;
130 }
131
132 redo(): void {
133 this.editor?.redo();
134 }
135
136 toggleLineNumbers(): void {
137 this.showLineNumbers = !this.showLineNumbers;
138 }
139}
diff --git a/language-web/src/main/js/global.d.ts b/language-web/src/main/js/global.d.ts
new file mode 100644
index 00000000..39bda7f3
--- /dev/null
+++ b/language-web/src/main/js/global.d.ts
@@ -0,0 +1,5 @@
1declare module '*.module.scss' {
2 const cssVariables: { [key in string]?: string };
3 // eslint-disable-next-line import/no-default-export
4 export default cssVariables;
5}
diff --git a/language-web/src/main/js/index.jsx b/language-web/src/main/js/index.tsx
index b3277a30..1f08feeb 100644
--- a/language-web/src/main/js/index.jsx
+++ b/language-web/src/main/js/index.tsx
@@ -1,11 +1,12 @@
1import { CacheProvider } from '@emotion/react';
1import React from 'react'; 2import React from 'react';
2import { render } from 'react-dom'; 3import { render } from 'react-dom';
3import { createMuiTheme } from '@material-ui/core/styles';
4import { ThemeProvider } from '@material-ui/styles';
5
6import App from './App';
7import CssBaseline from '@material-ui/core/CssBaseline'; 4import CssBaseline from '@material-ui/core/CssBaseline';
8import RootStore, { RootStoreProvider } from './RootStore'; 5import { getCache } from 'tss-react/cache';
6
7import { App } from './App';
8import { RootStore, RootStoreProvider } from './RootStore';
9import { ThemeProvider } from './theme/ThemeProvider';
9 10
10import '../css/index.scss'; 11import '../css/index.scss';
11 12
@@ -25,15 +26,17 @@ enum TaxStatus {
25} 26}
26 27
27% A child cannot have any dependents. 28% A child cannot have any dependents.
28error invalidTaxStatus(Person p) <=> 29error invalidTaxStatus(Person p) <->
29 taxStatus(p, child), children(p, _q). 30 taxStatus(p, child), children(p, _q).
30 31
31Family('family'). 32unique family.
32members('family', anne). 33Family(family).
33members('family', bob). 34members(family, anne).
34members('family', ciri). 35members(family, bob).
36members(family, ciri).
35children(anne, ciri). 37children(anne, ciri).
36?children(bob, ciri). 38?children(bob, ciri).
39default children(ciri, *): false.
37taxStatus(anne, adult). 40taxStatus(anne, adult).
38age(anne, 35). 41age(anne, 35).
39bobAge: 27. 42bobAge: 27.
@@ -46,28 +49,15 @@ scope Family = 1, Person += 5..10.
46const rootStore = new RootStore(); 49const rootStore = new RootStore();
47rootStore.editorStore.updateValue(initialValue); 50rootStore.editorStore.updateValue(initialValue);
48 51
49const theme = createMuiTheme({
50 palette: {
51 type: 'dark',
52 background: {
53 default: '#212121',
54 },
55 primary: {
56 main: '#82aaff',
57 },
58 secondary: {
59 main: '#ff5370',
60 },
61 },
62});
63
64const app = ( 52const app = (
65 <ThemeProvider theme={theme}> 53 <RootStoreProvider rootStore={rootStore}>
66 <CssBaseline/> 54 <CacheProvider value={getCache()}>
67 <RootStoreProvider rootStore={rootStore}> 55 <ThemeProvider>
68 <App/> 56 <CssBaseline />
69 </RootStoreProvider> 57 <App />
70 </ThemeProvider> 58 </ThemeProvider>
71) 59 </CacheProvider>
60 </RootStoreProvider>
61);
72 62
73render(app, document.getElementById('app')); 63render(app, document.getElementById('app'));
diff --git a/language-web/src/main/js/makeStyles.ts b/language-web/src/main/js/makeStyles.ts
new file mode 100644
index 00000000..a80e8858
--- /dev/null
+++ b/language-web/src/main/js/makeStyles.ts
@@ -0,0 +1,4 @@
1import { createMakeStyles } from 'tss-react';
2import { useTheme } from '@material-ui/core/styles';
3
4export const { makeStyles } = createMakeStyles({ useTheme });
diff --git a/language-web/src/main/js/theme/EditorTheme.ts b/language-web/src/main/js/theme/EditorTheme.ts
new file mode 100644
index 00000000..9420dafa
--- /dev/null
+++ b/language-web/src/main/js/theme/EditorTheme.ts
@@ -0,0 +1,47 @@
1import { PaletteMode } from '@material-ui/core';
2
3import cssVariables from '../../css/themeVariables.module.scss';
4
5export enum EditorTheme {
6 Light,
7 Dark,
8}
9
10export class EditorThemeData {
11 className: string;
12
13 paletteMode: PaletteMode;
14
15 toggleDarkMode: EditorTheme;
16
17 foreground!: string;
18
19 background!: string;
20
21 paper!: string;
22
23 primary!: string;
24
25 secondary!: string;
26
27 constructor(className: string, paletteMode: PaletteMode, toggleDarkMode: EditorTheme) {
28 this.className = className;
29 this.paletteMode = paletteMode;
30 this.toggleDarkMode = toggleDarkMode;
31 Reflect.ownKeys(this).forEach((key) => {
32 if (!Reflect.get(this, key)) {
33 const cssKey = `${this.className}--${key.toString()}`;
34 if (cssKey in cssVariables) {
35 Reflect.set(this, key, cssVariables[cssKey]);
36 }
37 }
38 });
39 }
40}
41
42export const DEFAULT_THEME = EditorTheme.Dark;
43
44export const EDITOR_THEMES: { [key in EditorTheme]: EditorThemeData } = {
45 [EditorTheme.Light]: new EditorThemeData('light', 'light', EditorTheme.Dark),
46 [EditorTheme.Dark]: new EditorThemeData('dark', 'dark', EditorTheme.Light),
47};
diff --git a/language-web/src/main/js/theme/ThemeProvider.tsx b/language-web/src/main/js/theme/ThemeProvider.tsx
new file mode 100644
index 00000000..e7574725
--- /dev/null
+++ b/language-web/src/main/js/theme/ThemeProvider.tsx
@@ -0,0 +1,15 @@
1import { ThemeProvider as MaterialUiThemeProvider } from '@material-ui/core/styles';
2import { observer } from 'mobx-react-lite';
3import React from 'react';
4
5import { useRootStore } from '../RootStore';
6
7export const ThemeProvider: React.FC = observer(({ children }) => {
8 const { themeStore } = useRootStore();
9
10 return (
11 <MaterialUiThemeProvider theme={themeStore.materialUiTheme}>
12 {children}
13 </MaterialUiThemeProvider>
14 );
15});
diff --git a/language-web/src/main/js/theme/ThemeStore.ts b/language-web/src/main/js/theme/ThemeStore.ts
new file mode 100644
index 00000000..0f283c98
--- /dev/null
+++ b/language-web/src/main/js/theme/ThemeStore.ts
@@ -0,0 +1,53 @@
1import {
2 Theme,
3 createTheme,
4 responsiveFontSizes,
5} from '@material-ui/core/styles';
6import { makeAutoObservable } from 'mobx';
7
8import {
9 EditorTheme,
10 EditorThemeData,
11 DEFAULT_THEME,
12 EDITOR_THEMES,
13} from './EditorTheme';
14
15export class ThemeStore {
16 currentTheme: EditorTheme = DEFAULT_THEME;
17
18 constructor() {
19 makeAutoObservable(this);
20 }
21
22 toggleDarkMode(): void {
23 this.currentTheme = this.currentThemeData.toggleDarkMode;
24 }
25
26 private get currentThemeData(): EditorThemeData {
27 return EDITOR_THEMES[this.currentTheme];
28 }
29
30 get materialUiTheme(): Theme {
31 const themeData = this.currentThemeData;
32 const materialUiTheme = createTheme({
33 palette: {
34 mode: themeData.paletteMode,
35 background: {
36 default: themeData.background,
37 paper: themeData.paper,
38 },
39 primary: {
40 main: themeData.primary,
41 },
42 secondary: {
43 main: themeData.secondary,
44 },
45 },
46 });
47 return responsiveFontSizes(materialUiTheme);
48 }
49
50 get codeMirrorTheme(): string {
51 return `problem-${this.currentThemeData.className}`;
52 }
53}
diff --git a/language-web/src/main/js/xtext/xtext-codemirror.d.ts b/language-web/src/main/js/xtext/xtext-codemirror.d.ts
new file mode 100644
index 00000000..fff850b8
--- /dev/null
+++ b/language-web/src/main/js/xtext/xtext-codemirror.d.ts
@@ -0,0 +1,43 @@
1import { Editor } from 'codemirror';
2
3export function createEditor(options: IXtextOptions): IXtextCodeMirrorEditor;
4
5export function createServices(editor: Editor, options: IXtextOptions): IXtextServices;
6
7export function removeServices(editor: Editor): void;
8
9export interface IXtextOptions {
10 baseUrl?: string;
11 contentType?: string;
12 dirtyElement?: string | Element;
13 dirtyStatusClass?: string;
14 document?: Document;
15 enableContentAssistService?: boolean;
16 enableCors?: boolean;
17 enableFormattingAction?: boolean;
18 enableFormattingService?: boolean;
19 enableGeneratorService?: boolean;
20 enableHighlightingService?: boolean;
21 enableOccurrencesService?: boolean;
22 enableSaveAction?: boolean;
23 enableValidationService?: boolean;
24 loadFromServer?: boolean;
25 mode?: string;
26 parent?: string | Element;
27 parentClass?: string;
28 resourceId?: string;
29 selectionUpdateDelay?: number;
30 sendFullText?: boolean;
31 serviceUrl?: string;
32 showErrorDialogs?: boolean;
33 syntaxDefinition?: string;
34 textUpdateDelay?: number;
35 xtextLang?: string;
36}
37
38export interface IXtextCodeMirrorEditor extends Editor {
39 xtextServices: IXtextServices;
40}
41
42export interface IXtextServices {
43}
diff --git a/language-web/src/main/js/xtext/xtext-codemirror.js b/language-web/src/main/js/xtext/xtext-codemirror.js
index 4d50718c..d246172a 100644
--- a/language-web/src/main/js/xtext/xtext-codemirror.js
+++ b/language-web/src/main/js/xtext/xtext-codemirror.js
@@ -279,6 +279,7 @@ define([
279 } 279 }
280 }; 280 };
281 }), 281 }),
282 from: cursor,
282 to: cursor 283 to: cursor
283 }; 284 };
284 }}); 285 }});
@@ -329,10 +330,10 @@ define([
329 330
330 CodeMirrorServiceBuilder.prototype._clearAnnotations = function(annotations) { 331 CodeMirrorServiceBuilder.prototype._clearAnnotations = function(annotations) {
331 var editor = this.editor; 332 var editor = this.editor;
333 editor.clearGutter('annotations-gutter');
332 for (var i = 0; i < annotations.length; i++) { 334 for (var i = 0; i < annotations.length; i++) {
333 var annotation = annotations[i]; 335 var annotation = annotations[i];
334 if (annotation) { 336 if (annotation) {
335 editor.setGutterMarker(i, 'annotations-gutter', null);
336 annotations[i] = undefined; 337 annotations[i] = undefined;
337 } 338 }
338 } 339 }