Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve source selection UX #6766

Merged
merged 35 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6615151
Add new source option styling for pasting from clipboard
hannahblair Dec 12, 2023
08a0416
prevent device selection cut off
hannahblair Dec 12, 2023
f99fa3a
Check for dupe sources in source selection
hannahblair Dec 13, 2023
5ec31b8
tweaks
hannahblair Dec 13, 2023
60dc45e
tweak
hannahblair Dec 13, 2023
9756b35
add image interaction test
hannahblair Dec 13, 2023
d6c1e93
more tests
hannahblair Dec 13, 2023
838a16f
improve light/dark mode color contrast
hannahblair Dec 13, 2023
352f714
Merge branch 'main' into improve-source-selection
hannahblair Dec 13, 2023
9feb0cc
add changeset
gradio-pr-bot Dec 13, 2023
dc1526f
remove unused prop
hannahblair Dec 13, 2023
8681c41
Merge branch 'improve-source-selection' of github.com:gradio-app/grad…
hannahblair Dec 13, 2023
b0be967
add no device found placeholder
hannahblair Dec 13, 2023
94f2d51
style tweak
hannahblair Dec 13, 2023
8bbc7ed
Merge branch 'main' into improve-source-selection
hannahblair Dec 14, 2023
5d24f01
allow pasting on click + add e2e test
hannahblair Dec 14, 2023
8e7426d
Merge branch 'improve-source-selection' of github.com:gradio-app/grad…
hannahblair Dec 14, 2023
d5dec81
Merge branch 'main' into improve-source-selection
hannahblair Dec 14, 2023
e44d909
Merge branch 'main' into improve-source-selection
hannahblair Dec 15, 2023
c9ed31a
Merge branch 'improve-source-selection' of github.com:gradio-app/grad…
hannahblair Dec 15, 2023
bf95184
Merge branch 'main' into improve-source-selection
hannahblair Dec 18, 2023
0397497
fix e2e tests
hannahblair Dec 18, 2023
3cdad9e
Merge branch 'improve-source-selection' of github.com:gradio-app/grad…
hannahblair Dec 18, 2023
a823d4f
formatting
hannahblair Dec 18, 2023
216b9e9
Merge branch 'main' into improve-source-selection
hannahblair Dec 18, 2023
c6689d0
add timeout to e2e test
hannahblair Dec 18, 2023
b8c695a
Merge branch 'improve-source-selection' of github.com:gradio-app/grad…
hannahblair Dec 18, 2023
81ca4a4
tweak
hannahblair Dec 19, 2023
e64cf2b
tweak test
hannahblair Dec 19, 2023
f28633e
change `getByLabel` to `getByText`
hannahblair Dec 19, 2023
87d3b61
value tweak
hannahblair Dec 19, 2023
7f75408
logic tweak
hannahblair Dec 19, 2023
59a64da
test
hannahblair Dec 19, 2023
b6d8f54
formatting
hannahblair Dec 19, 2023
1a98513
Merge branch 'main' into improve-source-selection
hannahblair Dec 19, 2023
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
11 changes: 11 additions & 0 deletions .changeset/strong-files-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@gradio/app": patch
"@gradio/atoms": patch
"@gradio/audio": patch
"@gradio/image": patch
"@gradio/upload": patch
"@gradio/video": patch
"gradio": patch
---

fix:Improve source selection UX
6 changes: 4 additions & 2 deletions js/app/src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"remove": "Remove",
"share": "Share",
"submit": "Submit",
"undo": "Undo"
"undo": "Undo",
"no_devices": "No devices found"
},
"dataframe": {
"incorrect_format": "Incorrect format, only CSV and TSV files are supported",
Expand Down Expand Up @@ -110,6 +111,7 @@
"drop_csv": "Drop CSV Here",
"drop_file": "Drop File Here",
"drop_image": "Drop Image Here",
"drop_video": "Drop Video Here"
"drop_video": "Drop Video Here",
"paste_clipboard": "Paste from Clipboard"
}
}
28 changes: 27 additions & 1 deletion js/app/test/image_component_events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,33 @@ test("Image copy from clipboard dispatches upload event.", async ({ page }) => {
navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
});

await page.getByLabel("clipboard-image-toolbar-btn").click();
await page.getByLabel("Paste from clipboard").click();
await Promise.all([
page.waitForResponse(
(resp) => resp.url().includes("/clipboard.png") && resp.status() === 200
)
]);
await expect(page.getByLabel("# Change Events").first()).toHaveValue("1");
await expect(page.getByLabel("# Upload Events")).toHaveValue("1");
});

