254 lines
9.2 KiB
TypeScript
254 lines
9.2 KiB
TypeScript
import React, { useState, useEffect, ChangeEvent, useRef, useCallback } from 'react';
|
|
import { Input } from "@/components/ui/input";
|
|
import { Upload } from 'lucide-react';
|
|
import {FileMeta,CustomFile } from '@/types/webrtc';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
} from '@/components/ui/dialog';
|
|
// Add this declaration at the top of the file to extend existing types and avoid IDE errors
|
|
declare module "@/components/ui/input" {
|
|
interface InputProps {
|
|
webkitdirectory?: string | boolean;
|
|
directory?: string | boolean;
|
|
}
|
|
}
|
|
|
|
import { getDictionary } from '@/lib/dictionary';
|
|
import { useLocale } from '@/hooks/useLocale';
|
|
import type { Messages } from '@/types/messages';
|
|
import { en } from '@/constants/messages/en'; // Import English dictionary as default
|
|
|
|
const traverseFileTree = async (item: FileSystemEntry, path = ''): Promise<CustomFile[]> => {
|
|
return new Promise((resolve) => {
|
|
// console.log('path',path)//path in ['','test/','test/sub/']
|
|
if (item.isFile) {
|
|
(item as FileSystemFileEntry).file((file: File) => {
|
|
// console.log('file.name',file.name)//file.name in ['Gmail-773240713232313363.txt','link.txt','cvat-serverless部署踩坑及部署模型测试 (1).docx','images.jpg']
|
|
// console.log('fullName',path + file.name,'folderName',path.split('/')[0])
|
|
const customFile: CustomFile = Object.assign(file, { fullName: path + file.name, folderName: path.split('/')[0] });
|
|
resolve([customFile]);
|
|
});
|
|
} else if (item.isDirectory) {
|
|
const dirReader = (item as FileSystemDirectoryEntry).createReader();
|
|
let entries: FileSystemEntry[] = [];
|
|
|
|
const readEntries = () => {
|
|
dirReader.readEntries(async (results) => {
|
|
if (results.length) {
|
|
entries = entries.concat(Array.from(results));
|
|
readEntries();
|
|
} else {
|
|
const newPath = path + item.name + '/';
|
|
const subResults = await Promise.all(
|
|
entries.map((entry) => traverseFileTree(entry, newPath))
|
|
);
|
|
// console.log('subResults',subResults)
|
|
const files: CustomFile[] = subResults.flat();
|
|
// console.log('files',files)
|
|
resolve(files); // Removed conditional judgment, directly return processed files
|
|
}
|
|
});
|
|
};
|
|
|
|
readEntries();
|
|
}
|
|
});
|
|
};
|
|
|
|
function formatFileChosen(
|
|
template: string,
|
|
fileNum: number,
|
|
folderNum: number
|
|
) {
|
|
return template.replace('{fileNum}', fileNum.toString())
|
|
.replace('{folderNum}', folderNum.toString());
|
|
}
|
|
|
|
interface FileUploadHandlerProps {
|
|
onFilePicked: (files: CustomFile[]) => void;
|
|
}
|
|
|
|
const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
|
onFilePicked
|
|
}) => {
|
|
const locale = useLocale();
|
|
const [messages, setMessages] = useState<Messages>(en); // Use English dictionary as initial value
|
|
|
|
const dropZoneRef = useRef<HTMLDivElement>(null);// Drag and drop files to attachments -- support
|
|
const folderInputRef = useRef<HTMLInputElement>(null);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
// File selector -- message prompt
|
|
const [fileText, setFileText] = useState<string>(en.text.fileUploadHandler.NoFileChosen_tips);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (locale !== 'en') { // Only load other language packs if not English
|
|
getDictionary(locale)
|
|
.then(dict => {setMessages(dict);setFileText(dict.text.fileUploadHandler.NoFileChosen_tips);})
|
|
.catch(error => console.error('Failed to load messages:', error));
|
|
}
|
|
}, [locale]);
|
|
|
|
const handleFileChange = useCallback((newFiles: CustomFile[]) => {
|
|
// console.log(newFiles);
|
|
onFilePicked(newFiles);
|
|
|
|
const fileNum = newFiles.length;
|
|
const folderNum = newFiles.filter(file => file.folderName).length;
|
|
|
|
const choose_dis = formatFileChosen(
|
|
messages!.text.fileUploadHandler.fileChosen_tips_template, fileNum, folderNum
|
|
);
|
|
|
|
setFileText(choose_dis);
|
|
setTimeout(() => setFileText(messages!.text.fileUploadHandler.NoFileChosen_tips), 2000);
|
|
// Reset the file input
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = '';
|
|
}
|
|
}, [messages,onFilePicked]);
|
|
// Drag and drop folder upload response processing
|
|
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const items = e.dataTransfer.items;
|
|
if (items) {
|
|
const itemsArray = Array.from(items);
|
|
Promise.all(itemsArray.map(item => {
|
|
const entry = item.webkitGetAsEntry();
|
|
if (entry) {
|
|
return traverseFileTree(entry);
|
|
}
|
|
return Promise.resolve([]);
|
|
})).then(results => {
|
|
const allFiles = results.flat();
|
|
handleFileChange(allFiles);
|
|
});
|
|
}
|
|
}, [handleFileChange]);
|
|
/* Define a callback function handleDragOver to handle the drag-over event.
|
|
In handleDragOver, prevent default behavior and event propagation to ensure custom handling.
|
|
There is no dependency array, which means the handleDragOver function will only be created once when the component first renders, and will not be re-created in subsequent renders.
|
|
*/
|
|
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}, []);
|
|
// Click to upload file processing
|
|
const handleFileInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files) {
|
|
const files = Array.from(e.target.files);
|
|
let files2 = [];
|
|
for(let file of files){
|
|
const customFile: CustomFile = Object.assign(file, { fullName: file.name, folderName: '' });
|
|
files2.push(customFile);
|
|
}
|
|
handleFileChange(files2);
|
|
setIsModalOpen(false);// Close the dialog
|
|
}
|
|
}, [handleFileChange]);
|
|
// Click to upload folder response processing
|
|
const handleFolderInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files) {
|
|
const files_ = Array.from(e.target.files);
|
|
let files:CustomFile[] = [];
|
|
|
|
files_.forEach(file => {
|
|
// console.log('file.webkitRelativePath',file.webkitRelativePath)//[test/Gmail-773240713232313363.txt,test/link.txt,test/sub/cvat-serverless部署踩坑及部署模型测试 (1).docx,test/sub/images.jpg]
|
|
const pathParts = file.webkitRelativePath.split('/');
|
|
const customFile: CustomFile = Object.assign(file, { fullName: file.webkitRelativePath, folderName: pathParts[0] });
|
|
files.push(customFile);
|
|
});
|
|
|
|
handleFileChange(files);
|
|
setIsModalOpen(false);// Close the dialog
|
|
}
|
|
}, [handleFileChange]);
|
|
|
|
// Handle drag and drop area click
|
|
const handleZoneClick = () => {
|
|
setIsModalOpen(true);
|
|
};
|
|
// Handle file selection
|
|
const handleSelectFile = () => {
|
|
fileInputRef.current?.click();
|
|
};
|
|
|
|
// Handle folder selection
|
|
const handleSelectFolder = () => {
|
|
folderInputRef.current?.click();
|
|
};
|
|
if (messages === null) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
return (
|
|
<>
|
|
<div
|
|
ref={dropZoneRef}
|
|
onDrop={handleDrop}
|
|
onDragOver={handleDragOver}
|
|
className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer"
|
|
onClick={handleZoneClick}
|
|
>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
{messages.text.fileUploadHandler.Drag_tips}
|
|
</p>
|
|
<Upload className="h-12 w-12 mx-auto mb-4 text-blue-500" />
|
|
<p className="text-sm text-gray-600">{fileText}</p>
|
|
|
|
<Input
|
|
id="file-upload"
|
|
type="file"
|
|
onChange={handleFileInputChange}
|
|
multiple
|
|
className="hidden"
|
|
ref={fileInputRef}
|
|
/>
|
|
<Input
|
|
id="folder-upload"
|
|
type="file"
|
|
onChange={handleFolderInputChange}
|
|
multiple
|
|
webkitdirectory=""
|
|
directory=""
|
|
className="hidden"
|
|
ref={folderInputRef}
|
|
/>
|
|
</div>
|
|
|
|
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
|
<DialogContent className="sm:max-w-[425px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-xl font-semibold">
|
|
{messages.text.fileUploadHandler.chosenDiagTitle}
|
|
</DialogTitle>
|
|
<DialogDescription className="mt-2 text-muted-foreground">
|
|
{messages.text.fileUploadHandler.chosenDiagDescription}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="flex justify-center gap-4 mt-6">
|
|
<button
|
|
onClick={handleSelectFile}
|
|
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
|
|
>
|
|
{messages.text.fileUploadHandler.SelectFile_dis}
|
|
</button>
|
|
<button
|
|
onClick={handleSelectFolder}
|
|
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors"
|
|
>
|
|
{messages.text.fileUploadHandler.SelectFolder_dis}
|
|
</button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export { FileUploadHandler }; |