Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gradio/components/file_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ def __init__(
raise ValueError(
f"Invalid value for parameter `file_count`: {file_count}. Please choose from one of: {valid_file_count}"
)
if file_count == "single":
if isinstance(value, Sequence) and not isinstance(value, str) and len(value) > 1:
raise ValueError(
f'Invalid argument for paramter `value`: {value}. If `file_count = "single"`, you can not pass multiple files.'
)
self.file_count = file_count
self.height = height
self.max_height = max_height
Expand Down
7 changes: 5 additions & 2 deletions js/fileexplorer/shared/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
import { createEventDispatcher } from "svelte";
export let value: boolean;
export let disabled: boolean;
const dispatch = createEventDispatcher<{ change: boolean }>();
const dispatch = createEventDispatcher<{
change: { checked: boolean; shiftKey: boolean };
}>();
</script>

<input
bind:checked={value}
on:input={() => dispatch("change", !value)}
on:click={(e) =>
dispatch("change", { checked: !value, shiftKey: e.shiftKey })}
type="checkbox"
{disabled}
class:disabled={disabled && !value}
Expand Down
25 changes: 2 additions & 23 deletions js/fileexplorer/shared/DirectoryExplorer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
export let file_count: "single" | "multiple" = "multiple";
export let value: string[][] = [];
export let ls_fn: (path: string[]) => Promise<FileNode[]>;
let selected_folders: string[][] = [];

const paths_equal = (path: string[], path_2: string[]): boolean => {
return path.join("/") === path_2.join("/");
Expand All @@ -15,47 +14,27 @@
const path_in_set = (path: string[], set: string[][]): boolean => {
return set.some((x) => paths_equal(x, path));
};

const path_inside = (path: string[], path_2: string[]): boolean => {
return path.join("/").startsWith(path_2.join("/"));
};
</script>

<div class="file-wrap">
<FileTree
path={[]}
selected_files={value}
{selected_folders}
selected={value}
{interactive}
{ls_fn}
{file_count}
valid_for_selection={false}
on:check={(e) => {
const { path, checked, type } = e.detail;
if (checked) {
if (file_count === "single") {
value = [path];
} else if (type === "folder") {
if (!path_in_set(path, selected_folders)) {
selected_folders = [...selected_folders, path];
}
} else {
if (!path_in_set(path, value)) {
value = [...value, path];
}
}
} else {
selected_folders = selected_folders.filter(
(folder) => !path_inside(path, folder)
); // deselect all parent folders
if (type === "folder") {
selected_folders = selected_folders.filter(
(folder) => !path_inside(folder, path)
); // deselect all children folders
value = value.filter((file) => !path_inside(file, path)); // deselect all children files
} else {
value = value.filter((x) => !paths_equal(x, path));
}
value = value.filter((x) => !paths_equal(x, path));
}
}}
/>
Expand Down
101 changes: 38 additions & 63 deletions js/fileexplorer/shared/FileTree.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@
import FolderIcon from "../icons/light-folder.svg";

export let path: string[] = [];
export let selected_files: string[][] = [];
export let selected_folders: string[][] = [];
export let is_selected_entirely = false;
export let selected: string[][] = [];
export let interactive: boolean;
export let ls_fn: (path: string[]) => Promise<FileNode[]>;
export let file_count: "single" | "multiple" = "multiple";
export let valid_for_selection: boolean;

let content: FileNode[] = [];
let opened_folders: number[] = [];
Expand All @@ -27,37 +24,10 @@
}
};

const open_folder = (i: number): void => {
if (!opened_folders.includes(i)) {
opened_folders = [...opened_folders, i];
}
};

(async () => {
content = await ls_fn(path);
if (valid_for_selection) {
content = [{ name: ".", type: "file" }, ...content];
}
opened_folders = content
.map((x, i) =>
x.type === "folder" &&
(is_selected_entirely || selected_files.some((y) => y[0] === x.name))
? i
: null
)
.filter((x): x is number => x !== null);
})();

$: if (is_selected_entirely) {
content.forEach((x) => {
dispatch("check", {
path: [...path, x.name],
checked: true,
type: x.type
});
});
}

const dispatch = createEventDispatcher<{
check: { path: string[]; checked: boolean; type: "file" | "folder" };
}>();
Expand All @@ -67,27 +37,45 @@
{#each content as { type, name, valid }, i}
<li>
<span class="wrap">
{#if type === "folder" && file_count === "single"}
<span class="no-checkbox" aria-hidden="true"></span>
{:else}
<Checkbox
disabled={!interactive}
value={(type === "file" ? selected_files : selected_folders).some(
(x) => x[0] === name && x.length === 1
)}
on:change={(e) => {
let checked = e.detail;
<Checkbox
disabled={!interactive}
value={selected.some((x) => x[0] === name && x.length === 1)}
on:change={(e) => {
let { checked, shiftKey } = e.detail;
if (file_count === "single" || !shiftKey || selected.length === 0) {
dispatch("check", {
path: [...path, name],
checked,
type
});
if (type === "folder" && checked) {
open_folder(i);
} else {
let content_names = content.map((item) => item.name);
let last_selected_name = selected[selected.length - 1][0];
let last_index = content_names.indexOf(last_selected_name);
if (last_index === -1) {
dispatch("check", {
path: [...path, name],
checked,
type
});
} else {
let new_index = content_names.indexOf(name);
if (new_index < last_index) {
[last_index, new_index] = [new_index, last_index];
}
content
.slice(last_index, new_index + 1)
.forEach((item, index) => {
dispatch("check", {
path: [...path, item.name],
checked,
type: item.type
});
});
}
}}
/>
{/if}
}
}}
/>

{#if type === "folder"}
<span
Expand All @@ -103,29 +91,21 @@
}
}}><Arrow /></span
>
{:else}
<span class="file-icon">
<img src={name === "." ? FolderIcon : FileIcon} alt="file icon" />
</span>
{/if}
<span class="file-icon">
<img src={type === "file" ? FileIcon : FolderIcon} alt="file icon" />
</span>
{name}
</span>
{#if type === "folder" && opened_folders.includes(i)}
<svelte:self
path={[...path, name]}
selected_files={selected_files
.filter((x) => x[0] === name)
.map((x) => x.slice(1))}
selected_folders={selected_folders
selected={selected
.filter((x) => x[0] === name)
.map((x) => x.slice(1))}
is_selected_entirely={selected_folders.some(
(x) => x[0] === name && x.length === 1
)}
{interactive}
{ls_fn}
{file_count}
valid_for_selection={valid}
on:check
/>
{/if}
Expand Down Expand Up @@ -183,11 +163,6 @@
color: var(--color-accent);
}

.no-checkbox {
width: 18px;
height: 18px;
}

.hidden :global(> *) {
transform: rotate(0);
color: var(--body-text-color-subdued);
Expand Down