test("Image paste to clipboard via the Upload component works", async ({
page
}) => {
await page.evaluate(async () => {
navigator.clipboard.writeText("123");
});

await page.getByLabel("Paste from clipboard").click();
await page.evaluate(async () => {
const blob = await (
await fetch(
`https://gradio-builds.s3.amazonaws.com/assets/PDFDisplay.png`
)
).blob();
navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
});

await page.getByText("Paste from clipboard").click();
await expect(page.getByLabel("# Upload Events")).toHaveValue("1");
});
14 changes: 5 additions & 9 deletions js/app/test/video_component_events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { test, expect, drag_and_drop_file } from "@gradio/tootils";
test("Video click-to-upload uploads video successfuly. Clear, play, and pause buttons dispatch events correctly. Downloading the file works and has the correct name.", async ({
page
}) => {
await page
.getByRole("button", { name: "Drop Video Here - or - Click to Upload" })
.click();
await page.getByRole("button", { name: "Upload file" }).click();
const uploader = await page.locator("input[type=file]");
await uploader.setInputFiles(["./test/files/file_test.ogg"]);

Expand All @@ -14,9 +12,7 @@ test("Video click-to-upload uploads video successfuly. Clear, play, and pause bu

await page.getByLabel("Clear").click();
await expect(page.getByLabel("# Change Events")).toHaveValue("2");
await page
.getByRole("button", { name: "Drop Video Here - or - Click to Upload" })
.click();
await page.getByRole("button", { name: "Upload file" }).click();

await uploader.setInputFiles(["./test/files/file_test.ogg"]);

Expand All @@ -30,9 +26,7 @@ test("Video click-to-upload uploads video successfuly. Clear, play, and pause bu
});

test("Video play, pause events work correctly.", async ({ page }) => {
await page
.getByRole("button", { name: "Drop Video Here - or - Click to Upload" })
.click();
await page.getByLabel("Upload file").click();
const uploader = await page.locator("input[type=file]");
await uploader.setInputFiles(["./test/files/file_test.ogg"]);

Expand All @@ -48,6 +42,7 @@ test("Video play, pause events work correctly.", async ({ page }) => {
test("Video drag-and-drop uploads a file to the server correctly.", async ({
page
}) => {
await page.getByLabel("Upload file").click();
await drag_and_drop_file(
page,
"input[type=file]",
Expand All @@ -62,6 +57,7 @@ test("Video drag-and-drop uploads a file to the server correctly.", async ({
test("Video drag-and-drop displays a warning when the file is of the wrong mime type.", async ({
page
}) => {
await page.getByLabel("Upload file").click();
await drag_and_drop_file(
page,
"input[type=file]",
Expand Down
53 changes: 34 additions & 19 deletions js/atoms/src/SelectSource.svelte
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
<script lang="ts">
import { Microphone, Upload, Video } from "@gradio/icons";
import { Microphone, Upload, Webcam, ImagePaste } from "@gradio/icons";

export let sources: string[];
export let active_source: string;
type source_types = "upload" | "microphone" | "webcam" | "clipboard" | null;

export let sources: Partial<source_types>[];
export let active_source: Partial<source_types>;
export let handle_clear: () => void = () => {};
export let handle_select: (
source_type: Partial<source_types>
) => void = () => {};

$: unique_sources = [...new Set(sources)];

async function handle_select_source(
source: Partial<source_types>
): Promise<void> {
handle_clear();
active_source = source;
handle_select(source);
}
</script>

{#if sources.length > 1}
{#if unique_sources.length > 1}
<span class="source-selection" data-testid="source-select">
{#if sources.includes("upload")}
<button
class="icon"
class:selected={active_source === "upload"}
class:selected={active_source === "upload" || !active_source}
aria-label="Upload file"
on:click={() => {
handle_clear();
active_source = "upload";
}}><Upload /></button
on:click={() => handle_select_source("upload")}><Upload /></button
>
{/if}

Expand All @@ -25,22 +37,26 @@
class="icon"
class:selected={active_source === "microphone"}
aria-label="Record audio"
on:click={() => {
handle_clear();
active_source = "microphone";
}}><Microphone /></button
on:click={() => handle_select_source("microphone")}
><Microphone /></button
>
{/if}

{#if sources.includes("webcam")}
<button
class="icon"
class:selected={active_source === "webcam"}
aria-label="Record video"
on:click={() => {
handle_clear();
active_source = "webcam";
}}><Video /></button
aria-label="Capture from camera"
on:click={() => handle_select_source("webcam")}><Webcam /></button
>
{/if}
{#if sources.includes("clipboard")}
<button
class="icon"
class:selected={active_source === "clipboard"}
aria-label="Paste from clipboard"
on:click={() => handle_select_source("clipboard")}
><ImagePaste /></button
>
{/if}
</span>
Expand All @@ -58,7 +74,6 @@
right: 0;
margin-left: auto;
margin-right: auto;
align-self: flex-end;
}

.icon {
Expand Down
16 changes: 12 additions & 4 deletions js/atoms/src/UploadText.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
import type { I18nFormatter } from "@gradio/utils";
import { Upload as UploadIcon } from "@gradio/icons";
export let type: "video" | "image" | "audio" | "file" | "csv" = "file";
import { Upload as UploadIcon, ImagePaste } from "@gradio/icons";
export let type: "video" | "image" | "audio" | "file" | "csv" | "clipboard" =
"file";
export let i18n: I18nFormatter;
export let message: string | undefined = undefined;
export let mode: "full" | "short" = "full";
Expand All @@ -12,12 +13,19 @@
video: "upload_text.drop_video",
audio: "upload_text.drop_audio",
file: "upload_text.drop_file",
csv: "upload_text.drop_csv"
csv: "upload_text.drop_csv",
clipboard: "upload_text.paste_clipboard"
};
</script>

<div class="wrap">
<span class="icon-wrap" class:hovered><UploadIcon /> </span>
<span class="icon-wrap" class:hovered>
{#if type === "clipboard"}
<ImagePaste />
{:else}
<UploadIcon />
{/if}
</span>

{i18n(defs[type] || defs.file)}

Expand Down
2 changes: 1 addition & 1 deletion js/audio/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@

let dragging: boolean;

$: if (sources) {
$: if (!active_source && sources) {
active_source = sources[0];
}

Expand Down
1 change: 0 additions & 1 deletion js/audio/interactive/InteractiveAudio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@
bind:dragging
on:error={({ detail }) => dispatch("error", detail)}
{root}
include_sources={sources.length > 1}
>
<slot />
</Upload>
Expand Down
67 changes: 48 additions & 19 deletions js/image/Image.stories.svelte
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
<script lang="ts">
import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
import StaticImage from "./Index.svelte";
import { userEvent, within } from "@storybook/testing-library";
</script>

<Meta
title="Components/Image"
component={Image}
argTypes={{
value: {
control: "object",
description: "The image URL or file to display",
name: "value"
},
show_download_button: {
options: [true, false],
description: "If false, the download button will not be visible",
control: { type: "boolean" },
defaultValue: true
}
}}
/>
<Meta title="Components/Image" component={Image} />

<Template let:args>
<div
Expand All @@ -31,7 +16,7 @@
</Template>

<Story
name="Static Image with label and download button"
name="static with label and download button"
args={{
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
Expand All @@ -44,7 +29,7 @@
/>

<Story
name="Static Image with no label or download button"
name="static with no label or download button"
args={{
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
Expand All @@ -55,3 +40,47 @@
show_download_button: false
}}
/>

<Story
name="interactive with upload, clipboard, and webcam"
args={{
sources: ["upload", "clipboard", "webcam"],
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
orig_name: "cheetah.jpg"
},
show_label: false,
show_download_button: false,
interactive: true
}}
play={async ({ canvasElement }) => {
const canvas = within(canvasElement);

const webcamButton = await canvas.findByLabelText("Capture from camera");
userEvent.click(webcamButton);

userEvent.click(await canvas.findByTitle("select video source"));
userEvent.click(await canvas.findByLabelText("select source"));
userEvent.click(await canvas.findByLabelText("Upload file"));
userEvent.click(await canvas.findByLabelText("Paste from clipboard"));
}}
/>

<Story
name="interactive with webcam"
args={{
sources: ["webcam"],
show_download_button: true,
interactive: true
}}
/>

<Story
name="interactive with clipboard"
args={{
sources: ["clipboard"],
show_download_button: true,
interactive: true
}}
/>
14 changes: 8 additions & 6 deletions js/image/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import type { LoadingStatus } from "@gradio/statustracker";
import { normalise_file } from "@gradio/client";

type sources = "upload" | "webcam" | "clipboard" | null;

export let elem_id = "";
export let elem_classes: string[] = [];
export let visible = true;
Expand Down Expand Up @@ -66,7 +68,7 @@
$: url && gradio.dispatch("change");

let dragging: boolean;
let active_tool: null | "webcam" = null;
let active_source: sources = null;
</script>

{#if !interactive}
Expand Down Expand Up @@ -124,7 +126,7 @@
/>

<ImageUploader
bind:active_tool
bind:active_source
bind:value
selectable={_selectable}
{root}
Expand All @@ -144,17 +146,17 @@
loading_status.status = "error";
gradio.dispatch("error", detail);
}}
on:click={() => gradio.dispatch("error", "bad thing happened")}
hannahblair marked this conversation as resolved.
Show resolved Hide resolved
on:error
{label}
{show_label}
{pending}
{streaming}
{mirror_webcam}
i18n={gradio.i18n}
>
{#if sources.includes("upload")}
<UploadText i18n={gradio.i18n} type="image" mode="short" />
{#if active_source === "upload" || !active_source}
<UploadText i18n={gradio.i18n} type="image" />
{:else if active_source === "clipboard"}
<UploadText i18n={gradio.i18n} type="clipboard" mode="short" />
{:else}
<Empty unpadded_box={true} size="large"><Image /></Empty>
{/if}
Expand Down
Loading
Loading