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 21 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/strong-files-swim.md
@@ -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
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"
}
}
22 changes: 21 additions & 1 deletion js/app/test/image_component_events.spec.ts
Expand Up @@ -65,7 +65,27 @@ 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 expect(page.getByLabel("# Change Events").first()).toHaveValue("1");
await expect(page.getByLabel("# Upload Events")).toHaveValue("1");

await page.evaluate(async () => {
navigator.clipboard.writeText("");
});

await page.getByLabel("Upload file").click();
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 page.pause();
await expect(page.getByLabel("# Change Events").first()).toHaveValue("4");
});
53 changes: 34 additions & 19 deletions js/atoms/src/SelectSource.svelte
@@ -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
@@ -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
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
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
@@ -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
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