import '@/components/Playground/CodeEditor/FileSelector/file-selector.scss' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' import FlexBox from '@/components/common/FlexBox' import { IFiles } from '@/components/Playground/shared' import { getFileNameList, IMPORT_MAP_FILE_NAME, TS_CONFIG_FILE_NAME } from '@/components/Playground/files' import Item from '@/components/Playground/CodeEditor/FileSelector/Item' interface FileSelectorProps { files?: IFiles onChange?: (fileName: string) => void onError?: (msg: string) => void readonly?: boolean notRemovableFiles?: string[] onRemoveFile?: (fileName: string) => void onAddFile?: (fileName: string) => void onUpdateFileName?: (newFileName: string, oldFileName: string) => void selectedFileName?: string } const FileSelector = ({ files = {}, onChange, onError, readonly = false, notRemovableFiles = [], onRemoveFile, onAddFile, onUpdateFileName, selectedFileName = '' }: FileSelectorProps) => { const hideScrollbarRef = useRef(null) const [tabs, setTabs] = useState([]) const [creating, setCreating] = useState(false) const [hasEditing, setHasEditing] = useState(false) const getMaxSequenceTabName = (filesName: string[]) => { const result = filesName.reduce((max, filesName) => { const match = filesName.match(/Component(\d+)\.tsx/) if (match) { const sequenceNumber = parseInt(match[1], 10) return Math.max(Number(max), sequenceNumber) } return max }, 0) return `Component${result + 1}.tsx` } const addTab = () => { if (hasEditing) { return } setTabs([...tabs, getMaxSequenceTabName(tabs)]) setCreating(true) setTimeout(() => { hideScrollbarRef.current?.scrollRight(1000) }) } const handleOnCancel = () => { onError?.('') if (!creating) { return } tabs.pop() setTabs([...tabs]) setCreating(false) } const handleOnClickTab = (fileName: string) => { if (creating) { return } onChange?.(fileName) } const editImportMap = () => { if (hasEditing) { return } onChange?.(IMPORT_MAP_FILE_NAME) } const editTsconfig = () => { if (hasEditing) { return } onChange?.(TS_CONFIG_FILE_NAME) } const handleOnSaveTab = (value: string, item: string) => { if (creating) { onAddFile?.(value) setCreating(false) } else { onUpdateFileName?.(value, item) } setTimeout(() => { handleOnClickTab(value) }) } const handleOnValidateTab = (newFileName: string, oldFileName: string) => { if (newFileName.length > 40) { onError?.('File name is too long, maximum 40 characters.') } if (!/\.(jsx|tsx|js|ts|css|json)$/.test(newFileName)) { onError?.('Playground only supports *.jsx, *.tsx, *.js, *.ts, *.css, *.json files.') return false } if ( tabs.map((item) => item.toLowerCase()).includes(newFileName.toLowerCase()) && newFileName.toLowerCase() !== oldFileName.toLowerCase() ) { onError?.(`File "${newFileName}" already exists.`) return false } onError?.('') return true } const handleOnRemove = (fileName: string) => { onRemoveFile?.(fileName) if (fileName === selectedFileName) { const keys = getFileNameList(files).filter( (item) => ![IMPORT_MAP_FILE_NAME, TS_CONFIG_FILE_NAME].includes(item) && !files[item].hidden ) const index = keys.indexOf(fileName) - 1 if (index >= 0) { handleOnClickTab(keys[index]) } else { handleOnClickTab(keys[1]) } } } useEffect(() => { getFileNameList(files).length ? setTabs( getFileNameList(files).filter( (item) => ![IMPORT_MAP_FILE_NAME, TS_CONFIG_FILE_NAME].includes(item) && !files[item].hidden ) ) : setTabs([]) }, [files]) return ( <>
{tabs.map((item, index) => ( handleOnSaveTab(name, item)} onCancel={handleOnCancel} onRemove={handleOnRemove} onClick={() => handleOnClickTab(item)} /> ))} {!readonly && ( )}
{(files[IMPORT_MAP_FILE_NAME] || files[TS_CONFIG_FILE_NAME]) && (
{files[TS_CONFIG_FILE_NAME] && ( )} {files[IMPORT_MAP_FILE_NAME] && ( )}
)}
) } FileSelector.Item = Item export default FileSelector