diff options
Diffstat (limited to 'language-web')
-rw-r--r-- | language-web/package-lock.json | 39 | ||||
-rw-r--r-- | language-web/package.json | 1 | ||||
-rw-r--r-- | language-web/src/main/css/index.scss | 79 | ||||
-rw-r--r-- | language-web/src/main/html/index.html | 1 | ||||
-rw-r--r-- | language-web/src/main/js/App.jsx | 61 | ||||
-rw-r--r-- | language-web/src/main/js/RootStore.jsx | 2 | ||||
-rw-r--r-- | language-web/src/main/js/editor/Editor.jsx | 2 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorButtons.jsx | 67 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorStore.jsx | 6 | ||||
-rw-r--r-- | language-web/src/main/js/index.jsx | 16 |
10 files changed, 225 insertions, 49 deletions
diff --git a/language-web/package-lock.json b/language-web/package-lock.json index b237bfa2..49deae9f 100644 --- a/language-web/package-lock.json +++ b/language-web/package-lock.json | |||
@@ -10,6 +10,7 @@ | |||
10 | "dependencies": { | 10 | "dependencies": { |
11 | "@material-ui/core": "^4.11.4", | 11 | "@material-ui/core": "^4.11.4", |
12 | "@material-ui/icons": "^4.11.2", | 12 | "@material-ui/icons": "^4.11.2", |
13 | "@material-ui/lab": "^4.0.0-alpha.58", | ||
13 | "codemirror": "^5.62.0", | 14 | "codemirror": "^5.62.0", |
14 | "jquery": "^3.6.0", | 15 | "jquery": "^3.6.0", |
15 | "mobx": "^6.3.2", | 16 | "mobx": "^6.3.2", |
@@ -1733,6 +1734,32 @@ | |||
1733 | } | 1734 | } |
1734 | } | 1735 | } |
1735 | }, | 1736 | }, |
1737 | "node_modules/@material-ui/lab": { | ||
1738 | "version": "4.0.0-alpha.58", | ||
1739 | "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.58.tgz", | ||
1740 | "integrity": "sha512-GKHlJqLxUeHH3L3dGQ48ZavYrqGOTXkFkiEiuYMAnAvXAZP4rhMIqeHOPXSUQan4Bd8QnafDcpovOSLnadDmKw==", | ||
1741 | "dependencies": { | ||
1742 | "@babel/runtime": "^7.4.4", | ||
1743 | "@material-ui/utils": "^4.11.2", | ||
1744 | "clsx": "^1.0.4", | ||
1745 | "prop-types": "^15.7.2", | ||
1746 | "react-is": "^16.8.0 || ^17.0.0" | ||
1747 | }, | ||
1748 | "engines": { | ||
1749 | "node": ">=8.0.0" | ||
1750 | }, | ||
1751 | "peerDependencies": { | ||
1752 | "@material-ui/core": "^4.9.10", | ||
1753 | "@types/react": "^16.8.6 || ^17.0.0", | ||
1754 | "react": "^16.8.0 || ^17.0.0", | ||
1755 | "react-dom": "^16.8.0 || ^17.0.0" | ||
1756 | }, | ||
1757 | "peerDependenciesMeta": { | ||
1758 | "@types/react": { | ||
1759 | "optional": true | ||
1760 | } | ||
1761 | } | ||
1762 | }, | ||
1736 | "node_modules/@material-ui/styles": { | 1763 | "node_modules/@material-ui/styles": { |
1737 | "version": "4.11.4", | 1764 | "version": "4.11.4", |
1738 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", | 1765 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", |
@@ -14327,6 +14354,18 @@ | |||
14327 | "@babel/runtime": "^7.4.4" | 14354 | "@babel/runtime": "^7.4.4" |
14328 | } | 14355 | } |
14329 | }, | 14356 | }, |
14357 | "@material-ui/lab": { | ||
14358 | "version": "4.0.0-alpha.58", | ||
14359 | "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.58.tgz", | ||
14360 | "integrity": "sha512-GKHlJqLxUeHH3L3dGQ48ZavYrqGOTXkFkiEiuYMAnAvXAZP4rhMIqeHOPXSUQan4Bd8QnafDcpovOSLnadDmKw==", | ||
14361 | "requires": { | ||
14362 | "@babel/runtime": "^7.4.4", | ||
14363 | "@material-ui/utils": "^4.11.2", | ||
14364 | "clsx": "^1.0.4", | ||
14365 | "prop-types": "^15.7.2", | ||
14366 | "react-is": "^16.8.0 || ^17.0.0" | ||
14367 | } | ||
14368 | }, | ||
14330 | "@material-ui/styles": { | 14369 | "@material-ui/styles": { |
14331 | "version": "4.11.4", | 14370 | "version": "4.11.4", |
14332 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", | 14371 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", |
diff --git a/language-web/package.json b/language-web/package.json index e8d0a32f..7ebe40d3 100644 --- a/language-web/package.json +++ b/language-web/package.json | |||
@@ -41,6 +41,7 @@ | |||
41 | "dependencies": { | 41 | "dependencies": { |
42 | "@material-ui/core": "^4.11.4", | 42 | "@material-ui/core": "^4.11.4", |
43 | "@material-ui/icons": "^4.11.2", | 43 | "@material-ui/icons": "^4.11.2", |
44 | "@material-ui/lab": "^4.0.0-alpha.58", | ||
44 | "codemirror": "^5.62.0", | 45 | "codemirror": "^5.62.0", |
45 | "jquery": "^3.6.0", | 46 | "jquery": "^3.6.0", |
46 | "mobx": "^6.3.2", | 47 | "mobx": "^6.3.2", |
diff --git a/language-web/src/main/css/index.scss b/language-web/src/main/css/index.scss index 62109a2f..d9c9b4b4 100644 --- a/language-web/src/main/css/index.scss +++ b/language-web/src/main/css/index.scss | |||
@@ -1,31 +1,84 @@ | |||
1 | @import 'codemirror/lib/codemirror'; | 1 | @import 'codemirror/lib/codemirror'; |
2 | @import 'codemirror/addon/hint/show-hint'; | 2 | @import 'codemirror/addon/hint/show-hint'; |
3 | @import 'codemirror/theme/material-darker'; | ||
3 | 4 | ||
4 | @import 'xtext/xtext-codemirror'; | 5 | .CodeMirror { |
6 | font-size: 16px; | ||
7 | height: 100%; | ||
8 | } | ||
5 | 9 | ||
6 | .problem-class { | 10 | .annotations-gutter { |
7 | color: #005032; | 11 | width: 12px; |
8 | } | 12 | } |
9 | 13 | ||
10 | .problem-abstract { | 14 | .xtext-annotation_error { |
11 | color: #8b8816; | 15 | width: 12px; |
16 | height: 12px; | ||
17 | background-image: url('images/error_an.gif'); | ||
18 | background-repeat: no-repeat; | ||
19 | } | ||
20 | |||
21 | .xtext-annotation_warning { | ||
22 | width: 12px; | ||
23 | height: 12px; | ||
24 | background-image: url('images/warning_an.gif'); | ||
25 | background-repeat: no-repeat; | ||
26 | } | ||
27 | |||
28 | .xtext-annotation_info { | ||
29 | width: 12px; | ||
30 | height: 12px; | ||
31 | background-image: url('images/info_an.gif'); | ||
32 | background-repeat: no-repeat; | ||
12 | } | 33 | } |
13 | 34 | ||
14 | .problem-enum { | 35 | .xtext-marker_error { |
15 | color: #644632; | 36 | z-index: 30; |
37 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); | ||
38 | background-repeat: repeat-x; | ||
39 | background-position: left bottom; | ||
40 | } | ||
41 | |||
42 | .xtext-marker_warning { | ||
43 | z-index: 20; | ||
44 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); | ||
45 | background-repeat: repeat-x; | ||
46 | background-position: left bottom; | ||
47 | } | ||
48 | |||
49 | .xtext-marker_info { | ||
50 | z-index: 10; | ||
51 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAANklEQVQI12NkgIIVRx8tZGBg6GZccfRRKAMDgw8DA0M3AwPDIiYGBoZKBgaG7ghruSsMDAwpABH5CoqwzCoTAAAAAElFTkSuQmCC"); | ||
52 | background-repeat: repeat-x; | ||
53 | background-position: left bottom; | ||
54 | } | ||
55 | |||
56 | .xtext-marker_read { | ||
57 | background: rgba(128, 203, 196, 0.2); | ||
58 | } | ||
59 | |||
60 | .xtext-marker_write { | ||
61 | background: rgba(255, 229, 100, 0.2); | ||
62 | } | ||
63 | |||
64 | .problem-class, .problem-enum { | ||
65 | @extend .cm-type; | ||
66 | } | ||
67 | |||
68 | .problem-abstract { | ||
69 | font-style: italic; | ||
16 | } | 70 | } |
17 | 71 | ||
18 | .problem-reference { | 72 | .problem-reference { |
19 | color: #0000c0; | 73 | @extend .cm-def; |
20 | } | 74 | } |
21 | 75 | ||
22 | .problem-containment { | 76 | .problem-containment { |
23 | font-weight: bold; | 77 | font-weight: bold; |
24 | } | 78 | } |
25 | 79 | ||
26 | .CodeMirror .cm-quoted-name, .problem-enum-node { | 80 | .cm-quoted-name, .problem-enum-node { |
27 | color: #1a1a1a !important; | 81 | @extend .cm-atom; |
28 | font-weight: bold; | ||
29 | } | 82 | } |
30 | 83 | ||
31 | .problem-new-node { | 84 | .problem-new-node { |
@@ -33,9 +86,9 @@ | |||
33 | } | 86 | } |
34 | 87 | ||
35 | .problem-variable { | 88 | .problem-variable { |
36 | color: #6a3e3e; | 89 | @extend .cm-variable; |
37 | } | 90 | } |
38 | 91 | ||
39 | .problem-singleton-variable { | 92 | .problem-singleton-variable { |
40 | color: #a1706f; | 93 | opacity: 0.7; |
41 | } | 94 | } |
diff --git a/language-web/src/main/html/index.html b/language-web/src/main/html/index.html index 01b4e536..7762706f 100644 --- a/language-web/src/main/html/index.html +++ b/language-web/src/main/html/index.html | |||
@@ -2,6 +2,7 @@ | |||
2 | <html> | 2 | <html> |
3 | <head> | 3 | <head> |
4 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
5 | <title>Example Web Editor</title> | 6 | <title>Example Web Editor</title> |
6 | </head> | 7 | </head> |
7 | <body> | 8 | <body> |
diff --git a/language-web/src/main/js/App.jsx b/language-web/src/main/js/App.jsx index 61ded943..a0920823 100644 --- a/language-web/src/main/js/App.jsx +++ b/language-web/src/main/js/App.jsx | |||
@@ -2,7 +2,7 @@ import React from 'react'; | |||
2 | import { makeStyles } from '@material-ui/core/styles'; | 2 | import { makeStyles } from '@material-ui/core/styles'; |
3 | import AppBar from '@material-ui/core/AppBar'; | 3 | import AppBar from '@material-ui/core/AppBar'; |
4 | import Box from '@material-ui/core/Box'; | 4 | import Box from '@material-ui/core/Box'; |
5 | import Fab from '@material-ui/core/Fab'; | 5 | import Button from '@material-ui/core/Button'; |
6 | import IconButton from '@material-ui/core/IconButton'; | 6 | import IconButton from '@material-ui/core/IconButton'; |
7 | import Toolbar from '@material-ui/core/Toolbar'; | 7 | import Toolbar from '@material-ui/core/Toolbar'; |
8 | import Typography from '@material-ui/core/Typography'; | 8 | import Typography from '@material-ui/core/Typography'; |
@@ -13,30 +13,32 @@ import Editor from './editor/Editor'; | |||
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: { | ||
17 | maxHeight: '100vh', | ||
18 | }, | ||
16 | menuButton: { | 19 | menuButton: { |
17 | marginRight: theme.spacing(2), | 20 | marginRight: theme.spacing(2), |
18 | }, | 21 | }, |
19 | title: { | 22 | title: { |
20 | flexGrow: 1, | 23 | flexGrow: 1, |
21 | }, | 24 | }, |
22 | fab: { | 25 | editorBox: { |
23 | position: 'fixed', | 26 | overflow: 'auto', |
24 | right: theme.spacing(3), | ||
25 | bottom: theme.spacing(3), | ||
26 | zIndex: 1000, | ||
27 | }, | 27 | }, |
28 | extendedIcon: { | ||
29 | marginRight: theme.spacing(1), | ||
30 | } | ||
31 | })); | 28 | })); |
32 | 29 | ||
33 | export default () => { | 30 | export default () => { |
34 | const classes = useStyles(); | 31 | const classes = useStyles(); |
35 | 32 | ||
36 | return ( | 33 | return ( |
37 | <> | 34 | <Box |
35 | display='flex' | ||
36 | flexDirection='column' | ||
37 | className={classes.container} | ||
38 | > | ||
38 | <AppBar | 39 | <AppBar |
39 | position='static' | 40 | position='static' |
41 | color='inherit' | ||
40 | > | 42 | > |
41 | <Toolbar> | 43 | <Toolbar> |
42 | <IconButton | 44 | <IconButton |
@@ -56,18 +58,35 @@ export default () => { | |||
56 | </Typography> | 58 | </Typography> |
57 | </Toolbar> | 59 | </Toolbar> |
58 | </AppBar> | 60 | </AppBar> |
59 | <Box> | 61 | <Box |
60 | <EditorButtons/> | 62 | display='flex' |
63 | justifyContent='space-between' | ||
64 | alignItems='center' | ||
65 | p={1} | ||
66 | > | ||
67 | <Box | ||
68 | display='flex' | ||
69 | alignItems='center' | ||
70 | > | ||
71 | <EditorButtons/> | ||
72 | </Box> | ||
73 | <Box> | ||
74 | <Button | ||
75 | variant='outlined' | ||
76 | color='primary' | ||
77 | startIcon={<PlayArrowIcon/>} | ||
78 | > | ||
79 | Generate | ||
80 | </Button> | ||
81 | </Box> | ||
61 | </Box> | 82 | </Box> |
62 | <Editor/> | 83 | <Box |
63 | <Fab | 84 | flexGrow={1} |
64 | variant='extended' | 85 | flexShrink={1} |
65 | color='secondary' | 86 | className={classes.editorBox} |
66 | className={classes.fab} | ||
67 | > | 87 | > |
68 | <PlayArrowIcon className={classes.extendedIcon}/> | 88 | <Editor/> |
69 | Generate | 89 | </Box> |
70 | </Fab> | 90 | </Box> |
71 | </> | ||
72 | ); | 91 | ); |
73 | }; | 92 | }; |
diff --git a/language-web/src/main/js/RootStore.jsx b/language-web/src/main/js/RootStore.jsx index 1ee2823d..a437fdd0 100644 --- a/language-web/src/main/js/RootStore.jsx +++ b/language-web/src/main/js/RootStore.jsx | |||
@@ -1,4 +1,3 @@ | |||
1 | import { makeAutoObservable } from 'mobx'; | ||
2 | import React, { createContext, useContext } from 'react'; | 1 | import React, { createContext, useContext } from 'react'; |
3 | 2 | ||
4 | import EditorStore from './editor/EditorStore'; | 3 | import EditorStore from './editor/EditorStore'; |
@@ -8,7 +7,6 @@ export default class RootStore { | |||
8 | 7 | ||
9 | constructor() { | 8 | constructor() { |
10 | this.editorStore = new EditorStore(); | 9 | this.editorStore = new EditorStore(); |
11 | makeAutoObservable(this); | ||
12 | } | 10 | } |
13 | } | 11 | } |
14 | 12 | ||
diff --git a/language-web/src/main/js/editor/Editor.jsx b/language-web/src/main/js/editor/Editor.jsx index c4b2e183..98cf2715 100644 --- a/language-web/src/main/js/editor/Editor.jsx +++ b/language-web/src/main/js/editor/Editor.jsx | |||
@@ -12,6 +12,8 @@ export default observer(() => { | |||
12 | const codeMirrorOptions = { | 12 | const codeMirrorOptions = { |
13 | mode: 'xtext/problem', | 13 | mode: 'xtext/problem', |
14 | indentUnit: 2, | 14 | indentUnit: 2, |
15 | theme: 'material-darker', | ||
16 | lineNumbers: editorStore.showLineNumbers, | ||
15 | }; | 17 | }; |
16 | 18 | ||
17 | const xtextOptions = { | 19 | const xtextOptions = { |
diff --git a/language-web/src/main/js/editor/EditorButtons.jsx b/language-web/src/main/js/editor/EditorButtons.jsx index b9f0d076..62f82f29 100644 --- a/language-web/src/main/js/editor/EditorButtons.jsx +++ b/language-web/src/main/js/editor/EditorButtons.jsx | |||
@@ -1,27 +1,70 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | 1 | import { observer } from 'mobx-react-lite'; |
2 | import React from 'react'; | 2 | import React from 'react'; |
3 | import IconButton from '@material-ui/core/IconButton'; | 3 | import { makeStyles } from '@material-ui/core/styles'; |
4 | import Button from '@material-ui/core/Button'; | ||
5 | import ButtonGroup from '@material-ui/core/ButtonGroup'; | ||
6 | import Divider from '@material-ui/core/Divider'; | ||
7 | import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered'; | ||
4 | import RedoIcon from '@material-ui/icons/Redo'; | 8 | import RedoIcon from '@material-ui/icons/Redo'; |
5 | import UndoIcon from '@material-ui/icons/Undo'; | 9 | import UndoIcon from '@material-ui/icons/Undo'; |
6 | 10 | import ToggleButton from '@material-ui/lab/ToggleButton'; | |
7 | import { useRootStore } from '../RootStore'; | 11 | import { useRootStore } from '../RootStore'; |
8 | 12 | ||
13 | const useStyles = makeStyles(theme => ({ | ||
14 | iconButton: { | ||
15 | padding: 8, | ||
16 | minWidth: 36, | ||
17 | '&.MuiButtonGroup-groupedTextHorizontal': { | ||
18 | borderRight: 0, | ||
19 | }, | ||
20 | }, | ||
21 | flatToggleButton: { | ||
22 | padding: 8, | ||
23 | border: 0, | ||
24 | color: theme.palette.text.primary, | ||
25 | }, | ||
26 | divider: { | ||
27 | margin: theme.spacing(0.5), | ||
28 | } | ||
29 | })); | ||
30 | |||
9 | export default observer(() => { | 31 | export default observer(() => { |
10 | const editorStore = useRootStore().editorStore; | 32 | const editorStore = useRootStore().editorStore; |
33 | const classes = useStyles(); | ||
11 | return ( | 34 | return ( |
12 | <> | 35 | <> |
13 | <IconButton | 36 | <ButtonGroup |
14 | disabled={!editorStore.canUndo} | 37 | variant='text' |
15 | onClick={() => editorStore.undo()} | ||
16 | > | 38 | > |
17 | <UndoIcon fontSize='small'/> | 39 | <Button |
18 | </IconButton> | 40 | disabled={!editorStore.canUndo} |
19 | <IconButton | 41 | onClick={() => editorStore.undo()} |
20 | disabled={!editorStore.canRedo} | 42 | className={classes.iconButton} |
21 | onClick={() => editorStore.redo()} | 43 | > |
44 | <UndoIcon fontSize='small'/> | ||
45 | </Button> | ||
46 | <Button | ||
47 | disabled={!editorStore.canRedo} | ||
48 | onClick={() => editorStore.redo()} | ||
49 | className={classes.iconButton} | ||
50 | > | ||
51 | <RedoIcon fontSize='small'/> | ||
52 | </Button> | ||
53 | </ButtonGroup> | ||
54 | <Divider | ||
55 | flexItem | ||
56 | orientation='vertical' | ||
57 | className={classes.divider} | ||
58 | /> | ||
59 | <ToggleButton | ||
60 | value='Show line numbers' | ||
61 | selected={editorStore.showLineNumbers} | ||
62 | onChange={() => editorStore.toggleLineNumbers()} | ||
63 | size='small' | ||
64 | className={classes.flatToggleButton} | ||
22 | > | 65 | > |
23 | <RedoIcon fontSize='small'/> | 66 | <FormatListNumberedIcon fontSize='small'/> |
24 | </IconButton> | 67 | </ToggleButton> |
25 | </> | 68 | </> |
26 | ); | 69 | ); |
27 | }); | 70 | }); |
diff --git a/language-web/src/main/js/editor/EditorStore.jsx b/language-web/src/main/js/editor/EditorStore.jsx index 9c286c28..6b03b383 100644 --- a/language-web/src/main/js/editor/EditorStore.jsx +++ b/language-web/src/main/js/editor/EditorStore.jsx | |||
@@ -7,6 +7,8 @@ export default class EditorStore { | |||
7 | editor = null; | 7 | editor = null; |
8 | /** @type {string} */ | 8 | /** @type {string} */ |
9 | value = ''; | 9 | value = ''; |
10 | /** @type {boolean} */ | ||
11 | showLineNumbers = false; | ||
10 | 12 | ||
11 | constructor() { | 13 | constructor() { |
12 | this.atom = createAtom('EditorStore'); | 14 | this.atom = createAtom('EditorStore'); |
@@ -72,4 +74,8 @@ export default class EditorStore { | |||
72 | redo() { | 74 | redo() { |
73 | this.editor.redo(); | 75 | this.editor.redo(); |
74 | } | 76 | } |
77 | |||
78 | toggleLineNumbers() { | ||
79 | this.showLineNumbers = !this.showLineNumbers; | ||
80 | } | ||
75 | } | 81 | } |
diff --git a/language-web/src/main/js/index.jsx b/language-web/src/main/js/index.jsx index 17e6aef2..7b807c77 100644 --- a/language-web/src/main/js/index.jsx +++ b/language-web/src/main/js/index.jsx | |||
@@ -46,7 +46,21 @@ scope Family = 1, Person += 5..10. | |||
46 | const rootStore = new RootStore(); | 46 | const rootStore = new RootStore(); |
47 | rootStore.editorStore.updateValue(initialValue); | 47 | rootStore.editorStore.updateValue(initialValue); |
48 | 48 | ||
49 | const theme = createMuiTheme(); | 49 | const theme = createMuiTheme({ |
50 | palette: { | ||
51 | type: 'dark', | ||
52 | background: { | ||
53 | default: '#212121', | ||
54 | paper: '#333333', | ||
55 | }, | ||
56 | primary: { | ||
57 | main: '#82aaff', | ||
58 | }, | ||
59 | secondary: { | ||
60 | main: '#ff5370', | ||
61 | }, | ||
62 | }, | ||
63 | }); | ||
50 | 64 | ||
51 | const app = ( | 65 | const app = ( |
52 | <ThemeProvider theme={theme}> | 66 | <ThemeProvider theme={theme}> |