mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-12 12:40:29 +08:00
Fix directory-only glob for FileExplorer (#6689)
* Fix glob * add changeset * Fix test * lint * sort * Remove empty * Select directory + test * Workign fix * WOrkign fix * Directory click 😅 * add changeset * Fix test * Add dirs * Select directory + test * WOrkign fix * Directory click 😅 * WIP * add code * Lint * Use same color as file icon * Fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
parent
6a9151d5c9
commit
c9673cacd6
6
.changeset/happy-bags-rule.md
Normal file
6
.changeset/happy-bags-rule.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gradio/fileexplorer": patch
|
||||
"gradio": patch
|
||||
---
|
||||
|
||||
fix:Fix directory-only glob for FileExplorer
|
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: file_explorer_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": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('dir1')\n", "!wget -q -O dir1/bar.txt https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir1/bar.txt\n", "!wget -q -O dir1/foo.txt https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir1/foo.txt\n", "os.mkdir('dir2')\n", "!wget -q -O dir2/baz.png https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir2/baz.png\n", "!wget -q -O dir2/foo.png https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir2/foo.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from pathlib import Path\n", "\n", "base_root = Path(__file__).parent.resolve()\n", "\n", "with gr.Blocks() as demo:\n", " dd = gr.Dropdown(\n", " label=\"Select File Explorer Root\",\n", " value=str(base_root / \"dir1\"),\n", " choices=[str(base_root / \"dir1\"), str(base_root / \"dir2\")],\n", " )\n", " fe = gr.FileExplorer(root=str(base_root / \"dir1\"), interactive=True)\n", " dd.select(lambda s: gr.FileExplorer(root=s), inputs=[dd], outputs=[fe])\n", "\n", " with gr.Row():\n", " a = gr.Textbox(elem_id=\"input-box\")\n", " a.change(lambda x: x, inputs=[a])\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: file_explorer_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": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('dir1')\n", "!wget -q -O dir1/bar.txt https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir1/bar.txt\n", "!wget -q -O dir1/foo.txt https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir1/foo.txt\n", "os.mkdir('dir2')\n", "!wget -q -O dir2/baz.png https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir2/baz.png\n", "!wget -q -O dir2/foo.png https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir2/foo.png\n", "os.mkdir('dir3')\n", "!wget -q -O dir3/dir3_foo.txt https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir3/dir3_foo.txt\n", "!wget -q -O dir3/dir4 https://github.com/gradio-app/gradio/raw/main/demo/file_explorer_component_events/dir3/dir4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from pathlib import Path\n", "\n", "base_root = Path(__file__).parent.resolve()\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " dd = gr.Dropdown(label=\"Select File Explorer Root\",\n", " value=str(base_root / \"dir1\"),\n", " choices=[str(base_root / \"dir1\"), str(base_root / \"dir2\"),\n", " str(base_root / \"dir3\")])\n", " with gr.Group():\n", " dir_only_glob = gr.Checkbox(label=\"Show only directories\", value=False)\n", " ignore_dir_in_glob = gr.Checkbox(label=\"Ignore directories in glob\", value=False)\n", "\n", " fe = gr.FileExplorer(root=str(base_root / \"dir1\"),\n", " glob=\"**/*\", interactive=True)\n", " textbox = gr.Textbox(label=\"Selected Directory\")\n", " run = gr.Button(\"Run\")\n", " \n", " dir_only_glob.select(lambda s: gr.FileExplorer(glob=\"**/\" if s else \"**/*.*\",\n", " file_count=\"multiple\",\n", " root=str(base_root / \"dir3\")) ,\n", " inputs=[dir_only_glob], outputs=[fe])\n", " ignore_dir_in_glob.select(lambda s: gr.FileExplorer(glob=\"**/*\",\n", " ignore_glob=\"**/\",\n", " root=str(base_root / \"dir3\")),\n", " inputs=[ignore_dir_in_glob], outputs=[fe]) \n", "\n", " dd.select(lambda s: gr.FileExplorer(root=s), inputs=[dd], outputs=[fe])\n", " run.click(lambda s: \",\".join(s) if isinstance(s, list) else s, inputs=[fe], outputs=[textbox])\n", "\n", " with gr.Row():\n", " a = gr.Textbox(elem_id=\"input-box\")\n", " a.change(lambda x: x, inputs=[a])\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -4,13 +4,31 @@ from pathlib import Path
|
||||
base_root = Path(__file__).parent.resolve()
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
dd = gr.Dropdown(
|
||||
label="Select File Explorer Root",
|
||||
value=str(base_root / "dir1"),
|
||||
choices=[str(base_root / "dir1"), str(base_root / "dir2")],
|
||||
)
|
||||
fe = gr.FileExplorer(root=str(base_root / "dir1"), interactive=True)
|
||||
with gr.Row():
|
||||
dd = gr.Dropdown(label="Select File Explorer Root",
|
||||
value=str(base_root / "dir1"),
|
||||
choices=[str(base_root / "dir1"), str(base_root / "dir2"),
|
||||
str(base_root / "dir3")])
|
||||
with gr.Group():
|
||||
dir_only_glob = gr.Checkbox(label="Show only directories", value=False)
|
||||
ignore_dir_in_glob = gr.Checkbox(label="Ignore directories in glob", value=False)
|
||||
|
||||
fe = gr.FileExplorer(root=str(base_root / "dir1"),
|
||||
glob="**/*", interactive=True)
|
||||
textbox = gr.Textbox(label="Selected Directory")
|
||||
run = gr.Button("Run")
|
||||
|
||||
dir_only_glob.select(lambda s: gr.FileExplorer(glob="**/" if s else "**/*.*",
|
||||
file_count="multiple",
|
||||
root=str(base_root / "dir3")) ,
|
||||
inputs=[dir_only_glob], outputs=[fe])
|
||||
ignore_dir_in_glob.select(lambda s: gr.FileExplorer(glob="**/*",
|
||||
ignore_glob="**/",
|
||||
root=str(base_root / "dir3")),
|
||||
inputs=[ignore_dir_in_glob], outputs=[fe])
|
||||
|
||||
dd.select(lambda s: gr.FileExplorer(root=s), inputs=[dd], outputs=[fe])
|
||||
run.click(lambda s: ",".join(s) if isinstance(s, list) else s, inputs=[fe], outputs=[textbox])
|
||||
|
||||
with gr.Row():
|
||||
a = gr.Textbox(elem_id="input-box")
|
||||
|
@ -116,8 +116,11 @@ class FileExplorer(Component):
|
||||
return None
|
||||
else:
|
||||
return self._safe_join(payload.root[0])
|
||||
|
||||
return [self._safe_join(file) for file in (payload.root)]
|
||||
files = []
|
||||
for file in payload.root:
|
||||
file_ = self._safe_join(file)
|
||||
files.append(file_)
|
||||
return files
|
||||
|
||||
def _strip_root(self, path):
|
||||
if path.startswith(self.root):
|
||||
@ -129,10 +132,11 @@ class FileExplorer(Component):
|
||||
return None
|
||||
|
||||
files = [value] if isinstance(value, str) else value
|
||||
root = []
|
||||
for file in files:
|
||||
root.append(self._strip_root(file).split(os.path.sep))
|
||||
|
||||
return FileExplorerData(
|
||||
root=[self._strip_root(file).split(os.path.sep) for file in files]
|
||||
)
|
||||
return FileExplorerData(root=root)
|
||||
|
||||
@server
|
||||
def ls(self, value=None) -> list[dict[str, str]] | None:
|
||||
@ -190,17 +194,26 @@ class FileExplorer(Component):
|
||||
_tree.append({"path": parts[i], "type": type, "children": []})
|
||||
_tree = _tree[-1]["children"]
|
||||
|
||||
files = []
|
||||
files: list[Path] = []
|
||||
for result in expand_braces(self.glob):
|
||||
files += list(Path(self.root).resolve().glob(result))
|
||||
|
||||
files = [f for f in files if f != Path(self.root).resolve()]
|
||||
|
||||
ignore_files = []
|
||||
if self.ignore_glob:
|
||||
for result in expand_braces(self.ignore_glob):
|
||||
ignore_files += list(Path(self.root).resolve().glob(result))
|
||||
files = list(set(files) - set(ignore_files))
|
||||
|
||||
tree = make_tree([str(f.relative_to(self.root)) for f in files])
|
||||
files_with_sep = []
|
||||
for f in files:
|
||||
file = str(f.relative_to(self.root))
|
||||
if f.is_dir():
|
||||
file += os.path.sep
|
||||
files_with_sep.append(file)
|
||||
|
||||
tree = make_tree(files_with_sep)
|
||||
return tree
|
||||
|
||||
def _safe_join(self, folders):
|
||||
|
@ -37,3 +37,120 @@ test("File Explorer is interactive and re-runs the server_fn when root is update
|
||||
page.locator("span").filter({ hasText: "foo.png" }).getByRole("checkbox")
|
||||
).toBeChecked();
|
||||
});
|
||||
|
||||
test("File Explorer correctly displays both directories and files. Directories included in value.", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.getByLabel("Select File Explorer Root").click();
|
||||
await page.getByLabel(new RegExp("/dir3$"), { exact: true }).first().click();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir4" })
|
||||
.getByLabel("expand directory")
|
||||
.click();
|
||||
|
||||
await page
|
||||
.locator("li")
|
||||
.filter({ hasText: "dir4 dir5 dir7 . dir_4_foo.txt" })
|
||||
.getByRole("checkbox")
|
||||
.nth(3)
|
||||
.check();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir_4_foo.txt" })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir3_foo.txt" })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
|
||||
await page.getByRole("button", { name: "Run" }).click();
|
||||
const directory_paths_displayed = async () => {
|
||||
const value = await page.getByLabel("Selected Directory").inputValue();
|
||||
const files = value.split(",");
|
||||
expect(files.some((f) => f.endsWith("dir4"))).toBeTruthy();
|
||||
expect(files.some((f) => f.endsWith("dir_4_foo.txt"))).toBeTruthy();
|
||||
expect(files.some((f) => f.endsWith("dir3_foo.txt"))).toBeTruthy();
|
||||
};
|
||||
await expect(directory_paths_displayed).toPass();
|
||||
});
|
||||
|
||||
test("File Explorer selects all children when top level directory is selected.", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.getByLabel("Select File Explorer Root").click();
|
||||
await page.getByLabel(new RegExp("/dir3$"), { exact: true }).first().click();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir4" })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
|
||||
await page.getByRole("button", { name: "Run" }).click();
|
||||
|
||||
const directory_paths_displayed = async () => {
|
||||
const value = await page.getByLabel("Selected Directory").inputValue();
|
||||
const files_and_dirs = value.split(",");
|
||||
expect(files_and_dirs.length).toBe(6);
|
||||
};
|
||||
await expect(directory_paths_displayed).toPass();
|
||||
});
|
||||
|
||||
test("File Explorer correctly displays only directories and properly adds it to the value", async ({
|
||||
page
|
||||
}) => {
|
||||
const check = page.getByRole("checkbox", {
|
||||
name: "Show only directories",
|
||||
exact: true
|
||||
});
|
||||
await check.click();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir4" })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
await page.getByRole("button", { name: "Run" }).click();
|
||||
|
||||
const directory_paths_displayed = async () => {
|
||||
const value = await page.getByLabel("Selected Directory").inputValue();
|
||||
const dirs = value.split(",");
|
||||
expect(dirs.some((f) => f.endsWith("dir4"))).toBeTruthy();
|
||||
expect(dirs.some((f) => f.endsWith("dir5"))).toBeTruthy();
|
||||
expect(dirs.some((f) => f.endsWith("dir7"))).toBeTruthy();
|
||||
};
|
||||
await expect(directory_paths_displayed).toPass();
|
||||
});
|
||||
|
||||
test("File Explorer correctly excludes directories when ignore_glob is '**/'.", async ({
|
||||
page
|
||||
}) => {
|
||||
const check = page.getByRole("checkbox", {
|
||||
name: "Ignore directories in glob",
|
||||
exact: true
|
||||
});
|
||||
await check.click();
|
||||
|
||||
await page
|
||||
.locator("span")
|
||||
.filter({ hasText: "dir4" })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
await page.getByRole("button", { name: "Run" }).click();
|
||||
|
||||
const only_files_displayed = async () => {
|
||||
const value = await page.getByLabel("Selected Directory").inputValue();
|
||||
const files = value.split(",");
|
||||
expect(files.length).toBe(3);
|
||||
expect(files.some((f) => f.endsWith("dir_4_foo.txt"))).toBeTruthy();
|
||||
expect(files.some((f) => f.endsWith("dir5_foo.txt"))).toBeTruthy();
|
||||
expect(files.some((f) => f.endsWith("dir7_foo.txt"))).toBeTruthy();
|
||||
};
|
||||
await expect(only_files_displayed).toPass();
|
||||
});
|
||||
|
124
js/fileexplorer/icons/light-folder.svg
Normal file
124
js/fileexplorer/icons/light-folder.svg
Normal file
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg7"
|
||||
sodipodi:docname="light-folder-new.svg"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="7.375"
|
||||
inkscape:cx="15.932203"
|
||||
inkscape:cy="16"
|
||||
inkscape:window-width="1312"
|
||||
inkscape:window-height="529"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg7" />
|
||||
<defs
|
||||
id="defs6">
|
||||
<clipPath
|
||||
id="clipPath1">
|
||||
<path
|
||||
d="m69.63 12.145h-.052c-22.727-.292-46.47 4.077-46.709 4.122-2.424.451-4.946 2.974-5.397 5.397-.044.237-4.414 23.983-4.122 46.71-.292 22.777 4.078 46.523 4.122 46.761.451 2.423 2.974 4.945 5.398 5.398.237.044 23.982 4.413 46.709 4.121 22.779.292 46.524-4.077 46.761-4.121 2.423-.452 4.946-2.976 5.398-5.399.044-.236 4.413-23.981 4.121-46.709.292-22.777-4.077-46.523-4.121-46.761-.453-2.423-2.976-4.946-5.398-5.397-.238-.045-23.984-4.414-46.71-4.122"
|
||||
id="path1" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="352.98"
|
||||
x2="-601.15"
|
||||
y1="663.95"
|
||||
x1="-591.02"
|
||||
id="2">
|
||||
<stop
|
||||
stop-color="#a0a0a0"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#aaa"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="354.29"
|
||||
x2="-704.05"
|
||||
y1="647.77"
|
||||
x1="-701.19"
|
||||
id="1">
|
||||
<stop
|
||||
stop-color="#acabab"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#d4d4d4"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="0"
|
||||
x1="59.12"
|
||||
y1="-19.888"
|
||||
x2="59.15"
|
||||
y2="-37.783"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(4.17478 0 0 4.16765-1069.7 447.73)">
|
||||
<stop
|
||||
stop-color="#a0a0a0"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#bdbdbd"
|
||||
id="stop6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
transform="matrix(.07089 0 0 .07017 23.295-40.67)"
|
||||
fill="#60aae5"
|
||||
id="g7"
|
||||
style="fill:#888888;fill-opacity:1">
|
||||
<path
|
||||
transform="matrix(.7872 0 0 .79524 415.34 430.11)"
|
||||
d="m-884.1 294.78c-4.626 0-8.349 3.718-8.349 8.335v161.41l468.19 1v-121.2c0-4.618-3.724-8.335-8.35-8.335h-272.65c-8.51.751-9.607-.377-13.812-5.981-5.964-7.968-14.969-21.443-20.84-29.21-4.712-6.805-5.477-6.02-13.292-6.02z"
|
||||
fill="url(#0)"
|
||||
color="#000"
|
||||
id="path6"
|
||||
style="fill:#888888;fill-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(.7872 0 0 .79524 415.34 430.11)"
|
||||
y="356.85"
|
||||
x="-890.28"
|
||||
height="295.13"
|
||||
width="463.85"
|
||||
fill="url(#1)"
|
||||
stroke="url(#1)"
|
||||
stroke-width="2.378"
|
||||
rx="9.63"
|
||||
id="rect6"
|
||||
style="fill:#888888;fill-opacity:1" />
|
||||
<rect
|
||||
width="463.85"
|
||||
height="295.13"
|
||||
x="-890.28"
|
||||
y="356.85"
|
||||
transform="matrix(.7872 0 0 .79524 415.34 430.11)"
|
||||
fill="none"
|
||||
stroke="url(#2)"
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="5.376"
|
||||
rx="9.63"
|
||||
id="rect7"
|
||||
style="fill:#888888;fill-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -2,7 +2,6 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let value: boolean;
|
||||
export let disabled: boolean;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: boolean }>();
|
||||
</script>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Node } from "./utils";
|
||||
import { dequal } from "dequal";
|
||||
import FileTree from "./FileTree.svelte";
|
||||
import { make_fs_store } from "./utils";
|
||||
|
@ -5,6 +5,7 @@
|
||||
import Arrow from "./ArrowIcon.svelte";
|
||||
import Checkbox from "./Checkbox.svelte";
|
||||
import FileIcon from "../icons/light-file.svg";
|
||||
import FolderIcon from "../icons/light-folder.svg";
|
||||
|
||||
export let interactive: boolean;
|
||||
export let tree: Node[] = [];
|
||||
@ -51,12 +52,16 @@
|
||||
(tree[i].children_visible = !tree[i].children_visible)}
|
||||
><Arrow /></span
|
||||
>
|
||||
{:else if path === ""}
|
||||
<span class="file-icon">
|
||||
<img src={FolderIcon} alt="folder icon" />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="file-icon">
|
||||
<img src={FileIcon} alt="file icon" />
|
||||
</span>
|
||||
{/if}
|
||||
{path}
|
||||
{path ? path : "."}
|
||||
</span>
|
||||
{#if children && children_visible}
|
||||
<svelte:self
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { writable, type Readable } from "svelte/store";
|
||||
import { dequal } from "dequal";
|
||||
|
||||
export interface Node {
|
||||
type: "file" | "folder";
|
||||
path: string;
|
||||
@ -75,16 +76,18 @@ export const make_fs_store = (): FSStore => {
|
||||
ensure_visible(_node);
|
||||
const nodes = check_node_and_children(_node.children, true, [_node]);
|
||||
check_parent(_node);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const path = get_full_path(node);
|
||||
if (seen_nodes.has(path.join("/"))) {
|
||||
return;
|
||||
}
|
||||
const normalized_path = path.join("/");
|
||||
// let normalized_path = path.join("/");
|
||||
// normalized_path = normalized_path.endsWith("/") ? normalized_path.slice(0, -1) : normalized_path;
|
||||
if (node.type === "file") {
|
||||
if (seen_nodes.has(normalized_path)) {
|
||||
return;
|
||||
}
|
||||
new_checked_paths.push(path);
|
||||
seen_nodes.add(normalized_path);
|
||||
}
|
||||
seen_nodes.add(path.join("/"));
|
||||
});
|
||||
}
|
||||
|
||||
@ -119,20 +122,19 @@ export const make_fs_store = (): FSStore => {
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const _path = get_full_path(nodes[i]);
|
||||
if (!checked) {
|
||||
if (!nodes[i].checked) {
|
||||
new_checked_paths.delete(_path.join("/"));
|
||||
} else if (checked) {
|
||||
} else if (nodes[i].checked) {
|
||||
if (file_count === "single") {
|
||||
new_checked_paths = new Map();
|
||||
}
|
||||
|
||||
if (nodes[i].type === "file") {
|
||||
new_checked_paths.set(_path.join("/"), _path);
|
||||
}
|
||||
new_checked_paths.set(_path.join("/"), _path);
|
||||
}
|
||||
}
|
||||
|
||||
check_parent(_node);
|
||||
|
||||
set(root.children!);
|
||||
old_checked_paths = Array.from(new_checked_paths).map((v) => v[1]);
|
||||
return old_checked_paths;
|
||||
@ -261,7 +263,6 @@ function check_parent(node: Node | null | undefined): void {
|
||||
nodes_checked.push(_node.checked);
|
||||
_node = _node.previous;
|
||||
}
|
||||
|
||||
if (nodes_checked.every((v) => v === true)) {
|
||||
node.parent!.checked = true;
|
||||
check_parent(node?.parent);
|
||||
|
@ -2734,6 +2734,67 @@ class TestFileExplorer:
|
||||
preprocessed_data = file_explorer.preprocess(input_data)
|
||||
assert preprocessed_data == []
|
||||
|
||||
def test_file_explorer_dir_only_glob(self, tmpdir):
|
||||
tmpdir.mkdir("foo")
|
||||
tmpdir.mkdir("bar")
|
||||
tmpdir.mkdir("baz")
|
||||
(Path(tmpdir) / "baz" / "qux").mkdir()
|
||||
(Path(tmpdir) / "foo" / "abc").mkdir()
|
||||
(Path(tmpdir) / "foo" / "abc" / "def").mkdir()
|
||||
(Path(tmpdir) / "foo" / "abc" / "def" / "file.txt").touch()
|
||||
|
||||
file_explorer = gr.FileExplorer(glob="**/", root=Path(tmpdir))
|
||||
tree = file_explorer.ls()
|
||||
|
||||
def sort_answer(answer):
|
||||
answer = sorted(answer, key=lambda x: x["path"])
|
||||
for item in answer:
|
||||
if item["children"]:
|
||||
item["children"] = sort_answer(item["children"])
|
||||
return answer
|
||||
|
||||
answer = [
|
||||
{
|
||||
"path": "bar",
|
||||
"type": "folder",
|
||||
"children": [{"path": "", "type": "file", "children": None}],
|
||||
},
|
||||
{
|
||||
"path": "baz",
|
||||
"type": "folder",
|
||||
"children": [
|
||||
{"path": "", "type": "file", "children": None},
|
||||
{
|
||||
"path": "qux",
|
||||
"type": "folder",
|
||||
"children": [{"path": "", "type": "file", "children": None}],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"path": "foo",
|
||||
"type": "folder",
|
||||
"children": [
|
||||
{"path": "", "type": "file", "children": None},
|
||||
{
|
||||
"path": "abc",
|
||||
"type": "folder",
|
||||
"children": [
|
||||
{"path": "", "type": "file", "children": None},
|
||||
{
|
||||
"path": "def",
|
||||
"type": "folder",
|
||||
"children": [
|
||||
{"path": "", "type": "file", "children": None}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
assert sort_answer(tree) == sort_answer(answer)
|
||||
|
||||
|
||||
def test_component_class_ids():
|
||||
button_id = gr.Button().component_class_id
|
||||
|
Loading…
x
Reference in New Issue
Block a user