refactor(editor): replace codemirror with react-codemirror2

This commit is contained in:
Bowen Tan 2022-08-23 13:56:44 +08:00
parent ab60a14972
commit 373cfb9ba9
11 changed files with 106 additions and 195 deletions

View File

@ -57,6 +57,7 @@
"mobx-react-lite": "^3.2.2",
"re-resizable": "^6.9.5",
"react": "^17.0.2",
"react-codemirror2": "^7.2.1",
"react-dom": "^17.0.2",
"react-json-tree": "^0.16.1",
"scroll-into-view": "^1.16.2",

View File

@ -0,0 +1,53 @@
import React, { useState } from 'react';
import { UnControlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/mode/css/css';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/display/autorefresh';
import type { ModeSpec, ModeSpecOptions } from 'codemirror';
// Result
import 'codemirror/mode/javascript/javascript';
import { css, cx } from '@emotion/css';
export const CodeEditor: React.FC<{
defaultCode: string;
className?: string;
mode?: string | ModeSpec<ModeSpecOptions>;
onChange?: (v: string) => void;
onBlur?: (v: string) => void;
}> = ({ defaultCode, mode, className, onChange, onBlur }) => {
const [value, setValue] = useState(defaultCode);
const style = css`
.CodeMirror {
height: 100%;
}
`;
return (
<CodeMirror
className={cx([style, className])}
value={value}
onChange={(_x, _y, v: string) => {
setValue(v);
onChange?.(v);
}}
onBlur={() => {
setValue(value);
onBlur?.(value);
}}
options={{
mode: mode || 'css',
foldGutter: true,
lineWrapping: true,
lineNumbers: false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
foldOptions: {
widget: () => {
return '\u002E\u002E\u002E';
},
},
theme: 'ayu-mirage',
}}
/>
);
};

View File

@ -1,65 +0,0 @@
import React, { useEffect, useRef } from 'react';
import CodeMirror from 'codemirror';
import { Box } from '@chakra-ui/react';
import { css } from '@emotion/css';
import 'codemirror/mode/css/css';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/display/autorefresh';
export const CssEditor: React.FC<{
defaultCode: string;
onChange?: (v: string) => void;
onBlur?: (v: string) => void;
}> = ({ defaultCode, onChange, onBlur }) => {
const style = css`
width: 100%;
.CodeMirror {
width: 100%;
height: 120px;
}
`;
const wrapperEl = useRef<HTMLDivElement>(null);
const cm = useRef<CodeMirror.Editor | null>(null);
useEffect(() => {
if (!wrapperEl.current) {
return;
}
if (!cm.current) {
cm.current = CodeMirror(wrapperEl.current, {
value: defaultCode,
mode: {
name: 'css',
},
foldGutter: true,
lineWrapping: true,
lineNumbers: false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
foldOptions: {
widget: () => {
return '\u002E\u002E\u002E';
},
},
theme: 'ayu-mirage',
autoRefresh: { delay: 50 },
});
} else {
cm.current.setValue(defaultCode);
}
const changeHandler = (instance: CodeMirror.Editor) => {
onChange?.(instance.getValue());
};
const blurHandler = (instance: CodeMirror.Editor) => {
onBlur?.(instance.getValue());
};
cm.current.on('change', changeHandler);
cm.current.on('blur', blurHandler);
return () => {
cm.current?.off('change', changeHandler);
cm.current?.off('blur', blurHandler);
};
}, [onBlur, onChange, defaultCode]);
return <Box className={style} ref={wrapperEl} />;
};

View File

@ -1,67 +0,0 @@
import React, { useEffect, useRef } from 'react';
import CodeMirror from 'codemirror';
import { Box } from '@chakra-ui/react';
import { css } from '@emotion/css';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/display/autorefresh';
export const SchemaEditor: React.FC<{
defaultCode: string;
onChange: (v: string) => void;
}> = ({ defaultCode, onChange }) => {
const style = css`
.CodeMirror {
width: 100%;
height: 100%;
}
`;
const wrapperEl = useRef<HTMLDivElement>(null);
const cm = useRef<CodeMirror.Editor | null>(null);
useEffect(() => {
if (!wrapperEl.current) {
return;
}
if (!cm.current) {
cm.current = CodeMirror(wrapperEl.current, {
value: defaultCode,
mode: {
name: 'javascript',
json: true,
},
foldGutter: true,
lineWrapping: true,
lineNumbers: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
foldOptions: {
widget: () => {
return '\u002E\u002E\u002E';
},
},
theme: 'ayu-mirage',
/**
* Codemirror has a serach addon which can search all the content
* without render all.
* But it's search behavior is differnet with popular code editors
* and the native UX of the browser:
* https://github.com/codemirror/CodeMirror/issues/4491#issuecomment-284741358
* So since our schema is not that large, currently we will render
* all content to support native search.
*/
viewportMargin: Infinity,
autoRefresh: { delay: 50 },
});
}
const handler = (instance: CodeMirror.Editor) => {
onChange(instance.getValue());
};
cm.current.on('change', handler);
return () => {
cm.current?.off('change', handler);
};
}, [defaultCode, onChange]);
return <Box className={style} ref={wrapperEl} height="100%" width="100%" />;
};

View File

@ -1,3 +1,2 @@
export * from './StateViewer';
export * from './SchemaEditor';
export * from './CssEditor';
export * from './CodeEditor';

View File

@ -13,9 +13,10 @@ import {
import { observer } from 'mobx-react-lite';
import { genOperation } from '../operations';
import { AppModel } from '../AppModel/AppModel';
import { SchemaEditor } from './CodeEditor';
import { EditorServices } from '../types';
import { Application } from '@sunmao-ui/core';
import { CodeEditor } from './CodeEditor/CodeEditor';
import { css } from '@emotion/css';
type Props = {
app: Application;
@ -46,8 +47,18 @@ export const CodeModeModal: React.FC<Props> = observer(({ app, onClose, services
<ModalHeader>Code Mode</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Box overflow="auto" minHeight="600px" maxHeight="1000px">
<SchemaEditor defaultCode={JSON.stringify(app, null, 2)} onChange={setCode} />
<Box overflow="auto" height="calc(100vh - 270px)">
<CodeEditor
className={css`
height: 100%;
`}
mode={{
name: 'javascript',
json: true,
}}
defaultCode={JSON.stringify(app, null, 2)}
onChange={setCode}
/>
</Box>
</ModalBody>
<ModalFooter>

View File

@ -21,10 +21,11 @@ import { ComponentSchema } from '@sunmao-ui/core';
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
import { FontWidget, SizeWidget, ColorWidget, SpaceWidget } from '@sunmao-ui/editor-sdk';
import { capitalize } from 'lodash';
import { CssEditor } from '../../../components/CodeEditor';
import { genOperation } from '../../../operations';
import { formWrapperCSS } from '../style';
import { EditorServices } from '../../../types';
import { CodeEditor } from '../../CodeEditor';
import { css } from '@emotion/css';
type PartialCSSProperties = Partial<Record<keyof React.CSSProperties, any>>;
@ -258,7 +259,15 @@ export const StyleTraitForm: React.FC<Props> = props => {
</Box>
</CollapsibleFormControl>
<CollapsibleFormControl label="CSS">
<CssEditor defaultCode={style} onBlur={v => changeStyleContent(i, v)} />
<CodeEditor
className={css`
width: 100%;
height: 120px;
`}
mode="css"
defaultCode={style}
onBlur={v => changeStyleContent(i, v)}
/>
</CollapsibleFormControl>
</VStack>
</AccordionPanel>

View File

@ -10,7 +10,8 @@ import {
Spinner,
Tag,
} from '@chakra-ui/react';
import { Result } from './Result';
import { CodeEditor } from '../../CodeEditor';
import { css } from '@emotion/css';
interface Props {
data?: unknown;
@ -56,7 +57,21 @@ export const Response: React.FC<Props> = props => {
</h2>
<AccordionPanel pb={4} padding={0} height="250px">
<Flex alignItems="center" justifyContent="center" height="100%">
{props.loading ? <Spinner /> : <Result defaultCode={error || data} />}
{props.loading ? (
<Spinner />
) : (
<CodeEditor
className={css`
width: 100%;
height: 100%;
`}
mode={{
name: 'javascript',
json: true,
}}
defaultCode={error || data}
/>
)}
</Flex>
</AccordionPanel>
</AccordionItem>

View File

@ -1,54 +0,0 @@
import React, { useEffect, useRef } from 'react';
import CodeMirror from 'codemirror';
import { Box } from '@chakra-ui/react';
import { css } from '@emotion/css';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/display/autorefresh';
export const Result: React.FC<{
defaultCode: string;
}> = ({ defaultCode }) => {
const style = css`
.CodeMirror {
width: 100%;
height: 100%;
}
`;
const wrapperEl = useRef<HTMLDivElement>(null);
const cm = useRef<CodeMirror.Editor | null>(null);
useEffect(() => {
if (!wrapperEl.current) {
return;
}
if (!cm.current) {
cm.current = CodeMirror(wrapperEl.current, {
value: defaultCode,
mode: {
name: 'javascript',
json: true,
},
foldGutter: true,
lineWrapping: true,
lineNumbers: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
foldOptions: {
widget: () => {
return '\u002E\u002E\u002E';
},
},
viewportMargin: Infinity,
readOnly: true,
autoRefresh: { delay: 50 },
});
}
setTimeout(() => {
cm.current?.refresh();
});
}, [defaultCode]);
return <Box className={style} ref={wrapperEl} height="100%" width="100%" />;
};

View File

@ -15,6 +15,10 @@ export default defineConfig({
esbuild: {
logOverride: { 'this-is-undefined-in-esm': 'silent' },
},
define: {
// react-codemirror2 need this
global: 'globalThis',
},
build: {
rollupOptions: {
input: {

View File

@ -10846,6 +10846,11 @@ react-clientside-effect@^1.2.5:
dependencies:
"@babel/runtime" "^7.12.13"
react-codemirror2@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c"
integrity sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==
react-color@^2.19.3:
version "2.19.3"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"