Skip to content

Commit

Permalink
feat(frontend): change to use monaco editor
Browse files Browse the repository at this point in the history
  • Loading branch information
SARDONYX-sard committed May 30, 2024
1 parent cb47e09 commit 3313bbd
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 155 deletions.
9 changes: 9 additions & 0 deletions dar2oar_gui/frontend/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2023 Luma <lumakernel@gmail.com>
// SPDX-License-Identifier: MIT or Apache-2.0
declare module 'monaco-vim' {
class VimMode {
dispose(): void;
initVimMode(editor: editor.IStandaloneCodeEditor, statusbarNode?: Element | null): VimMode;
}
export function initVimMode(editor: editor.IStandaloneCodeEditor, statusbarNode?: HTMLElement): VimMode;
}
49 changes: 49 additions & 0 deletions dar2oar_gui/frontend/src/app/client_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2023 Luma <lumakernel@gmail.com>
// SPDX-License-Identifier: MIT or Apache-2.0
'use client';
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import NextLink from 'next/link';

import Menu from '@/components/navigation';
import SnackBarProvider from '@/components/providers/snackbar';

import type { ComponentProps, ReactNode } from 'react';

const darkTheme = createTheme({
palette: {
mode: 'dark',
},
components: {
// biome-ignore lint/style/useNamingConvention: <explanation>
MuiLink: {
defaultProps: {
component: (props: ComponentProps<typeof NextLink>) => <NextLink {...props} />,
},
},
// biome-ignore lint/style/useNamingConvention: <explanation>
MuiButtonBase: {
defaultProps: {
// biome-ignore lint/style/useNamingConvention: <explanation>
LinkComponent: (props: ComponentProps<typeof NextLink>) => <NextLink {...props} />,
},
},
},
});

interface ClientLayoutProps {
children: ReactNode;
}
const queryClient = new QueryClient();
export default function ClientLayout({ children }: Readonly<ClientLayoutProps>) {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={darkTheme}>
<SnackBarProvider />
<CssBaseline />
{children}
<Menu />
</ThemeProvider>
</QueryClientProvider>
);
}
5 changes: 5 additions & 0 deletions dar2oar_gui/frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ a {
color-scheme: dark;
}
}

.monaco-editor,
.monaco-editor-background {
background-color: #2424248c !important;
}
17 changes: 3 additions & 14 deletions dar2oar_gui/frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,7 @@ import '@/app/globals.css';

const inter = Inter({ subsets: ['latin'] });

const Menu = dynamic(() => import('@/components/navigation'), {
loading: () => <Loading />,
ssr: false,
});

