import { useState, useRef, useCallback, useEffect, useMemo, MouseEvent } from 'react'; import Icon from '@components/icons'; import Arrow from '@components/icons/arrow'; import DeleteIcon from '@components/icons/delete'; import EditIcon from '@components/icons/edit'; import AddFileIcon from '@components/icons/addfile'; import AddFolderIcon from '@components/icons/addfolder'; import { getOldNewPath } from '@utils'; const File: React.FC<{ getAllFiles: () => any, disableFileOps?: { add?: boolean, delete?: boolean, rename?: boolean, }, disableFolderOps?: { add?: boolean, delete?: boolean, rename?: boolean, }, file: any, onPathChange: (key: string) => void, root: boolean, currentPath?: string, activeDirectory?: string, useFileMenu?: boolean, onAddFile: (...args: any[]) => void, onConfirmAddFile: (...args: any[]) => void, onDeleteFile: (...args: any[]) => void, onEditFileName: (...args: any[]) => void, onConfirmAddFolder: (...args: any[]) => void, onAddFolder: (...args: any[]) => void, onDeleteFolder: (...args: any[]) => void, onEditFolderName: (...args: any[]) => void, onContextMenu: (e: any, fileAction:(action:string, path: string, isFile: boolean) => void) => void onClickItem?: (file: any) => void, }> = ({ getAllFiles, disableFileOps = {}, disableFolderOps = {}, file, onPathChange, currentPath = '', root, useFileMenu = true, activeDirectory = '', onAddFile, onConfirmAddFile, onDeleteFile, onEditFileName, onConfirmAddFolder, onAddFolder, onDeleteFolder, onEditFolderName, onContextMenu, onClickItem, }) => { const [showChild, setShowChild] = useState(false); const [editing, setEditing] = useState(false); const nameRef = useRef(null); const isDirectorySelected = useMemo(() => { if (!activeDirectory || file._isFile) { return false; } if (file.path === activeDirectory.replace(/\/$/, '')) { const isCurrentFileInChildren = currentPath.replace(/\/[^\/]*$/, '') === file.path; return isCurrentFileInChildren ? !showChild : true; } return false; }, [activeDirectory, currentPath, showChild, file]); const handleDirectoryClick = useCallback((e: MouseEvent) => { onClickItem?.({ path: e.currentTarget?.dataset?.src, isFile: false, }); setShowChild((pre:boolean) => !pre); }, [onClickItem]); const handleFileClick = useCallback((e: MouseEvent) => { onClickItem?.({ path: e.currentTarget?.dataset?.src, isFile: true, }); const key = e.currentTarget.dataset.src!; onPathChange(key); }, [onPathChange, onClickItem]); const [showError, setShowError] = useState(''); const handleBlur = useCallback(() => { setShowError(''); const name = nameRef.current?.textContent; if (editing) { setEditing(false); if (file.name !== name) { if (file._isDirectory) { onEditFolderName(file.path, name); } else if (name) { const { newpath, } = getOldNewPath(file.path, name); const files = getAllFiles(); if (!files[newpath]) { onEditFileName(file.path, name); } } } } else { if (file._isDirectory) { onConfirmAddFolder({ ...file, name, }) } else { onConfirmAddFile({ ...file, name, }); } } }, [ getAllFiles, editing, file, onEditFileName, onConfirmAddFile, onConfirmAddFolder, onEditFolderName, ]); const handleChange = useCallback(() => { const name = nameRef.current?.textContent; if (!name) { return setShowError('文件名不能为空'); } if (file.name === name) { return setShowError(''); } const { newpath, } = getOldNewPath(file.path, name); const filenames = Object.keys(getAllFiles()); for (let i = 0; i < filenames.length; i++) { if (newpath === filenames[i]) { return setShowError('文件名已存在'); } else if (filenames[i].startsWith(newpath + '/')) { return setShowError('文件名已存在'); } } setShowError(''); }, [getAllFiles, file]); const handleKeyDown = useCallback((e: any) => { if (e.keyCode === 13) { e.preventDefault(); handleBlur(); } }, [handleBlur]); useEffect(() => { if (!root && !file.name) { nameRef.current!.focus(); } }, [file, root]); useEffect(() => { if (editing) { const dotIndex = file.name.indexOf('.'); nameRef.current!.textContent = file.name; nameRef.current!.focus(); const selection = window.getSelection(); const range = document.createRange(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain range.setStart(nameRef.current?.firstChild!, 0); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain range.setEnd(nameRef.current?.firstChild!, dotIndex > 0 ? dotIndex : file.name.length); selection?.removeAllRanges(); selection?.addRange(range); } }, [editing, file]); useEffect(() => { const callback = (e: any) => { setShowChild(false); } // 监听收起事件 window.addEventListener('__COLLAPSE__FILE__', callback) return () => { window.removeEventListener('__COLLAPSE__FILE__', callback) }; }, []); const keys = useMemo(() => { if (file._isFile) return []; const childs = file.children; const folders = Object.keys(childs).filter(key => !childs[key]._isFile).sort(); const files = Object.keys(childs).filter(key => childs[key]._isFile).sort(); return folders.concat(files); }, [file]); useEffect(() => { if (currentPath && currentPath.startsWith(file.path + '/')) { setShowChild(true); } }, [currentPath, file.path]); useEffect(() => { // auto expand if there is a new file in subfolder const traverse = (item: any) => { return !item.name || Object.values(item?.children || []).some?.(traverse); }; const hasNewFile = traverse(file); if (hasNewFile) { setShowChild(true); } }, [file]); const handleFileAction = (action: string, path: string, isFile: boolean) => { switch (action) { case 'newFile': if (!isFile) { setShowChild(true); onAddFile(path + '/'); } break; case 'newFolder': if (!isFile) { setShowChild(true); onAddFolder(path + '/'); } break; case 'delete': isFile ? onDeleteFile(path) : onDeleteFolder(path); break; case 'editName': setEditing(true) break; default: break; } }; if (file._isFile) { let fileType; if (file.name && file.name.indexOf('.') !== -1) { fileType = `file_type_${file.name.split('.').slice(-1)}`; } else { fileType = 'default_file'; } return (
onContextMenu(e, handleFileAction)} className={`music-monaco-editor-list-file-item-row ${currentPath === file.path ? 'music-monaco-editor-list-file-item-row-focused' : ''}`}> { (file.name && !editing) ? ( <> {file.name} { (disableFileOps.rename || useFileMenu) ? null : { e.stopPropagation(); setEditing(true); }} className="music-monaco-editor-list-split-icon" /> } { (disableFileOps.delete || useFileMenu) ? null : ( { e.stopPropagation(); onDeleteFile(file.path); }} className="music-monaco-editor-list-split-icon" /> ) } ) : ( <>
{ e.stopPropagation(); }} onInput={handleChange} spellCheck={false} onKeyDown={handleKeyDown} onBlur={handleBlur} ref={nameRef} className={`music-monaco-editor-list-file-item-new ${showError ? 'music-monaco-editor-list-file-item-new-error' : ''}`} contentEditable>
) }
) } return (
{ file._isDirectory && (
onContextMenu(e, handleFileAction)}> {(file.name && !editing) ? ( <> {file.name} { (disableFolderOps.rename || useFileMenu) ? null : ( { e.stopPropagation(); setEditing(true); }} className="music-monaco-editor-list-split-icon" /> ) } { (disableFolderOps.delete || useFileMenu) ? null : ( { e.stopPropagation(); onDeleteFolder(file.path); }} className="music-monaco-editor-list-split-icon" /> ) } { (disableFileOps.add || useFileMenu) ? null : ( { e.stopPropagation(); setShowChild(true); onAddFile(file.path + '/'); }} className="music-monaco-editor-list-split-icon" /> ) } { (disableFolderOps.add || useFileMenu) ? null : ( { e.stopPropagation(); setShowChild(true); onAddFolder(file.path + '/'); }} className="music-monaco-editor-list-split-icon" /> ) } ) : (
{ e.stopPropagation(); }} onInput={handleChange} spellCheck={false} onKeyDown={handleKeyDown} onBlur={handleBlur} ref={nameRef} className={`music-monaco-editor-list-file-item-new ${showError ? 'music-monaco-editor-list-file-item-new-error' : ''}`} contentEditable /> ) }
) } { (showChild || root) && (
{ keys.map(item => ( )) }
) }
) } export default File;