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