diff --git a/.changeset/forty-hornets-share.md b/.changeset/forty-hornets-share.md new file mode 100644 index 0000000000..66f279c6a2 --- /dev/null +++ b/.changeset/forty-hornets-share.md @@ -0,0 +1,6 @@ +--- +"@gradio/gallery": minor +"gradio": minor +--- + +feat:Adds `change()` event to `gr.Gallery` diff --git a/gradio/components/gallery.py b/gradio/components/gallery.py index 7f19ee66eb..429202257e 100644 --- a/gradio/components/gallery.py +++ b/gradio/components/gallery.py @@ -15,6 +15,7 @@ from gradio import utils from gradio.components.base import IOComponent, _Keywords from gradio.deprecation import warn_deprecation, warn_style_method_deprecation from gradio.events import ( + Changeable, EventListenerMethod, Selectable, ) @@ -23,7 +24,7 @@ set_documentation_group("component") @document() -class Gallery(IOComponent, GallerySerializable, Selectable): +class Gallery(IOComponent, GallerySerializable, Changeable, Selectable): """ Used to display a list of images as a gallery that can be scrolled through. Preprocessing: this component does *not* accept input. diff --git a/js/gallery/Gallery.test.ts b/js/gallery/Gallery.test.ts new file mode 100644 index 0000000000..91659d14b0 --- /dev/null +++ b/js/gallery/Gallery.test.ts @@ -0,0 +1,72 @@ +import { test, describe, assert, afterEach, vi } from "vitest"; +import { cleanup, render } from "@gradio/tootils"; +import { setupi18n } from "../app/src/i18n"; + +import Gallery from "./static"; +import type { LoadingStatus } from "@gradio/statustracker"; + +const loading_status: LoadingStatus = { + eta: 0, + queue_position: 1, + queue_size: 1, + status: "complete" as LoadingStatus["status"], + scroll_to_output: false, + visible: true, + fn_index: 0, + show_progress: "full" +}; + +describe("Gallery", () => { + afterEach(() => { + cleanup(); + vi.useRealTimers(); + }); + beforeEach(() => { + setupi18n(); + }); + test("renders the image provided", async () => { + const { getByTestId } = await render(Gallery, { + show_label: true, + label: "Gallery", + loading_status: loading_status, + preview: true, + root: "", + root_url: "", + value: [ + "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg" + ] + }); + let item = getByTestId("detailed-image") as HTMLImageElement; + assert.equal( + item.src, + "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg" + ); + }); + + test("triggers the change event if and only if the images change", async () => { + const { listen, component } = await render(Gallery, { + show_label: true, + label: "Gallery", + loading_status: loading_status, + preview: true, + root: "", + root_url: "", + value: [ + "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg" + ] + }); + const change_event = listen("change"); + + await component.$set({ + value: [ + "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg" + ] + }); + assert.equal(change_event.callCount, 0); + + await component.$set({ + value: ["https://gradio-static-files.s3.us-west-2.amazonaws.com/lion.jpg"] + }); + assert.equal(change_event.callCount, 1); + }); +}); diff --git a/js/gallery/package.json b/js/gallery/package.json index 3e68d56589..3a805ec2d2 100644 --- a/js/gallery/package.json +++ b/js/gallery/package.json @@ -13,7 +13,8 @@ "@gradio/image": "workspace:^", "@gradio/statustracker": "workspace:^", "@gradio/upload": "workspace:^", - "@gradio/utils": "workspace:^" + "@gradio/utils": "workspace:^", + "dequal": "^2.0.2" }, "main_changeset": true, "exports": { diff --git a/js/gallery/static/Gallery.svelte b/js/gallery/static/Gallery.svelte index 12f3526411..68097ea208 100644 --- a/js/gallery/static/Gallery.svelte +++ b/js/gallery/static/Gallery.svelte @@ -2,7 +2,7 @@ import { BlockLabel, Empty, ShareButton } from "@gradio/atoms"; import { ModifyUpload } from "@gradio/upload"; import type { SelectData } from "@gradio/utils"; - + import { dequal } from "dequal"; import { createEventDispatcher } from "svelte"; import { tick } from "svelte"; import { _ } from "svelte-i18n"; @@ -30,6 +30,7 @@ export let show_download_button = false; const dispatch = createEventDispatcher<{ + change: undefined; select: SelectData; }>(); @@ -53,7 +54,7 @@ let selected_image = preview && value?.length ? 0 : null; let old_selected_image: number | null = selected_image; - $: if (prevValue !== value) { + $: if (!dequal(prevValue, value)) { // When value is falsy (clear button or first load), // preview determines the selected image if (was_reset) { @@ -69,6 +70,7 @@ ? selected_image : null; } + dispatch("change"); prevValue = value; } @@ -160,10 +162,12 @@ container_width / 2 + container_element.scrollLeft; - container_element?.scrollTo({ - left: pos < 0 ? 0 : pos, - behavior: "smooth" - }); + if (container_element && typeof container_element.scrollTo === "function") { + container_element.scrollTo({ + left: pos < 0 ? 0 : pos, + behavior: "smooth" + }); + } } let client_height = 0; diff --git a/js/gallery/static/StaticGallery.svelte b/js/gallery/static/StaticGallery.svelte index 0ede91593d..b9a9b8e5db 100644 --- a/js/gallery/static/StaticGallery.svelte +++ b/js/gallery/static/StaticGallery.svelte @@ -29,6 +29,7 @@ export let show_share_button = false; export let show_download_button = false; export let gradio: Gradio<{ + change: typeof value; select: SelectData; share: ShareData; error: string; @@ -49,6 +50,7 @@ > gradio.dispatch("change", value)} on:select={(e) => gradio.dispatch("select", e.detail)} on:share={(e) => gradio.dispatch("share", e.detail)} on:error={(e) => gradio.dispatch("error", e.detail)} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21734c9980..d49441e23b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -828,6 +828,9 @@ importers: '@gradio/utils': specifier: workspace:^ version: link:../utils + dequal: + specifier: ^2.0.2 + version: 2.0.2 js/group: {}