jeanma's picture
Omnilingual ASR transcription demo
ae238b3 verified
import { useCallback, useState, DragEvent, useRef } from 'react';
interface UseDragAndDropOptions {
onFileDropped: (file: File) => void;
acceptedTypes?: string[];
}
interface UseDragAndDropReturn {
isDragActive: boolean;
dragProps: {
onDrop: (event: DragEvent<HTMLDivElement>) => void;
onDragOver: (event: DragEvent<HTMLDivElement>) => void;
onDragEnter: (event: DragEvent<HTMLDivElement>) => void;
onDragLeave: (event: DragEvent<HTMLDivElement>) => void;
};
}
const isValidFileType = (file: File, acceptedTypes?: string[]): boolean => {
if (!acceptedTypes || acceptedTypes.length === 0) return true;
return acceptedTypes.some(type => {
if (type.endsWith('/*')) {
// Handle MIME type categories like 'video/*' or 'audio/*'
const category = type.slice(0, -2);
return file.type.startsWith(category + '/');
} else {
// Handle exact MIME types
return file.type === type;
}
});
};
export const useDragAndDrop = ({
onFileDropped,
acceptedTypes = ['video/*', 'audio/*']
}: UseDragAndDropOptions): UseDragAndDropReturn => {
const [isDragActive, setIsDragActive] = useState(false);
const dragCounter = useRef(0);
const dragLeaveTimeout = useRef<number | null>(null);
const handleDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
// Clear any pending drag leave timeout
if (dragLeaveTimeout.current) {
clearTimeout(dragLeaveTimeout.current);
dragLeaveTimeout.current = null;
}
dragCounter.current += 1;
if (event.dataTransfer?.items && event.dataTransfer.items.length > 0) {
setIsDragActive(true);
}
}, []);
const handleDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
dragCounter.current -= 1;
// Use a small timeout to prevent flickering when moving between child elements
dragLeaveTimeout.current = window.setTimeout(() => {
if (dragCounter.current === 0) {
setIsDragActive(false);
}
}, 10);
}, []);
const handleDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
// Set the dropEffect to indicate this is a copy operation
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy';
}
}, []);
const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
// Clear timeout and reset state
if (dragLeaveTimeout.current) {
clearTimeout(dragLeaveTimeout.current);
dragLeaveTimeout.current = null;
}
setIsDragActive(false);
dragCounter.current = 0;
const files = Array.from(event.dataTransfer?.files || []);
if (files.length > 0) {
const validFile = files.find(file => isValidFileType(file, acceptedTypes));
if (validFile) {
onFileDropped(validFile);
} else {
console.warn('Dropped file is not a supported type:', files[0]?.type);
// You could also call an onError callback here if needed
}
}
}, [onFileDropped, acceptedTypes]);
return {
isDragActive,
dragProps: {
onDrop: handleDrop,
onDragOver: handleDragOver,
onDragEnter: handleDragEnter,
onDragLeave: handleDragLeave,
},
};
};