Optimize remove file, rename file and readonly in CodeEditor
This commit is contained in:
@@ -2,7 +2,7 @@ import { editor, IPosition, Selection } from 'monaco-editor'
|
||||
import ScrollType = editor.ScrollType
|
||||
import { Monaco } from '@monaco-editor/react'
|
||||
import { getWorker, MonacoJsxSyntaxHighlight } from 'monaco-jsx-syntax-highlight'
|
||||
import { createATA, TypeHelper } from '@/components/ReactPlayground/CodeEditor/Editor/ata.ts'
|
||||
import { createATA, TypeHelper } from '@/components/ReactPlayground/CodeEditor/Editor/ata'
|
||||
|
||||
export const useEditor = () => {
|
||||
const doOpenEditor = (
|
||||
|
||||
@@ -3,13 +3,14 @@ import { editor, Selection } from 'monaco-editor'
|
||||
import MonacoEditor, { Monaco } from '@monaco-editor/react'
|
||||
import '@/components/ReactPlayground/CodeEditor/Editor/editor.scss'
|
||||
import { IEditorOptions, IFiles, ITheme } from '@/components/ReactPlayground/shared'
|
||||
import { MonacoEditorConfig } from '@/components/ReactPlayground/CodeEditor/Editor/monacoConfig'
|
||||
import { fileNameToLanguage } from '@/components/ReactPlayground/utils'
|
||||
import { useEditor, useTypesProgress } from '@/components/ReactPlayground/CodeEditor/Editor/hooks'
|
||||
import { MonacoEditorConfig } from '@/components/ReactPlayground/CodeEditor/Editor/monacoConfig'
|
||||
|
||||
interface EditorProps {
|
||||
files?: IFiles
|
||||
selectedFileName?: string
|
||||
readonly?: boolean
|
||||
onChange?: (code: string | undefined) => void
|
||||
options?: IEditorOptions
|
||||
theme?: ITheme
|
||||
@@ -19,6 +20,7 @@ interface EditorProps {
|
||||
const Editor: React.FC<EditorProps> = ({
|
||||
files = {},
|
||||
selectedFileName = '',
|
||||
readonly,
|
||||
theme,
|
||||
onChange,
|
||||
options,
|
||||
@@ -92,7 +94,12 @@ const Editor: React.FC<EditorProps> = ({
|
||||
value={file.value}
|
||||
onChange={onChange}
|
||||
onMount={handleOnEditorDidMount}
|
||||
options={{ ...MonacoEditorConfig, ...options, theme: undefined }}
|
||||
options={{
|
||||
...MonacoEditorConfig,
|
||||
...options,
|
||||
theme: undefined,
|
||||
readOnly: readonly
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@ interface ItemProps {
|
||||
creating?: boolean
|
||||
value: string
|
||||
active?: boolean
|
||||
hasEditing?: boolean
|
||||
setHasEditing?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
onOk?: (fileName: string) => void
|
||||
onCancel?: () => void
|
||||
onRemove?: (fileName: string) => void
|
||||
@@ -16,6 +18,8 @@ const Item: React.FC<ItemProps> = ({
|
||||
readonly = false,
|
||||
value,
|
||||
active = false,
|
||||
hasEditing,
|
||||
setHasEditing,
|
||||
onOk,
|
||||
onCancel,
|
||||
onRemove,
|
||||
@@ -27,6 +31,14 @@ const Item: React.FC<ItemProps> = ({
|
||||
const [fileName, setFileName] = useState(value)
|
||||
const [creating, setCreating] = useState(prop.creating)
|
||||
|
||||
const handleOnClick = () => {
|
||||
if (hasEditing) {
|
||||
return
|
||||
}
|
||||
|
||||
onClick?.()
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
@@ -39,30 +51,35 @@ const Item: React.FC<ItemProps> = ({
|
||||
|
||||
const finishNameFile = () => {
|
||||
if (!creating || onValidate ? !onValidate?.(fileName, value) : false) {
|
||||
inputRef.current?.focus()
|
||||
return
|
||||
}
|
||||
|
||||
if (fileName === value && active) {
|
||||
setCreating(false)
|
||||
setHasEditing?.(false)
|
||||
return
|
||||
}
|
||||
|
||||
onOk?.(fileName)
|
||||
setCreating(false)
|
||||
setHasEditing?.(false)
|
||||
}
|
||||
|
||||
const cancelNameFile = () => {
|
||||
setFileName(value)
|
||||
setCreating(false)
|
||||
setHasEditing?.(false)
|
||||
onCancel?.()
|
||||
}
|
||||
|
||||
const handleOnDoubleClick = () => {
|
||||
if (readonly) {
|
||||
if (readonly || creating || hasEditing) {
|
||||
return
|
||||
}
|
||||
|
||||
setCreating(true)
|
||||
setHasEditing?.(true)
|
||||
setFileName(value)
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
@@ -75,6 +92,9 @@ const Item: React.FC<ItemProps> = ({
|
||||
|
||||
const handleOnDelete = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
if (hasEditing) {
|
||||
return
|
||||
}
|
||||
if (confirm(`确定删除文件 ${value} ?`)) {
|
||||
onRemove?.(value)
|
||||
}
|
||||
@@ -85,7 +105,7 @@ const Item: React.FC<ItemProps> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`tab-item${active ? ' active' : ''}`} onClick={onClick}>
|
||||
<div className={`tab-item${active ? ' active' : ''}`} onClick={handleOnClick}>
|
||||
{creating ? (
|
||||
<div className={'tab-item-input'}>
|
||||
<input
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1px;
|
||||
padding: 10px 20px 0 20px;
|
||||
padding: 4px 20px 0 20px;
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 30px;
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import React from 'react'
|
||||
import '@/components/ReactPlayground/CodeEditor/FileSelector/file-selector.scss'
|
||||
import { IFiles } from '@/components/ReactPlayground/shared.ts'
|
||||
import {
|
||||
ENTRY_FILE_NAME,
|
||||
IMPORT_MAP_FILE_NAME,
|
||||
MAIN_FILE_NAME
|
||||
} from '@/components/ReactPlayground/files.ts'
|
||||
import Item from '@/components/ReactPlayground/CodeEditor/FileSelector/Item.tsx'
|
||||
import { IFiles } from '@/components/ReactPlayground/shared'
|
||||
import { ENTRY_FILE_NAME, IMPORT_MAP_FILE_NAME } from '@/components/ReactPlayground/files'
|
||||
import Item from '@/components/ReactPlayground/CodeEditor/FileSelector/Item'
|
||||
|
||||
interface FileSelectorProps {
|
||||
files?: IFiles
|
||||
onChange?: (fileName: string) => void
|
||||
onError?: (msg: string) => void
|
||||
readonly?: boolean
|
||||
readonlyFiles?: string[]
|
||||
notRemovableFiles?: string[]
|
||||
onRemoveFile?: (fileName: string) => void
|
||||
onAddFile?: (fileName: string) => void
|
||||
onUpdateFileName?: (newFileName: string, oldFileName: string) => void
|
||||
@@ -25,7 +21,7 @@ const FileSelector: React.FC<FileSelectorProps> = ({
|
||||
onChange,
|
||||
onError,
|
||||
readonly = false,
|
||||
readonlyFiles = [],
|
||||
notRemovableFiles = [],
|
||||
onRemoveFile,
|
||||
onAddFile,
|
||||
onUpdateFileName,
|
||||
@@ -33,6 +29,7 @@ const FileSelector: React.FC<FileSelectorProps> = ({
|
||||
}) => {
|
||||
const [tabs, setTabs] = useState<string[]>([])
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [hasEditing, setHasEditing] = useState(false)
|
||||
|
||||
const getMaxSequenceTabName = (filesName: string[]) => {
|
||||
const result = filesName.reduce((max, filesName) => {
|
||||
@@ -79,7 +76,7 @@ const FileSelector: React.FC<FileSelectorProps> = ({
|
||||
onAddFile?.(value)
|
||||
setCreating(false)
|
||||
} else {
|
||||
onUpdateFileName?.(item, value)
|
||||
onUpdateFileName?.(value, item)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -106,7 +103,14 @@ const FileSelector: React.FC<FileSelectorProps> = ({
|
||||
|
||||
const handleOnRemove = (fileName: string) => {
|
||||
onRemoveFile?.(fileName)
|
||||
handleOnClickTab(Object.keys(files)[Object.keys(files).length - 1])
|
||||
if (fileName === selectedFileName) {
|
||||
const index = Object.keys(files).indexOf(fileName) - 1
|
||||
if (index >= 0) {
|
||||
handleOnClickTab(Object.keys(files)[index])
|
||||
} else {
|
||||
handleOnClickTab(Object.keys(files)[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -133,9 +137,9 @@ const FileSelector: React.FC<FileSelectorProps> = ({
|
||||
value={item}
|
||||
active={selectedFileName == item}
|
||||
creating={creating}
|
||||
readonly={
|
||||
readonly || readonlyFiles.includes(item) || MAIN_FILE_NAME == item
|
||||
}
|
||||
readonly={readonly || notRemovableFiles.includes(item)}
|
||||
hasEditing={hasEditing}
|
||||
setHasEditing={setHasEditing}
|
||||
onValidate={handleOnValidateTab}
|
||||
onOk={(name) => handleOnSaveTab(name, item)}
|
||||
onCancel={handleOnCancel}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import { IEditorOptions, IFiles, ITheme } from '@/components/ReactPlayground/shared'
|
||||
import { fileNameToLanguage } from '@/components/ReactPlayground/utils'
|
||||
import FileSelector from '@/components/ReactPlayground/CodeEditor/FileSelector'
|
||||
import Editor from '@/components/ReactPlayground/CodeEditor/Editor'
|
||||
import { IEditorOptions, IFiles, ITheme } from '@/components/ReactPlayground/shared.ts'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||
import FlexBox from '@/components/common/FlexBox.tsx'
|
||||
import _ from 'lodash'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
|
||||
interface CodeEditorProps {
|
||||
theme: ITheme
|
||||
files: IFiles
|
||||
readonly?: boolean
|
||||
readonlyFiles?: string[]
|
||||
notRemovable?: string[]
|
||||
selectedFileName: string
|
||||
options?: IEditorOptions
|
||||
onSelectedFileChange?: (fileName: string) => void
|
||||
onRemoveFile?: (fileName: string, files: IFiles) => void
|
||||
onRenameFile?: (newFileName: string, oldFileName: string, files: IFiles) => void
|
||||
}
|
||||
|
||||
const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
@@ -22,9 +25,11 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
files,
|
||||
readonly,
|
||||
readonlyFiles,
|
||||
notRemovable,
|
||||
options,
|
||||
onSelectedFileChange,
|
||||
onRemoveFile,
|
||||
onRenameFile,
|
||||
...props
|
||||
}) => {
|
||||
const [selectedFileName, setSelectedFileName] = useState(props.selectedFileName)
|
||||
@@ -43,6 +48,24 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
onRemoveFile?.(fileName, clone)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FitFullscreen>
|
||||
@@ -50,18 +73,20 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
<FileSelector
|
||||
files={files}
|
||||
readonly={readonly}
|
||||
readonlyFiles={readonlyFiles}
|
||||
notRemovableFiles={notRemovable}
|
||||
selectedFileName={
|
||||
onSelectedFileChange ? props.selectedFileName : selectedFileName
|
||||
}
|
||||
onChange={handleOnChangeSelectedFile}
|
||||
onRemoveFile={handleOnRemoveFile}
|
||||
onUpdateFileName={handleOnUpdateFileName}
|
||||
/>
|
||||
<Editor
|
||||
theme={theme}
|
||||
selectedFileName={selectedFileName}
|
||||
files={files}
|
||||
options={options}
|
||||
readonly={readonly || readonlyFiles?.includes(selectedFileName)}
|
||||
/>
|
||||
</FlexBox>
|
||||
</FitFullscreen>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { editor } from 'monaco-editor'
|
||||
|
||||
export type ILanguage = 'javascript' | 'typescript' | 'json' | 'css'
|
||||
export type ILanguage = 'javascript' | 'typescript' | 'json' | 'css' | 'xml'
|
||||
|
||||
export interface IFile {
|
||||
name: string
|
||||
|
||||
@@ -116,5 +116,6 @@ export const fileNameToLanguage = (name: string): ILanguage => {
|
||||
if (['ts', 'tsx'].includes(suffix)) return 'typescript'
|
||||
if (['json'].includes(suffix)) return 'json'
|
||||
if (['css'].includes(suffix)) return 'css'
|
||||
if (['svg'].includes(suffix)) return 'xml'
|
||||
return 'javascript'
|
||||
}
|
||||
|
||||
@@ -5,25 +5,53 @@ import { IFiles } from '@/components/ReactPlayground/shared.ts'
|
||||
|
||||
const OnlineEditor: React.FC = () => {
|
||||
const [files, setFiles] = useState<IFiles>({
|
||||
abc: {
|
||||
['App.tsx']: {
|
||||
name: 'App.tsx',
|
||||
language: fileNameToLanguage('App.tsx'),
|
||||
value: 'const a = () => {}'
|
||||
},
|
||||
['App.css']: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
},
|
||||
cde: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
},
|
||||
def: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
},
|
||||
efg: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
},
|
||||
fgh: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
},
|
||||
ghi: {
|
||||
name: 'App.css',
|
||||
language: fileNameToLanguage('App.css'),
|
||||
value: '.title {}'
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
theme={'light'}
|
||||
theme={'vs-dark'}
|
||||
files={files}
|
||||
selectedFileName={'abc'}
|
||||
selectedFileName={'App.css'}
|
||||
notRemovable={['App.css']}
|
||||
readonlyFiles={['App.tsx']}
|
||||
onRemoveFile={(_, files) => setFiles(files)}
|
||||
onRenameFile={(_, __, files) => setFiles(files)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user