mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
160
packages/web/components/common/Textarea/CodeEditor/Editor.tsx
Normal file
160
packages/web/components/common/Textarea/CodeEditor/Editor.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import Editor, { Monaco, loader } from '@monaco-editor/react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '../../Icon';
|
||||
|
||||
loader.config({
|
||||
paths: { vs: '/js/monaco-editor.0.45.0/vs' }
|
||||
});
|
||||
|
||||
type EditorVariablePickerType = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
|
||||
height?: number;
|
||||
resize?: boolean;
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
onChange?: (e: string) => void;
|
||||
onOpenModal?: () => void;
|
||||
variables?: EditorVariablePickerType[];
|
||||
defaultHeight?: number;
|
||||
};
|
||||
|
||||
const options = {
|
||||
lineNumbers: 'on',
|
||||
guides: {
|
||||
indentation: false
|
||||
},
|
||||
automaticLayout: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 4,
|
||||
horizontalScrollbarSize: 8,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
lineNumbersMinChars: 0,
|
||||
fontSize: 14,
|
||||
scrollBeyondLastLine: false,
|
||||
folding: true,
|
||||
overviewRulerBorder: false,
|
||||
tabSize: 2
|
||||
};
|
||||
|
||||
const MyEditor = ({
|
||||
defaultValue,
|
||||
value,
|
||||
onChange,
|
||||
resize,
|
||||
variables = [],
|
||||
defaultHeight = 200,
|
||||
onOpenModal,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [height, setHeight] = useState(defaultHeight);
|
||||
const initialY = useRef(0);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
initialY.current = e.clientY;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const deltaY = e.clientY - initialY.current;
|
||||
initialY.current = e.clientY;
|
||||
setHeight((prevHeight) => (prevHeight + deltaY < 100 ? 100 : prevHeight + deltaY));
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
}, []);
|
||||
|
||||
const beforeMount = useCallback((monaco: Monaco) => {
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: false,
|
||||
allowComments: false,
|
||||
schemas: [
|
||||
{
|
||||
uri: 'http://myserver/foo-schema.json', // 一个假设的 URI
|
||||
fileMatch: ['*'], // 匹配所有文件
|
||||
schema: {} // 空的 Schema
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
monaco.editor.defineTheme('JSONEditorTheme', {
|
||||
base: 'vs', // 可以基于已有的主题进行定制
|
||||
inherit: true, // 继承基础主题的设置
|
||||
rules: [{ token: 'variable', foreground: '2B5FD9' }],
|
||||
colors: {
|
||||
'editor.background': '#ffffff00',
|
||||
'editorLineNumber.foreground': '#aaa',
|
||||
'editorOverviewRuler.border': '#ffffff00',
|
||||
'editor.lineHighlightBackground': '#F7F8FA',
|
||||
'scrollbarSlider.background': '#E8EAEC',
|
||||
'editorIndentGuide.activeBackground': '#ddd',
|
||||
'editorIndentGuide.background': '#eee'
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'md'}
|
||||
borderColor={'myGray.200'}
|
||||
py={2}
|
||||
height={height}
|
||||
position={'relative'}
|
||||
pl={2}
|
||||
{...props}
|
||||
>
|
||||
<Editor
|
||||
height={'100%'}
|
||||
defaultLanguage="typescript"
|
||||
options={options as any}
|
||||
theme="JSONEditorTheme"
|
||||
beforeMount={beforeMount}
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange?.(e || '');
|
||||
}}
|
||||
/>
|
||||
{resize && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-1'}
|
||||
bottom={'-1'}
|
||||
zIndex={10}
|
||||
cursor={'ns-resize'}
|
||||
px={'4px'}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<MyIcon name={'common/editor/resizer'} width={'16px'} height={'16px'} />
|
||||
</Box>
|
||||
)}
|
||||
{!!onOpenModal && (
|
||||
<Box
|
||||
zIndex={10}
|
||||
position={'absolute'}
|
||||
bottom={0}
|
||||
right={2}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenModal}
|
||||
>
|
||||
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(MyEditor);
|
36
packages/web/components/common/Textarea/CodeEditor/index.tsx
Normal file
36
packages/web/components/common/Textarea/CodeEditor/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import MyEditor, { type Props as EditorProps } from './Editor';
|
||||
import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import MyModal from '../../MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
type Props = Omit<EditorProps, 'resize'> & {};
|
||||
|
||||
const CodeEditor = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyEditor {...props} resize onOpenModal={onOpen} />
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc="modal/edit"
|
||||
title={t('Code editor')}
|
||||
w={'full'}
|
||||
>
|
||||
<ModalBody>
|
||||
<MyEditor {...props} bg={'myGray.50'} defaultHeight={600} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={2} onClick={onClose}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CodeEditor);
|
Reference in New Issue
Block a user