Optimize CodeEditor
This commit is contained in:
@@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user