Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
9 changed files with 115 additions and 31 deletions
Showing only changes of commit 5a320e459a - Show all commits

View File

@@ -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 = (

View File

@@ -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
}}
/>
</>
)

View File

@@ -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

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>

View File

@@ -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

View File

@@ -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'
}

View File

@@ -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)}
/>
</>
)