diff --git a/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts b/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts index f14aa3b..7535cc7 100644 --- a/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts +++ b/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts @@ -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 = ( diff --git a/src/components/ReactPlayground/CodeEditor/Editor/index.tsx b/src/components/ReactPlayground/CodeEditor/Editor/index.tsx index 7be12ca..c3887bf 100644 --- a/src/components/ReactPlayground/CodeEditor/Editor/index.tsx +++ b/src/components/ReactPlayground/CodeEditor/Editor/index.tsx @@ -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 = ({ files = {}, selectedFileName = '', + readonly, theme, onChange, options, @@ -92,7 +94,12 @@ const Editor: React.FC = ({ value={file.value} onChange={onChange} onMount={handleOnEditorDidMount} - options={{ ...MonacoEditorConfig, ...options, theme: undefined }} + options={{ + ...MonacoEditorConfig, + ...options, + theme: undefined, + readOnly: readonly + }} /> ) diff --git a/src/components/ReactPlayground/CodeEditor/FileSelector/Item.tsx b/src/components/ReactPlayground/CodeEditor/FileSelector/Item.tsx index 27bbc02..bcb447c 100644 --- a/src/components/ReactPlayground/CodeEditor/FileSelector/Item.tsx +++ b/src/components/ReactPlayground/CodeEditor/FileSelector/Item.tsx @@ -5,6 +5,8 @@ interface ItemProps { creating?: boolean value: string active?: boolean + hasEditing?: boolean + setHasEditing?: React.Dispatch> onOk?: (fileName: string) => void onCancel?: () => void onRemove?: (fileName: string) => void @@ -16,6 +18,8 @@ const Item: React.FC = ({ readonly = false, value, active = false, + hasEditing, + setHasEditing, onOk, onCancel, onRemove, @@ -27,6 +31,14 @@ const Item: React.FC = ({ const [fileName, setFileName] = useState(value) const [creating, setCreating] = useState(prop.creating) + const handleOnClick = () => { + if (hasEditing) { + return + } + + onClick?.() + } + const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { event.preventDefault() @@ -39,30 +51,35 @@ const Item: React.FC = ({ 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 = ({ const handleOnDelete = (e: React.MouseEvent) => { e.stopPropagation() + if (hasEditing) { + return + } if (confirm(`确定删除文件 ${value} ?`)) { onRemove?.(value) } @@ -85,7 +105,7 @@ const Item: React.FC = ({ }, []) return ( -
+
{creating ? (
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 = ({ onChange, onError, readonly = false, - readonlyFiles = [], + notRemovableFiles = [], onRemoveFile, onAddFile, onUpdateFileName, @@ -33,6 +29,7 @@ const FileSelector: React.FC = ({ }) => { const [tabs, setTabs] = useState([]) 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 = ({ onAddFile?.(value) setCreating(false) } else { - onUpdateFileName?.(item, value) + onUpdateFileName?.(value, item) } setTimeout(() => { @@ -106,7 +103,14 @@ const FileSelector: React.FC = ({ 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 = ({ 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} diff --git a/src/components/ReactPlayground/CodeEditor/index.tsx b/src/components/ReactPlayground/CodeEditor/index.tsx index 0952284..a59640c 100644 --- a/src/components/ReactPlayground/CodeEditor/index.tsx +++ b/src/components/ReactPlayground/CodeEditor/index.tsx @@ -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 = ({ @@ -22,9 +25,11 @@ const CodeEditor: React.FC = ({ files, readonly, readonlyFiles, + notRemovable, options, onSelectedFileChange, onRemoveFile, + onRenameFile, ...props }) => { const [selectedFileName, setSelectedFileName] = useState(props.selectedFileName) @@ -43,6 +48,24 @@ const CodeEditor: React.FC = ({ 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 ( <> @@ -50,18 +73,20 @@ const CodeEditor: React.FC = ({ diff --git a/src/components/ReactPlayground/shared.ts b/src/components/ReactPlayground/shared.ts index 02fee27..0c7354e 100644 --- a/src/components/ReactPlayground/shared.ts +++ b/src/components/ReactPlayground/shared.ts @@ -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 diff --git a/src/components/ReactPlayground/utils.ts b/src/components/ReactPlayground/utils.ts index 95b5a90..38ac84a 100644 --- a/src/components/ReactPlayground/utils.ts +++ b/src/components/ReactPlayground/utils.ts @@ -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' } diff --git a/src/pages/OnlineEditor.tsx b/src/pages/OnlineEditor.tsx index 974ce4c..008cc53 100644 --- a/src/pages/OnlineEditor.tsx +++ b/src/pages/OnlineEditor.tsx @@ -5,25 +5,53 @@ import { IFiles } from '@/components/ReactPlayground/shared.ts' const OnlineEditor: React.FC = () => { const [files, setFiles] = useState({ - 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 ( <> setFiles(files)} + onRenameFile={(_, __, files) => setFiles(files)} /> )