Repository: gradio-app/gradio
Pull request: Few File component drag and drop
## Description
- Fixes Drag and Drop for types of the first element in file_types
- Added playwright playwright tests for drag & drop

Closes: #7094

## 🎯 PRs Should Target Issues

Before your create a PR, please check to see if there is [an existing issue](https://github.com/gradio-app/gradio/issues) for this change. If not, please create an issue before you create this PR, unless the fix is very small. 

Not adhering to this guideline will result in the PR being closed. 

## Tests

1. PRs will only be merged if tests pass on CI. To run the tests locally, please set up [your Gradio environment locally](https://github.com/gradio-app/gradio/blob/main/CONTRIBUTING.md) and run the tests: `bash scripts/run_all_tests.sh`

2. You may need to run the linters: `bash scripts/format_backend.sh` and `bash scripts/format_frontend.sh`
  


Problem statement:
gr.File only supports Drag and Drop for types of the first element in file_types
### Describe the bug

gr.File( file_types = [ 'image', 'video' ] ) only supports Drag and Drop for image file but not video file. (However, clicking interface to select file to upload is both support for the two. )
gr.File( file_types = [ 'file' ] ) can support all types to Drag and Drop but...

### Have you searched existing issues?  🔎

- [X] I have searched and found no existing issues

### Reproduction

```python
import gradio as gr
def generate_file(file_obj):
    return None
if __name__ == "__main__":
    inputs = gr.components.File( file_types = [ 'image', 'video' ] )
    outputs = gr.components.File()
    demo = gr.Interface(fn=generate_file, inputs=inputs, outputs=outputs)
    demo.launch(share=True)
```

### Screenshot

_No response_

### Logs

_No response_

### System Info

```shell
Gradio Environment Information:
------------------------------
Operating System: Linux
gradio version: 4.15.0
gradio_client version: 0.8.1

------------------------------------------------
gradio dependencies in your environment:

aiofiles: 23.2.1
altair: 5.2.0
fastapi: 0.109.0
ffmpy: 0.3.1
gradio-client==0.8.1 is not installed.
httpx: 0.26.0
huggingface-hub: 0.20.2
importlib-resources: 6.1.1
jinja2: 3.1.3
markupsafe: 2.1.3
matplotlib: 3.8.2
numpy: 1.26.2
orjson: 3.9.10
packaging: 23.2
pandas: 2.1.4
pillow: 10.2.0
pydantic: 2.5.3
pydub: 0.25.1
python-multipart: 0.0.6
pyyaml: 6.0.1
ruff: 0.1.14
semantic-version: 2.10.0
tomlkit==0.12.0 is not installed.
typer: 0.9.0
typing-extensions: 4.9.0
uvicorn: 0.25.0
authlib; extra == 'oauth' is not installed.
itsdangerous; extra == 'oauth' is not installed.

gradio_client dependencies in your environment:

fsspec: 2023.12.2
httpx: 0.26.0
huggingface-hub: 0.20.2
packaging: 23.2
typing-extensions: 4.9.0
websockets: 11.0.3
```


### Severity

I can work around it

Hints from issue discussion:
I thought we fixed this? @dawoodkhan82 can we add some functional tests to prevent this issue from reoccurring
Seems like an edge case that I didn't test for. I fixed it for specific file types, and added functional tests for those. But not generic file types like "image" and "video". Will fix!

Commit message: test fixes

Patch to review:
diff --git a/.changeset/puny-clowns-invent.md b/.changeset/puny-clowns-invent.md
new file mode 100644
index 0000000000..b6fd1252ce
--- /dev/null
+++ b/.changeset/puny-clowns-invent.md
@@ -0,0 +1,7 @@
+---
+"@gradio/file": patch
+"@gradio/upload": patch
+"gradio": patch
+---
+
+fix:Few File component fixes
diff --git a/demo/file_component_events/run.ipynb b/demo/file_component_events/run.ipynb
index af0f035d70..14027492c2 100644
--- a/demo/file_component_events/run.ipynb
+++ b/demo/file_component_events/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: file_component_events"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", "    \n", "    with gr.Row():\n", "        with gr.Column():\n", "            file_component = gr.File(label=\"Upload Single File\", file_count=\"single\")\n", "        with gr.Column():\n", "            output_file_1 = gr.File(label=\"Upload Single File Output\", file_count=\"single\")\n", "            num_load_btn_1 = gr.Number(label=\"# Load Upload Single File\", value=0)\n", "            file_component.upload(lambda s,n: (s, n + 1), [file_component, num_load_btn_1], [output_file_1, num_load_btn_1])\n", "    with gr.Row():\n", "        with gr.Column():\n", "            file_component_multiple = gr.File(label=\"Upload Multiple Files\", file_count=\"multiple\")\n", "        with gr.Column():\n", "            output_file_2 = gr.File(label=\"Upload Multiple Files Output\", file_count=\"multiple\")\n", "            num_load_btn_2 = gr.Number(label=\"# Load Upload Multiple Files\", value=0)\n", "            file_component_multiple.upload(lambda s,n: (s, n + 1), [file_component_multiple, num_load_btn_2], [output_file_2, num_load_btn_2])\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: file_component_events"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", "    \n", "    with gr.Row():\n", "        with gr.Column():\n", "            file_component = gr.File(label=\"Upload Single File\", file_count=\"single\")\n", "        with gr.Column():\n", "            output_file_1 = gr.File(label=\"Upload Single File Output\", file_count=\"single\")\n", "            num_load_btn_1 = gr.Number(label=\"# Load Upload Single File\", value=0)\n", "            file_component.upload(lambda s,n: (s, n + 1), [file_component, num_load_btn_1], [output_file_1, num_load_btn_1])\n", "    with gr.Row():\n", "        with gr.Column():\n", "            file_component_multiple = gr.File(label=\"Upload Multiple Files\", file_count=\"multiple\")\n", "        with gr.Column():\n", "            output_file_2 = gr.File(label=\"Upload Multiple Files Output\", file_count=\"multiple\")\n", "            num_load_btn_2 = gr.Number(label=\"# Load Upload Multiple Files\", value=0)\n", "            file_component_multiple.upload(lambda s,n: (s, n + 1), [file_component_multiple, num_load_btn_2], [output_file_2, num_load_btn_2])\n", "    with gr.Row():\n", "        with gr.Column():\n", "            file_component_specific = gr.File(label=\"Upload Multiple Files Image/Video\", file_count=\"multiple\", file_types=[\"image\", \"video\"])\n", "        with gr.Column():\n", "            output_file_3 = gr.File(label=\"Upload Multiple Files Output Image/Video\", file_count=\"multiple\")\n", "            num_load_btn_3 = gr.Number(label=\"# Load Upload Multiple Files Image/Video\", value=0)\n", "            file_component_specific.upload(lambda s,n: (s, n + 1), [file_component_specific, num_load_btn_3], [output_file_3, num_load_btn_3])\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/file_component_events/run.py b/demo/file_component_events/run.py
index 12a5a02c3f..3dc83c0b9b 100644
--- a/demo/file_component_events/run.py
+++ b/demo/file_component_events/run.py
@@ -16,6 +16,13 @@
             output_file_2 = gr.File(label="Upload Multiple Files Output", file_count="multiple")
             num_load_btn_2 = gr.Number(label="# Load Upload Multiple Files", value=0)
             file_component_multiple.upload(lambda s,n: (s, n + 1), [file_component_multiple, num_load_btn_2], [output_file_2, num_load_btn_2])
+    with gr.Row():
+        with gr.Column():
+            file_component_specific = gr.File(label="Upload Multiple Files Image/Video", file_count="multiple", file_types=["image", "video"])
+        with gr.Column():
+            output_file_3 = gr.File(label="Upload Multiple Files Output Image/Video", file_count="multiple")
+            num_load_btn_3 = gr.Number(label="# Load Upload Multiple Files Image/Video", value=0)
+            file_component_specific.upload(lambda s,n: (s, n + 1), [file_component_specific, num_load_btn_3], [output_file_3, num_load_btn_3])
 
 if __name__ == "__main__":
     demo.launch()
diff --git a/js/app/test/file_component_events.spec.ts b/js/app/test/file_component_events.spec.ts
index 975482baba..6bfef6868c 100644
--- a/js/app/test/file_component_events.spec.ts
+++ b/js/app/test/file_component_events.spec.ts
@@ -7,7 +7,7 @@ test("File component properly dispatches load event for the single file case.",
 		.getByRole("button", { name: "Drop File Here - or - Click to Upload" })
 		.first()
 		.click();
-	const uploader = await page.getByTestId("file-upload").first();
+	const uploader = await page.locator("input[type=file]").first();
 	await uploader.setInputFiles(["./test/files/cheetah1.jpg"]);
 
 	await expect(page.getByLabel("# Load Upload Single File")).toHaveValue("1");
@@ -18,36 +18,34 @@ test("File component properly dispatches load event for the single file case.",
 	await expect(download.suggestedFilename()).toBe("cheetah1.jpg");
 });
 
-test("File component properly dispatches load event for the multiple file case.", async ({
+test("File component drag-and-drop uploads a file to the server correctly.", async ({
 	page
 }) => {
-	await page
-		.getByRole("button", { name: "Drop File Here - or - Click to Upload" })
-		.last()
-		.click();
-	const uploader = await page.getByTestId("file-upload").last();
-	await uploader.setInputFiles([
-		"./test/files/face.obj",
-		"./test/files/cheetah1.jpg"
-	]);
+	const uploader = await page.locator("input[type=file]").nth(1);
+	await uploader.setInputFiles(["./test/files/alphabet.txt"]);
 
-	await expect(page.getByLabel("# Load Upload Multiple Files")).toHaveValue(
-		"1"
-	);
-
-	const downloadPromise = page.waitForEvent("download");
-	await page.getByRole("link").nth(1).click();
-	const download = await downloadPromise;
-	await expect(download.suggestedFilename()).toBe("cheetah1.jpg");
+	await expect(
+		page.getByLabel("# Load Upload Multiple Files").first()
+	).toHaveValue("1");
 });
 
-test("File component drag-and-drop uploads a file to the server correctly.", async ({
+test("File component properly handles drag and drop of image and video files.", async ({
 	page
 }) => {
-	await drag_and_drop_file(
-		page,
-		"input[type=file]",
-		"./test/files/alphabet.txt",
-		"alphabet.txt"
-	);
+	const uploader = await page.locator("input[type=file]").last();
+	await uploader.setInputFiles(["./test/files/cheetah1.jpg"]);
+
+	// Check that the image file was uploaded
+	await expect(
+		page.getByLabel("# Load Upload Multiple Files Image/Video")
+	).toHaveValue("1");
+
+	await page.getByLabel("Clear").click();
+
+	await uploader.setInputFiles(["./test/files/world.mp4"]);
+
+	// Check that the video file was uploaded
+	await expect(
+		page.getByLabel("# Load Upload Multiple Files Image/Video")
+	).toHaveValue("2");
 });
diff --git a/js/app/test/files/world.mp4 b/js/app/test/files/world.mp4
new file mode 100644
index 0000000000..b11552f9cb
Binary files /dev/null and b/js/app/test/files/world.mp4 differ
diff --git a/js/file/shared/File.svelte b/js/file/shared/File.svelte
index 8b97538910..00fbb85cb6 100644
--- a/js/file/shared/File.svelte
+++ b/js/file/shared/File.svelte
@@ -20,7 +20,7 @@
 	label={label || "File"}
 />
 
-{#if value && (Array.isArray(value) ? value.length > 0 : true)}
+{#if (Array.isArray(value) && value.length > 0) || value !== null}
 	<FilePreview {i18n} {selectable} on:select {value} {height} />
 {:else}
 	<Empty unpadded_box={true} size="large"><File /></Empty>
diff --git a/js/file/shared/FileUpload.svelte b/js/file/shared/FileUpload.svelte
index be6c4eaeb9..3fca386dd7 100644
--- a/js/file/shared/FileUpload.svelte
+++ b/js/file/shared/FileUpload.svelte
@@ -67,7 +67,7 @@
 	label={label || "File"}
 />
 
-{#if value && (Array.isArray(value) ? value.length > 0 : true)}
+{#if (Array.isArray(value) && value.length > 0) || value !== null}
 	<ModifyUpload {i18n} on:clear={handle_clear} absolute />
 	<FilePreview {i18n} on:select {selectable} {value} {height} />
 {:else}
diff --git a/js/upload/src/Upload.svelte b/js/upload/src/Upload.svelte
index 6a246f0943..f1eb95f6f5 100644
--- a/js/upload/src/Upload.svelte
+++ b/js/upload/src/Upload.svelte
@@ -27,7 +27,6 @@
 	let hidden_upload: HTMLInputElement;
 
 	const dispatch = createEventDispatcher();
-
 	$: if (filetype == null || typeof filetype === "string") {
 		accept_file_types = filetype;
 	} else {
@@ -111,19 +110,21 @@
 		if (!file_accept || file_accept === "*" || file_accept === "file/*") {
 			return true;
 		}
-		if (typeof file_accept === "string" && file_accept.endsWith("/*")) {
-			file_accept = file_accept.split(",");
-		}
-		if (Array.isArray(file_accept)) {
-			return (
-				file_accept.includes(mime_type) ||
-				file_accept.some((type) => {
-					const [category] = type.split("/");
-					return type.endsWith("/*") && mime_type.startsWith(category + "/");
-				})
-			);
+		let acceptArray: string[];
+		if (typeof file_accept === "string") {
+			acceptArray = file_accept.split(",");
+		} else if (Array.isArray(file_accept)) {
+			acceptArray = file_accept;
+		} else {
+			return false;
 		}
-		return file_accept === mime_type;
+		return (
+			acceptArray.includes(mime_type) ||
+			acceptArray.some((type) => {
+				const [category] = type.split("/").map((s) => s.trim());
+				return type.endsWith("/*") && mime_type.startsWith(category + "/");
+			})
+		);
 	}
 
 	async function loadFilesFromDrop(e: DragEvent): Promise<void> {


Code review comments:
diff_hunk: @@ -20,7 +20,7 @@
 	label={label || "File"}
 />
 
-{#if value && (Array.isArray(value) ? value.length > 0 : true)}
+{#if (Array.isArray(value) && value.length > 0) || value !== null}
path: js/file/shared/File.svelte
text: @user1:
#7132 has already been fixed so I don't think we need to make this change? 

@author:
Either is fine, but I can remove my change.

@user1:
The original seems slightly simpler to parse and we've already validated it, so maybe keep the original? Not a big deal either way