243 lines
8.1 KiB
TypeScript
243 lines
8.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import AceEditor from 'react-ace';
|
|
import 'brace/theme/tomorrow';
|
|
import 'brace/ext/language_tools';
|
|
|
|
import Style from './CodeEditor.less';
|
|
import Dialog from '../../components/Dialog';
|
|
import { Select, Option } from '../../components/Select';
|
|
import Button from '../../components/Button';
|
|
|
|
const languages = [
|
|
'javascript',
|
|
'typescript',
|
|
'text',
|
|
'java',
|
|
'c_cpp',
|
|
'python',
|
|
'ruby',
|
|
'php',
|
|
'golang',
|
|
'csharp',
|
|
'html',
|
|
'css',
|
|
'sql',
|
|
'json',
|
|
];
|
|
|
|
interface LoadedLanguage {
|
|
[key: string]: boolean;
|
|
}
|
|
const loadedLanguage: LoadedLanguage = {};
|
|
|
|
interface CodeEditorProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
onSend: (language: string, code: string) => void;
|
|
}
|
|
|
|
function CodeEditor(props: CodeEditorProps) {
|
|
const { visible, onClose, onSend } = props;
|
|
|
|
const [language, setLanguage] = useState('javascript');
|
|
const [value, setValue] = useState('');
|
|
const [timestamp, setTimestamp] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
setValue('');
|
|
}
|
|
}, [visible]);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
// 动态加载语言包
|
|
if (visible && !loadedLanguage[language]) {
|
|
/**
|
|
* 为了减小打包产物体积, 这里是逐个引入所需的语音包
|
|
* 如果直接用变量路径, 会将该目录下的文件全部打包
|
|
* 另外不单个引入也不好设置 webpackChunkName
|
|
*/
|
|
switch (language) {
|
|
case 'javascript': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-javascript" */ 'brace/mode/javascript'
|
|
);
|
|
break;
|
|
}
|
|
case 'typescript': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-typescript" */ 'brace/mode/typescript'
|
|
);
|
|
break;
|
|
}
|
|
case 'java': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-java" */ 'brace/mode/java'
|
|
);
|
|
break;
|
|
}
|
|
case 'c_cpp': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-c_cpp" */ 'brace/mode/c_cpp'
|
|
);
|
|
break;
|
|
}
|
|
case 'python': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-python" */ 'brace/mode/python'
|
|
);
|
|
break;
|
|
}
|
|
case 'ruby': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-ruby" */ 'brace/mode/ruby'
|
|
);
|
|
break;
|
|
}
|
|
case 'php': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-php" */ 'brace/mode/php'
|
|
);
|
|
break;
|
|
}
|
|
case 'golang': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-golang" */ 'brace/mode/golang'
|
|
);
|
|
break;
|
|
}
|
|
case 'csharp': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-csharp" */ 'brace/mode/csharp'
|
|
);
|
|
break;
|
|
}
|
|
case 'html': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-html" */ 'brace/mode/html'
|
|
);
|
|
break;
|
|
}
|
|
case 'css': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-css" */ 'brace/mode/css'
|
|
);
|
|
break;
|
|
}
|
|
case 'text': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-text" */ 'brace/mode/markdown'
|
|
);
|
|
break;
|
|
}
|
|
case 'sql': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-sql" */ 'brace/mode/sql'
|
|
);
|
|
break;
|
|
}
|
|
case 'json': {
|
|
// @ts-ignore
|
|
await import(
|
|
/* webpackChunkName: "react-ace-mode-json" */ 'brace/mode/json'
|
|
);
|
|
break;
|
|
}
|
|
default: {
|
|
console.warn('不支持的语言包', language);
|
|
}
|
|
}
|
|
loadedLanguage[language] = true;
|
|
setTimestamp(Date.now());
|
|
}
|
|
})();
|
|
}, [visible, language]);
|
|
|
|
function renderEditor() {
|
|
if (!loadedLanguage[language]) {
|
|
return null;
|
|
}
|
|
|
|
const editorProps = {
|
|
theme: 'tomorrow',
|
|
value,
|
|
onChange: (newValue: string) => setValue(newValue),
|
|
fontSize: 12,
|
|
width: '100%',
|
|
height: '100%',
|
|
showPrintMargin: true,
|
|
showGutter: true,
|
|
highlightActiveLine: true,
|
|
setOptions: {
|
|
enableBasicAutocompletion: true,
|
|
enableLiveAutocompletion: true,
|
|
enableSnippets: false,
|
|
showLineNumbers: true,
|
|
tabSize: 4,
|
|
},
|
|
};
|
|
|
|
return (
|
|
<AceEditor
|
|
mode={language === 'text' ? 'markdown' : language}
|
|
{...editorProps}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Dialog
|
|
className={Style.codeEditor}
|
|
title="请输入要发送的代码"
|
|
visible={visible}
|
|
onClose={onClose}
|
|
>
|
|
<div className={Style.container}>
|
|
<div className={Style.selectContainer}>
|
|
<h3 className={Style.title}>编程语言: </h3>
|
|
<Select
|
|
className={Style.languageSelect}
|
|
defaultValue={languages[0]}
|
|
// @ts-ignore
|
|
onSelect={(lang: string) => setLanguage(lang)}
|
|
>
|
|
{languages.map((lang) => (
|
|
<Option value={lang} key={lang}>
|
|
{lang}
|
|
</Option>
|
|
))}
|
|
</Select>
|
|
</div>
|
|
<div className={Style.editorContainer}>
|
|
{renderEditor()}
|
|
</div>
|
|
<Button
|
|
className={Style.sendButton}
|
|
onClick={() => onSend(language, value)}
|
|
>
|
|
发送
|
|
</Button>
|
|
</div>
|
|
</Dialog>
|
|
<span className="hide">{timestamp}</span>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default CodeEditor;
|