Skip to content

Commit

Permalink
Allow reordering files in gr.File (#10210)
Browse files Browse the repository at this point in the history
* allow reordering files in gr.File

* add changeset

* fix test

* Update gradio/components/file.py

Co-authored-by: Abubakar Abid <[email protected]>

---------

Co-authored-by: gradio-pr-bot <[email protected]>
Co-authored-by: Abubakar Abid <[email protected]>
  • Loading branch information
3 people authored Dec 17, 2024
1 parent d334769 commit 13a83e5
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .changeset/real-ads-like.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/file": minor
"gradio": minor
---

feat:Allow reordering files in gr.File
3 changes: 3 additions & 0 deletions gradio/components/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def __init__(
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
allow_reordering: bool = False,
):
"""
Parameters:
Expand All @@ -82,6 +83,7 @@ def __init__(
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
allow_reordering: if True, will allow users to reorder uploaded files by dragging and dropping.
"""
file_count_valid_types = ["single", "multiple", "directory"]
self.file_count = file_count
Expand Down Expand Up @@ -129,6 +131,7 @@ def __init__(
)
self.type = type
self.height = height
self.allow_reordering = allow_reordering

def _process_single_file(self, f: FileData) -> NamedString | bytes:
file_name = f.path
Expand Down
2 changes: 2 additions & 0 deletions gradio/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ def __init__(
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
allow_reordering: bool = False,
):
super().__init__(
value,
Expand All @@ -554,6 +555,7 @@ def __init__(
elem_classes=elem_classes,
render=render,
key=key,
allow_reordering=allow_reordering,
)


Expand Down
25 changes: 17 additions & 8 deletions js/file/File.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,23 @@
}}
/>
<Story
name="Multiple files, with height set to 150px"
name="Multiple files, with height set to 150px and reordering enabled"
args={{
value: Array(10).fill({
path: "cheetah.jpg",
orig_name: "cheetah.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
size: 10000
}),
height: 150
value: [
{
path: "cheetah.jpg",
orig_name: "cheetah.jpgz",
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
size: 10000
},
{
path: "cheetah.jpgs",
orig_name: "cheetah.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
size: 10000
}
],
height: 150,
allow_reordering: true
}}
/>
2 changes: 2 additions & 0 deletions js/file/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
export let file_count: "single" | "multiple" | "directory";
export let file_types: string[] = ["file"];
export let input_ready: boolean;
export let allow_reordering = false;
let uploading = false;
$: input_ready = !uploading;
Expand Down Expand Up @@ -103,6 +104,7 @@
selectable={_selectable}
{root}
{height}
{allow_reordering}
bind:uploading
max_file_size={gradio.max_file_size}
on:change={({ detail }) => {
Expand Down
105 changes: 104 additions & 1 deletion js/file/shared/FilePreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,64 @@
export let selectable = false;
export let height: number | undefined = undefined;
export let i18n: I18nFormatter;
export let allow_reordering = false;
let dragging_index: number | null = null;
let drop_target_index: number | null = null;
function handle_drag_start(event: DragEvent, index: number): void {
dragging_index = index;
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", index.toString());
}
}
function handle_drag_over(event: DragEvent, index: number): void {
event.preventDefault();
if (index === normalized_files.length - 1) {
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
const midY = rect.top + rect.height / 2;
drop_target_index =
event.clientY > midY ? normalized_files.length : index;
} else {
drop_target_index = index;
}
if (event.dataTransfer) {
event.dataTransfer.dropEffect = "move";
}
}
function handle_drag_end(event: DragEvent): void {
if (
!event.dataTransfer?.dropEffect ||
event.dataTransfer.dropEffect === "none"
) {
dragging_index = null;
drop_target_index = null;
}
}
function handle_drop(event: DragEvent, index: number): void {
event.preventDefault();
if (dragging_index === null || dragging_index === index) return;
const files = Array.isArray(value) ? [...value] : [value];
const [removed] = files.splice(dragging_index, 1);
files.splice(
drop_target_index === normalized_files.length
? normalized_files.length
: index,
0,
removed
);
const new_value = Array.isArray(value) ? files : files[0];
dispatch("change", new_value);
dragging_index = null;
drop_target_index = null;
}
function split_filename(filename: string): [string, string] {
const last_dot = filename.lastIndexOf(".");
Expand Down Expand Up @@ -70,15 +128,34 @@
>
<table class="file-preview">
<tbody>
{#each normalized_files as file, i (file)}
{#each normalized_files as file, i (file.url)}
<tr
class="file"
class:selectable
class:dragging={dragging_index === i}
class:drop-target={drop_target_index === i ||
(i === normalized_files.length - 1 &&
drop_target_index === normalized_files.length)}
data-drop-target={drop_target_index === normalized_files.length &&
i === normalized_files.length - 1
? "after"
: drop_target_index === i + 1
? "after"
: "before"}
draggable={allow_reordering && normalized_files.length > 1}
on:click={(event) => {
handle_row_click(event, i);
}}
on:dragstart={(event) => handle_drag_start(event, i)}
on:dragenter|preventDefault
on:dragover={(event) => handle_drag_over(event, i)}
on:drop={(event) => handle_drop(event, i)}
on:dragend={handle_drag_end}
>
<td class="filename" aria-label={file.orig_name}>
{#if allow_reordering && normalized_files.length > 1}
<span class="drag-handle">⋮⋮</span>
{/if}
<span class="stem">{file.filename_stem}</span>
<span class="ext">{file.filename_ext}</span>
</td>
Expand Down Expand Up @@ -204,4 +281,30 @@
tbody > tr:nth-child(odd) {
background: var(--table-odd-background-fill);
}
.drag-handle {
cursor: grab;
color: var(--body-text-color-subdued);
padding-right: var(--size-2);
user-select: none;
}
.dragging {
opacity: 0.5;
cursor: grabbing;
}
.drop-target {
border-top: 2px solid var(--color-accent);
}
tr:last-child.drop-target[data-drop-target="before"] {
border-top: 2px solid var(--color-accent);
border-bottom: none;
}
tr:last-child.drop-target[data-drop-target="after"] {
border-top: none;
border-bottom: 2px solid var(--color-accent);
}
</style>
2 changes: 2 additions & 0 deletions js/file/shared/FileUpload.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
export let upload: Client["upload"];
export let stream_handler: Client["stream"];
export let uploading = false;
export let allow_reordering = false;
async function handle_upload({
detail
Expand Down Expand Up @@ -97,6 +98,7 @@
{height}
on:change
on:delete
{allow_reordering}
/>
{:else}
<Upload
Expand Down
1 change: 1 addition & 0 deletions test/components/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def test_component_functions(self):
"key": None,
"height": None,
"type": "filepath",
"allow_reordering": False,
}
assert file_input.preprocess(None) is None
assert file_input.preprocess(x_file) is not None
Expand Down

0 comments on commit 13a83e5

Please sign in to comment.