176 lines
5.8 KiB
TypeScript
176 lines
5.8 KiB
TypeScript
import { Monaco } from '@monaco-editor/react'
|
|
import { editor } from 'monaco-editor'
|
|
import _ from 'lodash'
|
|
import useStyles from '@/components/Playground/CodeEditor/index.style'
|
|
import FlexBox from '@/components/common/FlexBox'
|
|
import { IEditorOptions, IFiles, ITsconfig } from '@/components/Playground/shared'
|
|
import {
|
|
fileNameToLanguage,
|
|
getFileNameList,
|
|
IMPORT_MAP_FILE_NAME,
|
|
TS_CONFIG_FILE_NAME
|
|
} from '@/components/Playground/files'
|
|
import FileSelector from '@/components/Playground/CodeEditor/FileSelector'
|
|
import Editor, { ExtraLib } from '@/components/Playground/CodeEditor/Editor'
|
|
|
|
interface CodeEditorProps {
|
|
isDarkMode?: boolean
|
|
showFileSelector?: boolean
|
|
tsconfig?: ITsconfig
|
|
files: IFiles
|
|
readonly?: boolean
|
|
readonlyFiles?: string[]
|
|
notRemovable?: string[]
|
|
selectedFileName?: string
|
|
options?: IEditorOptions
|
|
extraLibs?: ExtraLib[]
|
|
onEditorDidMount?: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void
|
|
onSelectedFileChange?: (fileName: string) => void
|
|
onAddFile?: (fileName: string, files: IFiles) => void
|
|
onRemoveFile?: (fileName: string, files: IFiles) => void
|
|
onRenameFile?: (newFileName: string, oldFileName: string, files: IFiles) => void
|
|
onChangeFileContent?: (content: string, fileName: string, files: IFiles) => void
|
|
onError?: (msg: string) => void
|
|
}
|
|
|
|
const CodeEditor = ({
|
|
isDarkMode,
|
|
showFileSelector = true,
|
|
tsconfig,
|
|
files,
|
|
readonly,
|
|
readonlyFiles,
|
|
notRemovable,
|
|
options,
|
|
onSelectedFileChange,
|
|
onAddFile,
|
|
onRemoveFile,
|
|
onRenameFile,
|
|
onChangeFileContent,
|
|
onError,
|
|
extraLibs,
|
|
onEditorDidMount,
|
|
...props
|
|
}: CodeEditorProps) => {
|
|
const { styles } = useStyles()
|
|
const filteredFilesName = getFileNameList(files).filter(
|
|
(item) => ![IMPORT_MAP_FILE_NAME, TS_CONFIG_FILE_NAME].includes(item) && !files[item].hidden
|
|
)
|
|
const propsSelectedFileName =
|
|
props.selectedFileName || (filteredFilesName.length ? filteredFilesName[0] : '')
|
|
const [selectedFileName, setSelectedFileName] = useState(propsSelectedFileName)
|
|
const [errorMsg, setErrorMsg] = useState('')
|
|
|
|
const handleOnChangeSelectedFile = (fileName: string) => {
|
|
if (onSelectedFileChange) {
|
|
onSelectedFileChange(fileName)
|
|
} else {
|
|
setSelectedFileName(fileName)
|
|
}
|
|
}
|
|
|
|
const handleOnRemoveFile = (fileName: string) => {
|
|
const clone = _.cloneDeep(files)
|
|
delete clone[fileName]
|
|
onRemoveFile?.(fileName, clone)
|
|
}
|
|
|
|
const handleOnAddFile = (fileName: string) => {
|
|
const clone = _.cloneDeep(files)
|
|
clone[fileName] = {
|
|
name: fileName,
|
|
language: fileNameToLanguage(fileName),
|
|
value: ''
|
|
}
|
|
onAddFile?.(fileName, clone)
|
|
handleOnChangeSelectedFile(fileName)
|
|
}
|
|
|
|
const handleOnUpdateFileName = (newFileName: string, oldFileName: string) => {
|
|
if (!files[oldFileName] || !newFileName) {
|
|
return
|
|
}
|
|
|
|
const { [oldFileName]: value, ...rest } = files
|
|
const newFile: IFiles = {
|
|
[newFileName]: {
|
|
...value,
|
|
language: fileNameToLanguage(newFileName),
|
|
name: newFileName
|
|
}
|
|
}
|
|
const newFiles: IFiles = { ...rest, ...newFile }
|
|
|
|
onRenameFile?.(newFileName, oldFileName, newFiles)
|
|
}
|
|
|
|
const handleOnError = (msg: string) => {
|
|
if (onError) {
|
|
onError(msg)
|
|
} else {
|
|
setErrorMsg(msg)
|
|
}
|
|
}
|
|
|
|
const handleOnChangeFileContent = (code: string = '') => {
|
|
if (!files[onSelectedFileChange ? propsSelectedFileName : selectedFileName]) {
|
|
return
|
|
}
|
|
const clone = _.cloneDeep(files)
|
|
clone[onSelectedFileChange ? propsSelectedFileName : selectedFileName].value = code ?? ''
|
|
onChangeFileContent?.(
|
|
code,
|
|
onSelectedFileChange ? propsSelectedFileName : selectedFileName,
|
|
clone
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<FlexBox className={styles.root}>
|
|
{showFileSelector && (
|
|
<FileSelector
|
|
files={files}
|
|
readonly={readonly}
|
|
notRemovableFiles={notRemovable}
|
|
selectedFileName={
|
|
onSelectedFileChange ? propsSelectedFileName : selectedFileName
|
|
}
|
|
onChange={handleOnChangeSelectedFile}
|
|
onRemoveFile={handleOnRemoveFile}
|
|
onUpdateFileName={handleOnUpdateFileName}
|
|
onAddFile={handleOnAddFile}
|
|
onError={handleOnError}
|
|
/>
|
|
)}
|
|
<Editor
|
|
isDarkMode={isDarkMode}
|
|
tsconfig={tsconfig}
|
|
selectedFileName={
|
|
onSelectedFileChange ? propsSelectedFileName : selectedFileName
|
|
}
|
|
files={files}
|
|
options={options}
|
|
readonly={
|
|
readonly ||
|
|
readonlyFiles?.includes(
|
|
onSelectedFileChange ? propsSelectedFileName : selectedFileName
|
|
) ||
|
|
!files[onSelectedFileChange ? propsSelectedFileName : selectedFileName]
|
|
}
|
|
onChange={handleOnChangeFileContent}
|
|
onJumpFile={handleOnChangeSelectedFile}
|
|
extraLibs={extraLibs}
|
|
onEditorDidMount={onEditorDidMount}
|
|
/>
|
|
{errorMsg && <div className={styles.errorMessage}>{errorMsg}</div>}
|
|
</FlexBox>
|
|
</>
|
|
)
|
|
}
|
|
|
|
CodeEditor.Editor = Editor
|
|
CodeEditor.FileSelector = FileSelector
|
|
|
|
export default CodeEditor
|