summaryrefslogtreecommitdiff
path: root/apps/web/src/media/use-file-upload.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/media/use-file-upload.ts')
-rw-r--r--apps/web/src/media/use-file-upload.ts101
1 files changed, 101 insertions, 0 deletions
diff --git a/apps/web/src/media/use-file-upload.ts b/apps/web/src/media/use-file-upload.ts
new file mode 100644
index 00000000..1ee30ad9
--- /dev/null
+++ b/apps/web/src/media/use-file-upload.ts
@@ -0,0 +1,101 @@
+import { useState, useRef } from "react";
+import { hasDragData } from "@/timeline/drag-data";
+
+interface UseFileUploadOptions {
+ accept?: string;
+ multiple?: boolean;
+ onFilesSelected?: (files: File[]) => void;
+}
+
+function containsFiles(dataTransfer: DataTransfer): boolean {
+ return !hasDragData({ dataTransfer }) && dataTransfer.types.includes("Files");
+}
+
+export function useFileUpload({
+ accept,
+ multiple,
+ onFilesSelected,
+}: UseFileUploadOptions = {}) {
+ const [isDragOver, setIsDragOver] = useState(false);
+ const dragCounterRef = useRef(0);
+ const inputRef = useRef<HTMLInputElement>(null);
+
+ function openFilePicker() {
+ if (!inputRef.current) return;
+
+ inputRef.current.accept = accept || "*";
+ inputRef.current.multiple = multiple || false;
+ inputRef.current.click();
+ }
+
+ function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
+ const files = Array.from(event.target.files ?? []);
+ if (files.length > 0 && onFilesSelected) {
+ onFilesSelected(files);
+ }
+
+ if (event.target) {
+ event.target.value = "";
+ }
+ }
+
+ function handleDragEnter(e: React.DragEvent) {
+ e.preventDefault();
+
+ if (!containsFiles(e.dataTransfer)) return;
+
+ dragCounterRef.current += 1;
+ setIsDragOver(true);
+ }
+
+ function handleDragOver(e: React.DragEvent) {
+ e.preventDefault();
+
+ if (!containsFiles(e.dataTransfer)) return;
+ }
+
+ function handleDragLeave(e: React.DragEvent) {
+ e.preventDefault();
+
+ if (!containsFiles(e.dataTransfer)) return;
+
+ dragCounterRef.current -= 1;
+ if (dragCounterRef.current === 0) {
+ setIsDragOver(false);
+ }
+ }
+
+ function handleDrop(e: React.DragEvent) {
+ e.preventDefault();
+ setIsDragOver(false);
+ dragCounterRef.current = 0;
+
+ if (onFilesSelected && containsFiles(e.dataTransfer)) {
+ const files = Array.from(e.dataTransfer.files);
+ const shouldUseMultiple = multiple ?? false;
+
+ if (shouldUseMultiple) {
+ onFilesSelected(files);
+ } else if (files.length > 0) {
+ onFilesSelected([files[0]]);
+ }
+ }
+ }
+
+ return {
+ isDragOver,
+ openFilePicker,
+ fileInputProps: {
+ ref: inputRef,
+ type: "file",
+ style: { display: "none" },
+ onChange: handleFileChange,
+ },
+ dragProps: {
+ onDragEnter: handleDragEnter,
+ onDragOver: handleDragOver,
+ onDragLeave: handleDragLeave,
+ onDrop: handleDrop,
+ },
+ };
+}