From 70dd02be202ae1b87ef8f7a2563ba09a3e7b0947 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 21 Aug 2022 03:19:03 +0200 Subject: refactor(frontend): improve code splitting --- subprojects/frontend/src/App.tsx | 19 +++----- subprojects/frontend/src/Loading.tsx | 4 +- subprojects/frontend/src/RootStore.tsx | 21 +++++++-- subprojects/frontend/src/editor/EditorArea.tsx | 22 +++------ subprojects/frontend/src/editor/EditorButtons.tsx | 52 +++++++++++++--------- subprojects/frontend/src/editor/EditorPane.tsx | 38 ++++++++++++++++ subprojects/frontend/src/editor/EditorTheme.ts | 8 ++-- subprojects/frontend/src/editor/GenerateButton.tsx | 17 +++++-- .../frontend/src/editor/SearchPanelPortal.tsx | 25 +++++++++++ subprojects/frontend/src/editor/SearchToolbar.tsx | 46 +++++++++++-------- .../frontend/src/editor/createEditorState.ts | 9 ++-- .../frontend/src/editor/editorClassNames.ts | 10 ----- subprojects/frontend/src/index.tsx | 9 ++-- 13 files changed, 179 insertions(+), 101 deletions(-) create mode 100644 subprojects/frontend/src/editor/EditorPane.tsx create mode 100644 subprojects/frontend/src/editor/SearchPanelPortal.tsx delete mode 100644 subprojects/frontend/src/editor/editorClassNames.ts (limited to 'subprojects/frontend/src') diff --git a/subprojects/frontend/src/App.tsx b/subprojects/frontend/src/App.tsx index 12c66eb9..3a25f43a 100644 --- a/subprojects/frontend/src/App.tsx +++ b/subprojects/frontend/src/App.tsx @@ -1,23 +1,14 @@ -import Box from '@mui/material/Box'; -import Toolbar from '@mui/material/Toolbar'; +import Stack from '@mui/material/Stack'; import React from 'react'; import TopBar from './TopBar'; -import EditorArea from './editor/EditorArea'; -import EditorButtons from './editor/EditorButtons'; -import GenerateButton from './editor/GenerateButton'; +import EditorPane from './editor/EditorPane'; export default function App(): JSX.Element { return ( - + - - - - - - - - + + ); } diff --git a/subprojects/frontend/src/Loading.tsx b/subprojects/frontend/src/Loading.tsx index a699adca..72020a43 100644 --- a/subprojects/frontend/src/Loading.tsx +++ b/subprojects/frontend/src/Loading.tsx @@ -3,8 +3,8 @@ import { styled } from '@mui/material/styles'; import React from 'react'; const LoadingRoot = styled('div')({ - width: '100vw', - height: '100vh', + width: '100%', + height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', diff --git a/subprojects/frontend/src/RootStore.tsx b/subprojects/frontend/src/RootStore.tsx index 4a267b0e..e08dd750 100644 --- a/subprojects/frontend/src/RootStore.tsx +++ b/subprojects/frontend/src/RootStore.tsx @@ -1,16 +1,31 @@ +import { getLogger } from 'loglevel'; +import { makeObservable, observable, runInAction } from 'mobx'; import React, { createContext, useContext } from 'react'; -import EditorStore from './editor/EditorStore'; +import type EditorStore from './editor/EditorStore'; import ThemeStore from './theme/ThemeStore'; +const log = getLogger('RootStore'); + export default class RootStore { - readonly editorStore: EditorStore; + editorStore: EditorStore | undefined; readonly themeStore: ThemeStore; constructor(initialValue: string) { - this.editorStore = new EditorStore(initialValue); this.themeStore = new ThemeStore(); + makeObservable(this, { + editorStore: observable, + }); + import('./editor/EditorStore') + .then(({ default: EditorStore }) => { + runInAction(() => { + this.editorStore = new EditorStore(initialValue); + }); + }) + .catch((error) => { + log.error('Failed to load EditorStore', error); + }); } } diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx index 915ec657..1c9b031b 100644 --- a/subprojects/frontend/src/editor/EditorArea.tsx +++ b/subprojects/frontend/src/editor/EditorArea.tsx @@ -1,17 +1,15 @@ -import Portal from '@mui/material/Portal'; import { useTheme } from '@mui/material/styles'; import { observer } from 'mobx-react-lite'; import React, { useCallback, useEffect } from 'react'; -import { useRootStore } from '../RootStore'; - +import type EditorStore from './EditorStore'; import EditorTheme from './EditorTheme'; -import SearchToolbar from './SearchToolbar'; -function EditorArea(): JSX.Element { - const { editorStore } = useRootStore(); - const { searchPanel: searchPanelStore } = editorStore; - const { element: searchPanelContainer } = searchPanelStore; +function EditorArea({ + editorStore, +}: { + editorStore: EditorStore; +}): JSX.Element { const { palette: { mode: paletteMode }, } = useTheme(); @@ -32,13 +30,7 @@ function EditorArea(): JSX.Element { - {searchPanelContainer !== undefined && ( - - - - )} - + /> ); } diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx index 8db9dfb7..cbe7c424 100644 --- a/subprojects/frontend/src/editor/EditorButtons.tsx +++ b/subprojects/frontend/src/editor/EditorButtons.tsx @@ -15,7 +15,7 @@ import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import { observer } from 'mobx-react-lite'; import React from 'react'; -import { useRootStore } from '../RootStore'; +import type EditorStore from './EditorStore'; // Exhastive switch as proven by TypeScript. // eslint-disable-next-line consistent-return @@ -32,22 +32,24 @@ function getLintIcon(severity: Diagnostic['severity'] | undefined) { } } -function EditorButtons(): JSX.Element { - const { editorStore } = useRootStore(); - +function EditorButtons({ + editorStore, +}: { + editorStore: EditorStore | undefined; +}): JSX.Element { return ( editorStore.undo()} + disabled={editorStore === undefined || !editorStore.canUndo} + onClick={() => editorStore?.undo()} aria-label="Undo" color="inherit" > editorStore.redo()} + disabled={editorStore === undefined || !editorStore.canRedo} + onClick={() => editorStore?.redo()} aria-label="Redo" color="inherit" > @@ -55,38 +57,44 @@ function EditorButtons(): JSX.Element { editorStore.toggleLineNumbers()} + selected={editorStore?.showLineNumbers ?? false} + disabled={editorStore === undefined} + onClick={() => editorStore?.toggleLineNumbers()} aria-label="Show line numbers" value="show-line-numbers" > editorStore.searchPanel.toggle()} + selected={editorStore?.searchPanel?.state ?? false} + disabled={editorStore === undefined} + onClick={() => editorStore?.searchPanel?.toggle()} aria-label="Show find/replace" - {...(editorStore.searchPanel.state && { - 'aria-controls': editorStore.searchPanel.id, - })} + {...(editorStore !== undefined && + editorStore.searchPanel.state && { + 'aria-controls': editorStore.searchPanel.id, + })} value="show-search-panel" > editorStore.lintPanel.toggle()} + selected={editorStore?.lintPanel?.state ?? false} + disabled={editorStore === undefined} + onClick={() => editorStore?.lintPanel.toggle()} aria-label="Show diagnostics panel" - {...(editorStore.lintPanel.state && { - 'aria-controls': editorStore.lintPanel.id, - })} + {...(editorStore !== undefined && + editorStore.lintPanel.state && { + 'aria-controls': editorStore.lintPanel.id, + })} value="show-lint-panel" > - {getLintIcon(editorStore.highestDiagnosticLevel)} + {getLintIcon(editorStore?.highestDiagnosticLevel)} editorStore.formatText()} + disabled={editorStore === undefined} + onClick={() => editorStore?.formatText()} aria-label="Automatic format" color="inherit" > diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx new file mode 100644 index 00000000..935a84e8 --- /dev/null +++ b/subprojects/frontend/src/editor/EditorPane.tsx @@ -0,0 +1,38 @@ +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Toolbar from '@mui/material/Toolbar'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import Loading from '../Loading'; +import { useRootStore } from '../RootStore'; + +import EditorArea from './EditorArea'; +import EditorButtons from './EditorButtons'; +import GenerateButton from './GenerateButton'; +import SearchPanelPortal from './SearchPanelPortal'; + +function EditorPane(): JSX.Element { + const { editorStore } = useRootStore(); + + return ( + + + + + + + {editorStore === undefined ? ( + + ) : ( + <> + + + + )} + + + ); +} + +export default observer(EditorPane); diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index d8680070..ca07d602 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts @@ -4,8 +4,6 @@ import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; import { alpha, styled, type CSSObject } from '@mui/material/styles'; -import editorClassNames from './editorClassNames'; - function svgURL(svg: string): string { return `url('data:image/svg+xml;utf8,${svg}')`; } @@ -315,7 +313,7 @@ export default styled('div', { '.cm-gutters:hover .cm-foldGutter': { opacity: 1, }, - [`.${editorClassNames.foldMarker}`]: { + '.problem-editor-foldMarker': { display: 'block', margin: '4px 0', padding: 0, @@ -330,10 +328,10 @@ export default styled('div', { margin: '2px 0', }, }, - [`.${editorClassNames.foldMarkerClosed}`]: { + '.problem-editor-foldMarker-closed': { transform: 'rotate(-90deg)', }, - [`.${editorClassNames.foldPlaceholder}`]: { + '.problem-editor-foldPlaceholder': { ...editorFontStyle, padding: 0, fontFamily: 'inherit', diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx index 4b998af6..a28f6b4b 100644 --- a/subprojects/frontend/src/editor/GenerateButton.tsx +++ b/subprojects/frontend/src/editor/GenerateButton.tsx @@ -3,12 +3,23 @@ import Button from '@mui/material/Button'; import { observer } from 'mobx-react-lite'; import React from 'react'; -import { useRootStore } from '../RootStore'; +import type EditorStore from './EditorStore'; const GENERATE_LABEL = 'Generate'; -function GenerateButton(): JSX.Element { - const { editorStore } = useRootStore(); +function GenerateButton({ + editorStore, +}: { + editorStore: EditorStore | undefined; +}): JSX.Element { + if (editorStore === undefined) { + return ( + + ); + } + const { errorCount, warningCount } = editorStore; const diagnostics: string[] = []; diff --git a/subprojects/frontend/src/editor/SearchPanelPortal.tsx b/subprojects/frontend/src/editor/SearchPanelPortal.tsx new file mode 100644 index 00000000..e8301489 --- /dev/null +++ b/subprojects/frontend/src/editor/SearchPanelPortal.tsx @@ -0,0 +1,25 @@ +import Portal from '@mui/material/Portal'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import type EditorStore from './EditorStore'; +import SearchToolbar from './SearchToolbar'; + +function SearchPanelPortal({ + editorStore: { searchPanel: searchPanelStore }, +}: { + editorStore: EditorStore; +}): JSX.Element | null { + const { element: searchPanelContainer } = searchPanelStore; + + if (searchPanelContainer === undefined) { + return null; + } + return ( + + + + ); +} + +export default observer(SearchPanelPortal); diff --git a/subprojects/frontend/src/editor/SearchToolbar.tsx b/subprojects/frontend/src/editor/SearchToolbar.tsx index 45f1336d..7e6ff4f7 100644 --- a/subprojects/frontend/src/editor/SearchToolbar.tsx +++ b/subprojects/frontend/src/editor/SearchToolbar.tsx @@ -20,12 +20,16 @@ import type SearchPanelStore from './SearchPanelStore'; const SPLIT_MEDIA_QUERY = '@media (max-width: 1200px)'; const ABBREVIATE_MEDIA_QUERY = '@media (max-width: 720px)'; -function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { +function SearchToolbar({ + searchPanelStore, +}: { + searchPanelStore: SearchPanelStore; +}): JSX.Element { const { id: panelId, query: { search, valid, caseSensitive, literal, regexp, replace }, invalidRegexp, - } = store; + } = searchPanelStore; const split = useMediaQuery(SPLIT_MEDIA_QUERY); const abbreviate = useMediaQuery(ABBREVIATE_MEDIA_QUERY); const [showRepalceState, setShowReplaceState] = useState(false); @@ -37,8 +41,8 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { const searchFieldRef = useCallback( (element: HTMLInputElement | null) => - store.setSearchField(element ?? undefined), - [store], + searchPanelStore.setSearchField(element ?? undefined), + [searchPanelStore], ); return ( @@ -68,15 +72,15 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { value={search} error={invalidRegexp} onChange={(event) => - store.updateQuery({ search: event.target.value }) + searchPanelStore.updateQuery({ search: event.target.value }) } onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); if (event.shiftKey) { - store.findPrevious(); + searchPanelStore.findPrevious(); } else { - store.findNext(); + searchPanelStore.findNext(); } } }} @@ -108,7 +112,7 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { store.findPrevious()} + onClick={() => searchPanelStore.findPrevious()} color="inherit" > @@ -116,7 +120,7 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { store.findNext()} + onClick={() => searchPanelStore.findNext()} color="inherit" > @@ -133,7 +137,9 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { - store.updateQuery({ caseSensitive: event.target.checked }) + searchPanelStore.updateQuery({ + caseSensitive: event.target.checked, + }) } size="small" /> @@ -146,7 +152,9 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { - store.updateQuery({ literal: event.target.checked }) + searchPanelStore.updateQuery({ + literal: event.target.checked, + }) } size="small" /> @@ -159,7 +167,9 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { - store.updateQuery({ regexp: event.target.checked }) + searchPanelStore.updateQuery({ + regexp: event.target.checked, + }) } size="small" /> @@ -172,7 +182,7 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { selected={showReplace} onClick={() => { if (showReplace) { - store.updateQuery({ replace: '' }); + searchPanelStore.updateQuery({ replace: '' }); setShowReplaceState(false); } else { setShowReplaceState(true); @@ -201,12 +211,12 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { aria-label="Replace with" value={replace} onChange={(event) => - store.updateQuery({ replace: event.target.value }) + searchPanelStore.updateQuery({ replace: event.target.value }) } onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); - store.replaceNext(); + searchPanelStore.replaceNext(); } }} variant="standard" @@ -221,7 +231,7 @@ function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element { >