const ThemeProvider = dynamic(() => import('@/components/providers/theme'), {
loading: () => <Loading />,
ssr: false,
});
const SnackBarProvider = dynamic(() => import('@/components/providers/snackbar'), {
const ClientLayout = dynamic(() => import('@/app/client_layout'), {
loading: () => <Loading />,
ssr: false,
});
Expand All @@ -37,13 +28,11 @@ export default function RootLayout({ children }: Props) {
return (
<html lang='en'>
<body className={inter.className}>
<ThemeProvider>
<SnackBarProvider />
<ClientLayout>
{children}
{/* To prevents the conversion button from being hidden because the menu is fixed. */}
<div style={{ height: '56px' }} />
<Menu />
</ThemeProvider>
</ClientLayout>
</body>
</html>
);
Expand Down
63 changes: 63 additions & 0 deletions dar2oar_gui/frontend/src/components/editor/code_editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2023 Luma <lumakernel@gmail.com>
// SPDX-License-Identifier: MIT or Apache-2.0
//
// issue: https://github.com/suren-atoyan/monaco-react/issues/136#issuecomment-731420078
'use client';
import Editor, { type OnMount } from '@monaco-editor/react';
import { type ComponentProps, memo, useCallback, useRef } from 'react';

import type monaco from 'monaco-editor/esm/vs/editor/editor.api';
import type { VimMode } from 'monaco-vim';

const loadVimKeyBindings: OnMount = (editor, monaco) => {
// setup key bindings before monaco-vim setup

// setup key bindings
editor.addAction({
// an unique identifier of the contributed action
id: 'some-unique-id',
// a label of the action that will be presented to the user
label: 'Some label!',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],

// the method that will be executed when the action is triggered.
run: (editor) => {
alert(`we wanna save something => ${editor.getValue()}`);
},
});

// setup monaco-vim
window.require.config({
paths: {
'monaco-vim': 'https://unpkg.com/monaco-vim/dist/monaco-vim',
},
});

window.require(['monaco-vim'], (monacoVim: VimMode) => {
const statusNode = document.querySelector('#status-node');
monacoVim.initVimMode(editor, statusNode);
});
};

export type CodeEditorProps = ComponentProps<typeof Editor> & {
readonly vimMode?: boolean;
};
function CodeEditorInternal({ vimMode = false, onMount, ...params }: CodeEditorProps) {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);

const handleDidMount: OnMount = useCallback(
(editor, monaco) => {
editorRef.current = editor;
if (vimMode) {
loadVimKeyBindings(editor, monaco);
}

onMount?.(editor, monaco);
},
[onMount, vimMode],
);

return <Editor key='' theme='vs-dark' {...params} onMount={handleDidMount} />;
}

export const CodeEditor = memo(CodeEditorInternal);
49 changes: 23 additions & 26 deletions dar2oar_gui/frontend/src/components/editor/css_editor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import InputLabel from '@mui/material/InputLabel';
import AceEditor from 'react-ace';

import { CodeEditor } from '@/components/editor';
import { useTranslation } from '@/hooks';
import { selectEditorMode } from '@/utils/selector';

export type CssEditorProps = {
editorMode: string;
Expand All @@ -14,34 +13,32 @@ export type CssEditorProps = {
export const CssEditor = ({ editorMode, setPreset, setStyle, style }: CssEditorProps) => {
const { t } = useTranslation();

const handleCodeChange = (newValue: string | undefined) => {
const value = newValue ?? '';
setStyle(value);
localStorage.setItem('customCSS', value);
setPreset('0');
};

return (
<>
<InputLabel sx={{ marginTop: '20px' }}>{t('custom-css-label')}</InputLabel>
<AceEditor
editorProps={{ $blockScrolling: true }}
enableBasicAutocompletion
enableLiveAutocompletion
enableSnippets
fontSize={'1rem'}
height='300px'
highlightActiveLine
keyboardHandler={selectEditorMode(editorMode)}
mode='css'
name='Custom CSS'
onChange={(value) => {
setStyle(value);
localStorage.setItem('customCSS', value);
setPreset('0');
}}
placeholder="{ body: url('https://localhost' }"
setOptions={{ useWorker: false }}
style={{
width: '95%',
backgroundColor: '#2424248c',
<CodeEditor
defaultValue={style}
height='260px'
language={'css'}
onChange={handleCodeChange}
options={{
suggestOnTriggerCharacters: true,
renderWhitespace: 'boundary',
rulers: [120],
hover: {
above: true,
},
}}
tabSize={2}
theme='one_dark'
value={style}
theme='vs-dark'
vimMode={editorMode === 'vim'}
width={'95%'}
/>
</>
);
Expand Down
10 changes: 1 addition & 9 deletions dar2oar_gui/frontend/src/components/editor/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
// @index('./*', f => `export * from '${f.path}'`)
export * from './css_editor';
export * from './js_editor';

// NOTE: These extensions must be loaded after importing AceEditor or they will error
import 'ace-builds/src-min-noconflict/ext-language_tools'; // For completion
import 'ace-builds/src-min-noconflict/keybinding-vim';
import 'ace-builds/src-min-noconflict/mode-css';
import 'ace-builds/src-min-noconflict/mode-javascript';
import 'ace-builds/src-min-noconflict/snippets/css';
import 'ace-builds/src-min-noconflict/snippets/javascript';
import 'ace-builds/src-min-noconflict/theme-one_dark';
export * from './code_editor';
48 changes: 18 additions & 30 deletions dar2oar_gui/frontend/src/components/editor/js_editor.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';
import { Checkbox, FormControlLabel, Grid, Tooltip } from '@mui/material';
import InputLabel from '@mui/material/InputLabel';
import AceEditor from 'react-ace';

import { CodeEditor } from '@/components/editor';
import { useInjectScript, useStorageState, useTranslation } from '@/hooks';
import { selectEditorMode } from '@/utils/selector';

export type JsEditorProps = {
editorMode: string;
Expand All @@ -12,7 +12,7 @@ export type JsEditorProps = {
export const JsEditor = ({ editorMode, marginTop }: JsEditorProps) => {
const { t } = useTranslation();

const [script, setScript] = useInjectScript();
const [script, handleCodeChange] = useInjectScript();
const [runScript, setRunScript] = useStorageState('runScript', 'false');

return (
Expand Down Expand Up @@ -56,35 +56,23 @@ export const JsEditor = ({ editorMode, marginTop }: JsEditorProps) => {
</Tooltip>
</Grid>

<AceEditor
editorProps={{ $blockScrolling: true }}
enableBasicAutocompletion
enableLiveAutocompletion
enableSnippets
fontSize={'1rem'}
height='250px'
highlightActiveLine
keyboardHandler={selectEditorMode(editorMode)}
mode='javascript'
name='Custom JavaScript'
onChange={(value) => {
localStorage.setItem('customJS', value);
setScript(value);
<CodeEditor
defaultValue={script}
height='280px'
language={'javascript'}
onChange={handleCodeChange}
options={{
renderWhitespace: 'boundary',
rulers: [120],
hover: {
above: true,
},
}}
placeholder={`(()=> {
const p = document.createElement('p');
p.innerText = 'Hello';
document.body.appendChild(p);
)()`}
setOptions={{ useWorker: false }}
style={{
width: '95%',
backgroundColor: '#2424248c',
}}
tabSize={2}
theme='one_dark'
value={script}
theme='vs-dark'
vimMode={editorMode === 'vim'}
width={'95%'}
/>
<InputLabel id='status-node' sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }} />
</>
);
};
15 changes: 9 additions & 6 deletions dar2oar_gui/frontend/src/components/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import { Box, Button, Grid } from '@mui/material';
import { Box, Button, Grid, Skeleton } from '@mui/material';
import Tab from '@mui/material/Tab';
import { Suspense, type SyntheticEvent } from 'react';

import { ExportBackupButton, ImportBackupButton, ImportLangButton } from '@/components/buttons';
import { CssEditor, type CssEditorProps, JsEditor } from '@/components/editor';
Expand All @@ -22,16 +23,14 @@ import { type EditorMode, selectEditorMode } from '@/utils/selector';

import packageJson from '@/../../package.json';

import type { SyntheticEvent } from 'react';

export default function Settings() {
useLocale();
const [editorMode, setEditorMode] = useStorageState('editorMode', 'default');
const [preset, setPreset] = useStorageState('presetNumber', '0');
const [style, setStyle] = useDynStyle();
const validEditorMode = selectEditorMode(editorMode);

const setEditorKeyMode = (editorMode: EditorMode) => setEditorMode(editorMode ?? 'default');
const setEditorKeyMode = (editorMode: EditorMode) => setEditorMode(editorMode);
return (
<Box
component='main'
Expand All @@ -44,8 +43,12 @@ export default function Settings() {
width: '100%',
}}
>
<CssEditor editorMode={validEditorMode} setPreset={setPreset} setStyle={setStyle} style={style} />
<JsEditor editorMode={validEditorMode} marginTop={'20px'} />
<Suspense fallback={<Skeleton />}>
<CssEditor editorMode={validEditorMode} setPreset={setPreset} setStyle={setStyle} style={style} />
</Suspense>
<Suspense fallback={<Skeleton />}>
<JsEditor editorMode={validEditorMode} marginTop={'20px'} />
</Suspense>

<Grid container sx={{ width: '95%' }}>
<Grid sx={{ overflowX: 'auto' }} xs={8}>
Expand Down
28 changes: 0 additions & 28 deletions dar2oar_gui/frontend/src/components/providers/theme.tsx

This file was deleted.

Loading

0 comments on commit 3313bbd

Please sign in to comment.