File size: 3,402 Bytes
ae238b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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,
    },
  };
};