add delete event to File component (#8417)

* fix param name

* format

* add delete event to File component

* fix

* add changeset

* fix

* fix

* fix

* add changeset

* format

* add changeset

* file file layout

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
pngwn 2024-06-05 13:14:10 +01:00 committed by GitHub
parent 63d36fbbf4
commit 96d8de2312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 189 additions and 40 deletions

View File

@ -0,0 +1,8 @@
---
"@gradio/file": minor
"@gradio/tootils": minor
"@gradio/upload": minor
"gradio": minor
---
feat:add delete event to `File` component

View File

@ -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", " 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", " with gr.Row():\n", " with gr.Column():\n", " file_component_pdf = gr.File(label=\"Upload PDF File\", file_types=[\"pdf\"])\n", " with gr.Column():\n", " output_file_4 = gr.File(label=\"Upload PDF File Output\")\n", " num_load_btn_4 = gr.Number(label=\"# Load Upload PDF File\", value=0)\n", " file_component_pdf.upload(lambda s,n: (s, n + 1), [file_component_pdf, num_load_btn_4], [output_file_4, num_load_btn_4])\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_invalid = gr.File(label=\"Upload File with Invalid file_types\", file_types=[\"invalid file_type\"])\n", " with gr.Column():\n", " output_file_5 = gr.File(label=\"Upload File with Invalid file_types Output\")\n", " num_load_btn_5 = gr.Number(label=\"# Load Upload File with Invalid file_types\", value=0)\n", " file_component_invalid.upload(lambda s,n: (s, n + 1), [file_component_invalid, num_load_btn_5], [output_file_5, num_load_btn_5])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"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", "\n", "def delete_file(n: int, file: gr.DeletedFileData):\n", " return [file.file.path, n + 1]\n", "\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(\n", " label=\"Upload Single File Output\", file_count=\"single\"\n", " )\n", " num_load_btn_1 = gr.Number(label=\"# Load Upload Single File\", value=0)\n", " file_component.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component, num_load_btn_1],\n", " [output_file_1, num_load_btn_1],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_multiple = gr.File(\n", " label=\"Upload Multiple Files\", file_count=\"multiple\"\n", " )\n", " with gr.Column():\n", " output_file_2 = gr.File(\n", " label=\"Upload Multiple Files Output\", file_count=\"multiple\"\n", " )\n", " num_load_btn_2 = gr.Number(label=\"# Load Upload Multiple Files\", value=0)\n", " file_component_multiple.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_multiple, num_load_btn_2],\n", " [output_file_2, num_load_btn_2],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_specific = gr.File(\n", " label=\"Upload Multiple Files Image/Video\",\n", " file_count=\"multiple\",\n", " file_types=[\"image\", \"video\"],\n", " )\n", " with gr.Column():\n", " output_file_3 = gr.File(\n", " label=\"Upload Multiple Files Output Image/Video\", file_count=\"multiple\"\n", " )\n", " num_load_btn_3 = gr.Number(\n", " label=\"# Load Upload Multiple Files Image/Video\", value=0\n", " )\n", " file_component_specific.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_specific, num_load_btn_3],\n", " [output_file_3, num_load_btn_3],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_pdf = gr.File(label=\"Upload PDF File\", file_types=[\"pdf\"])\n", " with gr.Column():\n", " output_file_4 = gr.File(label=\"Upload PDF File Output\")\n", " num_load_btn_4 = gr.Number(label=\"# Load Upload PDF File\", value=0)\n", " file_component_pdf.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_pdf, num_load_btn_4],\n", " [output_file_4, num_load_btn_4],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_invalid = gr.File(\n", " label=\"Upload File with Invalid file_types\",\n", " file_types=[\"invalid file_type\"],\n", " )\n", " with gr.Column():\n", " output_file_5 = gr.File(label=\"Upload File with Invalid file_types Output\")\n", " num_load_btn_5 = gr.Number(\n", " label=\"# Load Upload File with Invalid file_types\", value=0\n", " )\n", " file_component_invalid.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_invalid, num_load_btn_5],\n", " [output_file_5, num_load_btn_5],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " del_file_input = gr.File(label=\"Delete File\", file_count=\"multiple\")\n", " with gr.Column():\n", " del_file_data = gr.Textbox(label=\"Delete file data\")\n", " num_load_btn_6 = gr.Number(label=\"# Deleted File\", value=0)\n", " del_file_input.delete(\n", " delete_file,\n", " [num_load_btn_6],\n", " [del_file_data, num_load_btn_6],\n", " )\n", " # f = gr.File(label=\"Upload many File\", file_count=\"multiple\")\n", " # # f.delete(delete_file)\n", " # f.delete(delete_file, inputs=None, outputs=None)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -1,42 +1,100 @@
import gradio as gr
def delete_file(n: int, file: gr.DeletedFileData):
return [file.file.path, n + 1]
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
file_component = gr.File(label="Upload Single File", file_count="single")
with gr.Column():
output_file_1 = gr.File(label="Upload Single File Output", file_count="single")
output_file_1 = gr.File(
label="Upload Single File Output", file_count="single"
)
num_load_btn_1 = gr.Number(label="# Load Upload Single File", value=0)
file_component.upload(lambda s,n: (s, n + 1), [file_component, num_load_btn_1], [output_file_1, num_load_btn_1])
file_component.upload(
lambda s, n: (s, n + 1),
[file_component, num_load_btn_1],
[output_file_1, num_load_btn_1],
)
with gr.Row():
with gr.Column():
file_component_multiple = gr.File(label="Upload Multiple Files", file_count="multiple")
file_component_multiple = gr.File(
label="Upload Multiple Files", file_count="multiple"
)
with gr.Column():
output_file_2 = gr.File(label="Upload Multiple Files Output", file_count="multiple")
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])
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"])
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])
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],
)
with gr.Row():
with gr.Column():
file_component_pdf = gr.File(label="Upload PDF File", file_types=["pdf"])
with gr.Column():
output_file_4 = gr.File(label="Upload PDF File Output")
num_load_btn_4 = gr.Number(label="# Load Upload PDF File", value=0)
file_component_pdf.upload(lambda s,n: (s, n + 1), [file_component_pdf, num_load_btn_4], [output_file_4, num_load_btn_4])
file_component_pdf.upload(
lambda s, n: (s, n + 1),
[file_component_pdf, num_load_btn_4],
[output_file_4, num_load_btn_4],
)
with gr.Row():
with gr.Column():
file_component_invalid = gr.File(label="Upload File with Invalid file_types", file_types=["invalid file_type"])
file_component_invalid = gr.File(
label="Upload File with Invalid file_types",
file_types=["invalid file_type"],
)
with gr.Column():
output_file_5 = gr.File(label="Upload File with Invalid file_types Output")
num_load_btn_5 = gr.Number(label="# Load Upload File with Invalid file_types", value=0)
file_component_invalid.upload(lambda s,n: (s, n + 1), [file_component_invalid, num_load_btn_5], [output_file_5, num_load_btn_5])
num_load_btn_5 = gr.Number(
label="# Load Upload File with Invalid file_types", value=0
)
file_component_invalid.upload(
lambda s, n: (s, n + 1),
[file_component_invalid, num_load_btn_5],
[output_file_5, num_load_btn_5],
)
with gr.Row():
with gr.Column():
del_file_input = gr.File(label="Delete File", file_count="multiple")
with gr.Column():
del_file_data = gr.Textbox(label="Delete file data")
num_load_btn_6 = gr.Number(label="# Deleted File", value=0)
del_file_input.delete(
delete_file,
[num_load_btn_6],
[del_file_data, num_load_btn_6],
)
# f = gr.File(label="Upload many File", file_count="multiple")
# # f.delete(delete_file)
# f.delete(delete_file, inputs=None, outputs=None)
if __name__ == "__main__":
demo.launch()

