Optimize CodeEditor

This commit is contained in:
2024-01-09 14:53:55 +08:00
parent f9d0cc2611
commit d98fd522cf
5 changed files with 73 additions and 157 deletions

View File

@@ -48,6 +48,10 @@ const FileSelector: React.FC<FileSelectorProps> = ({
} }
const addTab = () => { const addTab = () => {
if (hasEditing) {
return
}
setTabs([...tabs, getMaxSequenceTabName(tabs)]) setTabs([...tabs, getMaxSequenceTabName(tabs)])
setCreating(true) setCreating(true)
setTimeout(() => { setTimeout(() => {
@@ -74,6 +78,10 @@ const FileSelector: React.FC<FileSelectorProps> = ({
} }
const editImportMap = () => { const editImportMap = () => {
if (hasEditing) {
return
}
onChange?.(IMPORT_MAP_FILE_NAME) onChange?.(IMPORT_MAP_FILE_NAME)
} }
@@ -104,17 +112,23 @@ const FileSelector: React.FC<FileSelectorProps> = ({
return false return false
} }
onError?.('')
return true return true
} }
const handleOnRemove = (fileName: string) => { const handleOnRemove = (fileName: string) => {
onRemoveFile?.(fileName) onRemoveFile?.(fileName)
if (fileName === selectedFileName) { if (fileName === selectedFileName) {
const index = Object.keys(files).indexOf(fileName) - 1 const keys = Object.keys(files).filter(
(item) =>
![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden
)
const index = keys.indexOf(fileName) - 1
if (index >= 0) { if (index >= 0) {
handleOnClickTab(Object.keys(files)[index]) handleOnClickTab(keys[index])
} else { } else {
handleOnClickTab(Object.keys(files)[1]) handleOnClickTab(keys[1])
} }
} }
} }

View File

@@ -1,5 +1,14 @@
[data-component=playground-code-editor] { [data-component=playground-code-editor] {
section { width: 100%;
height: 0 !important; height: 100%;
.playground-code-editor-message {
position: absolute;
bottom: 0;
width: 100%;
color: white;
background-color: #FF4D4FAA;
padding: 5px 10px;
font-size: 1.6em;
} }
} }

View File

@@ -5,7 +5,6 @@ import { IEditorOptions, IFiles, ITheme } from '@/components/Playground/shared'
import { fileNameToLanguage } from '@/components/Playground/files' import { fileNameToLanguage } from '@/components/Playground/files'
import FileSelector from '@/components/Playground/CodeEditor/FileSelector' import FileSelector from '@/components/Playground/CodeEditor/FileSelector'
import Editor from '@/components/Playground/CodeEditor/Editor' import Editor from '@/components/Playground/CodeEditor/Editor'
import FitFullscreen from '@/components/common/FitFullscreen'
import FlexBox from '@/components/common/FlexBox' import FlexBox from '@/components/common/FlexBox'
interface CodeEditorProps { interface CodeEditorProps {
@@ -21,6 +20,7 @@ interface CodeEditorProps {
onRemoveFile?: (fileName: string, files: IFiles) => void onRemoveFile?: (fileName: string, files: IFiles) => void
onRenameFile?: (newFileName: string, oldFileName: string, files: IFiles) => void onRenameFile?: (newFileName: string, oldFileName: string, files: IFiles) => void
onChangeFileContent?: (content: string, fileName: string, files: IFiles) => void onChangeFileContent?: (content: string, fileName: string, files: IFiles) => void
onError?: (msg: string) => void
} }
const CodeEditor: React.FC<CodeEditorProps> = ({ const CodeEditor: React.FC<CodeEditorProps> = ({
@@ -35,9 +35,11 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onRemoveFile, onRemoveFile,
onRenameFile, onRenameFile,
onChangeFileContent, onChangeFileContent,
onError,
...props ...props
}) => { }) => {
const [selectedFileName, setSelectedFileName] = useState(props.selectedFileName) const [selectedFileName, setSelectedFileName] = useState(props.selectedFileName)
const [errorMsg, setErrorMsg] = useState('')
const handleOnChangeSelectedFile = (fileName: string) => { const handleOnChangeSelectedFile = (fileName: string) => {
if (onSelectedFileChange) { if (onSelectedFileChange) {
@@ -82,7 +84,15 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onRenameFile?.(newFileName, oldFileName, newFiles) onRenameFile?.(newFileName, oldFileName, newFiles)
} }
const handleOnChangeFileContent = (code = '') => { const handleOnError = (msg: string) => {
if (onError) {
onError(msg)
} else {
setErrorMsg(msg)
}
}
const handleOnChangeFileContent = _.debounce((code = '') => {
if (!files[onSelectedFileChange ? props.selectedFileName : selectedFileName]) { if (!files[onSelectedFileChange ? props.selectedFileName : selectedFileName]) {
return return
} }
@@ -93,12 +103,11 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onSelectedFileChange ? props.selectedFileName : selectedFileName, onSelectedFileChange ? props.selectedFileName : selectedFileName,
clone clone
) )
} }, 250)
return ( return (
<> <>
<FitFullscreen data-component={'playground-code-editor'}> <FlexBox data-component={'playground-code-editor'}>
<FlexBox style={{ height: '100%' }}>
<FileSelector <FileSelector
files={files} files={files}
readonly={readonly} readonly={readonly}
@@ -110,6 +119,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onRemoveFile={handleOnRemoveFile} onRemoveFile={handleOnRemoveFile}
onUpdateFileName={handleOnUpdateFileName} onUpdateFileName={handleOnUpdateFileName}
onAddFile={handleOnAddFile} onAddFile={handleOnAddFile}
onError={handleOnError}
/> />
<Editor <Editor
theme={theme} theme={theme}
@@ -127,8 +137,8 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
} }
onChange={handleOnChangeFileContent} onChange={handleOnChangeFileContent}
/> />
{errorMsg && <div className={'playground-code-editor-message'}>{errorMsg}</div>}
</FlexBox> </FlexBox>
</FitFullscreen>
</> </>
) )
} }

View File

@@ -3,7 +3,7 @@ import importMap from '@/components/Playground/template/import-map.json?raw'
import AppCss from '@/components/Playground/template/src/App.css?raw' import AppCss from '@/components/Playground/template/src/App.css?raw'
import App from '@/components/Playground/template/src/App.tsx?raw' import App from '@/components/Playground/template/src/App.tsx?raw'
import main from '@/components/Playground/template/src/main.tsx?raw' import main from '@/components/Playground/template/src/main.tsx?raw'
import { ICustomFiles, IFiles, IImportMap } from '@/components/Playground/shared' import { IFiles } from '@/components/Playground/shared'
export const MAIN_FILE_NAME = 'App.tsx' export const MAIN_FILE_NAME = 'App.tsx'
export const IMPORT_MAP_FILE_NAME = 'import-map.json' export const IMPORT_MAP_FILE_NAME = 'import-map.json'
@@ -38,61 +38,6 @@ export const base64ToStr = (base64: string) => {
return '' return ''
} }
const transformCustomFiles = (files: ICustomFiles) => {
const newFiles: IFiles = {}
Object.keys(files).forEach((key) => {
const tempFile = files[key]
if (typeof tempFile === 'string') {
newFiles[key] = {
name: key,
language: fileNameToLanguage(key),
value: tempFile
}
} else {
newFiles[key] = {
name: key,
language: fileNameToLanguage(key),
value: tempFile.code,
hidden: tempFile.hidden,
active: tempFile.active
}
}
})
return newFiles
}
export const getCustomActiveFile = (files?: ICustomFiles) => {
if (!files) return null
return Object.keys(files).find((key) => {
const tempFile = files[key]
if (typeof tempFile !== 'string' && tempFile.active) {
return key
}
return null
})
}
export const getMergedCustomFiles = (files?: ICustomFiles, importMap?: IImportMap) => {
if (!files) return null
if (importMap) {
return {
...reactTemplateFiles,
...transformCustomFiles(files),
[IMPORT_MAP_FILE_NAME]: {
name: IMPORT_MAP_FILE_NAME,
language: 'json',
value: JSON.stringify(importMap, null, 2)
}
}
} else {
return {
...reactTemplateFiles,
...transformCustomFiles(files)
}
}
}
export const getFilesFromUrl = () => { export const getFilesFromUrl = () => {
let files: IFiles | undefined let files: IFiles | undefined
try { try {

View File

@@ -1,4 +1,3 @@
import React from 'react'
import { editor } from 'monaco-editor' import { editor } from 'monaco-editor'
export type ILanguage = 'javascript' | 'typescript' | 'json' | 'css' | 'xml' export type ILanguage = 'javascript' | 'typescript' | 'json' | 'css' | 'xml'
@@ -7,7 +6,6 @@ export interface IFile {
name: string name: string
value: string value: string
language: ILanguage language: ILanguage
active?: boolean
hidden?: boolean hidden?: boolean
} }
@@ -17,64 +15,4 @@ export interface IFiles {
export type ITheme = 'light' | 'vs-dark' export type ITheme = 'light' | 'vs-dark'
export type IImportMap = { imports: Record<string, string> }
export interface ICustomFiles {
[key: string]:
| string
| {
code: string
active?: boolean
hidden?: boolean
}
}
export type IEditorOptions = editor.IStandaloneEditorConstructionOptions export type IEditorOptions = editor.IStandaloneEditorConstructionOptions
export interface IEditorContainer {
showFileSelector?: boolean
fileSelectorReadOnly?: boolean
options?: IEditorOptions
}
export interface IOutput {
showCompileOutput?: boolean
}
export interface ISplitPane {
children?: React.ReactNode[]
defaultSizes?: number[]
}
export type IPlayground = {
width?: string | number
height?: string | number
theme?: ITheme
importMap?: IImportMap
files?: ICustomFiles
options?: {
lineNumbers?: boolean
fontSize?: number
tabSize?: number
}
showHeader?: boolean
border?: boolean
onFilesChange?: (url: string) => void
saveOnUrl?: boolean
autorun?: boolean
// recompileDelay
} & Omit<IEditorContainer, 'options'> &
IOutput &
ISplitPane
export interface IPlaygroundContext {
files: IFiles
filesHash: string
theme: ITheme
selectedFileName: string
setSelectedFileName: (fileName: string) => void
setTheme: (theme: ITheme) => void
setFiles: (files: IFiles) => void
addFile: (fileName: string) => void
removeFile: (fileName: string) => void
changeFileName: (oldFieldName: string, newFieldName: string) => void
changeTheme: (theme: ITheme) => void
}