View File

@ -60,7 +60,14 @@ from gradio.components import (
from gradio.components.audio import WaveformOptions
from gradio.components.image_editor import Brush, Eraser
from gradio.data_classes import FileData
from gradio.events import EventData, KeyUpData, LikeData, SelectData, on
from gradio.events import (
DeletedFileData,
EventData,
KeyUpData,
LikeData,
SelectData,
on,
)
from gradio.exceptions import Error
from gradio.external import load
from gradio.flagging import (

View File

@ -26,7 +26,7 @@ class File(Component):
Demo: zip_files, zip_to_json
"""
EVENTS = [Events.change, Events.select, Events.clear, Events.upload]
EVENTS = [Events.change, Events.select, Events.clear, Events.upload, Events.delete]
def __init__(
self,

View File

@ -8,7 +8,7 @@ import secrets
import shutil
from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypedDict, Union
from fastapi import Request
from gradio_client.utils import traverse
@ -189,6 +189,16 @@ class GradioRootModel(GradioBaseModel, RootModel):
GradioDataModel = Union[GradioModel, GradioRootModel]
class FileDataDict(TypedDict):
path: str # server filepath
url: Optional[str] # normalised server url
size: Optional[int] # size in bytes
orig_name: Optional[str] # original filename
mime_type: Optional[str]
is_stream: bool
meta: dict
class FileData(GradioModel):
path: str # server filepath
url: Optional[str] = None # normalised server url

View File

@ -20,6 +20,8 @@ from typing import (
from gradio_client.documentation import document
from jinja2 import Template
from gradio.data_classes import FileData, FileDataDict
if TYPE_CHECKING:
from gradio.blocks import Block, Component
@ -149,6 +151,15 @@ class KeyUpData(EventData):
"""
class DeletedFileData(EventData):
def __init__(self, target: Block | None, data: FileDataDict):
super().__init__(target, data)
self.file: FileData = FileData(**data)
"""
The file that was deleted.
"""
@dataclasses.dataclass
class EventListenerMethod:
block: Block | None
@ -585,6 +596,10 @@ class Events:
"apply",
doc="This listener is triggered when the user applies changes to the {{ component }} through an integrated UI action.",
)
delete = EventListener(
"delete",
doc="This listener is triggered when the user deletes and item from the {{ component }}. Uses event data gradio.DeletedFileData to carry `value` referring to the file that was deleted as an instance of FileData. See EventData documentation on how to use this event data",
)
class LikeData(EventData):

View File

@ -1,5 +1,13 @@
import { test, expect, drag_and_drop_file } from "@gradio/tootils";
async function error_modal_showed(page) {
const toast = page.getByTestId("toast-body");
expect(toast).toContainText("error");
const close = page.getByTestId("toast-close");
await close.click();
await expect(page.getByTestId("toast-body")).toHaveCount(0);
}
test("File component properly dispatches load event for the single file case.", async ({
page
}) => {
@ -63,11 +71,33 @@ test("File component properly handles drag and drop of pdf file.", async ({
test("File component properly handles invalid file_types.", async ({
page
}) => {
const uploader = await page.locator("input[type=file]").last();
await uploader.setInputFiles(["./test/files/cheetah1.jpg"]);
const locator = page.locator("input[type=file]").nth(4);
await drag_and_drop_file(
page,
locator,
"./test/files/cheetah1.jpg",
"cheetah1.jpg",
"image/jpeg"
);
// Check that the pdf file was uploaded
await expect(
page.getByLabel("# Load Upload File with Invalid file_types")
).toHaveValue("1");
await error_modal_showed(page);
});
test("Delete event is fired correctly", async ({ page }) => {
const locator = page.locator("input[type=file]").nth(5);
await drag_and_drop_file(
page,
locator,
"./test/files/cheetah1.jpg",
"cheetah1.jpg",
"image/jpeg",
2
);
await page.getByLabel("Remove this file").first().click();
await expect(page.getByLabel("# Deleted File")).toHaveValue("1");
expect(
(await page.getByLabel("Delete file data").inputValue()).length
).toBeGreaterThan(5);
});

View File

@ -40,6 +40,7 @@
clear: never;
select: SelectData;
clear_status: LoadingStatus;
delete: FileData;
}>;
export let file_count: string;
export let file_types: string[] = ["file"];
@ -110,6 +111,9 @@
loading_status.status = "error";
gradio.dispatch("error", detail);
}}
on:delete={({ detail }) => {
gradio.dispatch("delete", detail);
}}
i18n={gradio.i18n}
>
<UploadText i18n={gradio.i18n} type="file" />

View File

@ -8,6 +8,7 @@
const dispatch = createEventDispatcher<{
select: SelectData;
change: FileData[] | FileData;
delete: FileData;
}>();
export let value: FileData | FileData[];
export let selectable = false;
@ -46,9 +47,10 @@
}
function remove_file(index: number): void {
normalized_files.splice(index, 1);
const removed = normalized_files.splice(index, 1);
normalized_files = [...normalized_files];
value = normalized_files;
dispatch("delete", removed[0]);
dispatch("change", normalized_files);
}
</script>

View File

@ -50,16 +50,19 @@
$: dispatch("drag", dragging);
</script>
<BlockLabel
{show_label}
Icon={File}
float={value === null}
label={label || "File"}
/>
<BlockLabel {show_label} Icon={File} float={!value} label={label || "File"} />
{#if value && (Array.isArray(value) ? value.length > 0 : true)}
<ModifyUpload {i18n} on:clear={handle_clear} absolute />
<FilePreview {i18n} on:select {selectable} {value} {height} on:change />
<FilePreview
{i18n}
on:select
{selectable}
{value}
{height}
on:change
on:delete
/>
{:else}
<Upload
on:load={handle_upload}

View File

@ -1,4 +1,4 @@
import { test as base, type Page } from "@playwright/test";
import { test as base, type Locator, type Page } from "@playwright/test";
import { spy } from "tinyspy";
import url from "url";
import path from "path";
@ -163,29 +163,40 @@ export * from "./render";
export const drag_and_drop_file = async (
page: Page,
selector: string,
selector: string | Locator,
filePath: string,
fileName: string,
fileType = ""
fileType = "",
count = 1
): Promise<void> => {
const buffer = (await fsPromises.readFile(filePath)).toString("base64");
const dataTransfer = await page.evaluateHandle(
async ({ bufferData, localFileName, localFileType }) => {
async ({ bufferData, localFileName, localFileType, count }) => {
const dt = new DataTransfer();
const blobData = await fetch(bufferData).then((res) => res.blob());
const file = new File([blobData], localFileName, { type: localFileType });
dt.items.add(file);
const file = new File([blobData], localFileName, {
type: localFileType
});
for (let i = 0; i < count; i++) {
dt.items.add(file);
}
return dt;
},
{
bufferData: `data:application/octet-stream;base64,${buffer}`,
localFileName: fileName,
localFileType: fileType
localFileType: fileType,
count
}
);
await page.dispatchEvent(selector, "drop", { dataTransfer });
if (typeof selector === "string") {
await page.dispatchEvent(selector, "drop", { dataTransfer });
} else {
await selector.dispatchEvent("drop", { dataTransfer });
}
};

View File

@ -150,6 +150,7 @@
} else {
return false;
}
return (
acceptArray.includes(uploaded_file_extension) ||
acceptArray.some((type) => {