mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-25 12:10:31 +08:00
Add placeholder
param to Image and ImageEditor to replace upload image text (#8930)
This commit is contained in:
parent
747013bbac
commit
41d5ab987b
9
.changeset/hip-apes-help.md
Normal file
9
.changeset/hip-apes-help.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
"@gradio/atoms": minor
|
||||
"@gradio/image": minor
|
||||
"@gradio/imageeditor": minor
|
||||
"@gradio/simpleimage": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:Add `placeholder` param to Image and ImageEditor to replace upload image text
|
@ -74,6 +74,7 @@ class Image(StreamingInput, Component):
|
||||
key: int | str | None = None,
|
||||
mirror_webcam: bool = True,
|
||||
show_share_button: bool | None = None,
|
||||
placeholder: str | None = None,
|
||||
show_fullscreen_button: bool = True,
|
||||
):
|
||||
"""
|
||||
@ -102,6 +103,7 @@ class Image(StreamingInput, Component):
|
||||
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
|
||||
mirror_webcam: If True webcam will be mirrored. Default is True.
|
||||
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
|
||||
placeholder: Custom text for the upload area. Overrides default upload messages when provided. Accepts new lines and `#` to designate a heading.
|
||||
show_fullscreen_button: If True, will show a fullscreen icon in the corner of the component that allows user to view the image in fullscreen mode. If False, icon does not appear.
|
||||
"""
|
||||
self.format = format
|
||||
@ -141,6 +143,7 @@ class Image(StreamingInput, Component):
|
||||
else show_share_button
|
||||
)
|
||||
self.show_fullscreen_button = show_fullscreen_button
|
||||
self.placeholder = placeholder
|
||||
super().__init__(
|
||||
label=label,
|
||||
every=every,
|
||||
|
@ -171,6 +171,7 @@ class ImageEditor(Component):
|
||||
elem_classes: list[str] | str | None = None,
|
||||
render: bool = True,
|
||||
key: int | str | None = None,
|
||||
placeholder: str | None = None,
|
||||
mirror_webcam: bool = True,
|
||||
show_share_button: bool | None = None,
|
||||
_selectable: bool = False,
|
||||
@ -205,6 +206,7 @@ class ImageEditor(Component):
|
||||
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
|
||||
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
|
||||
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
|
||||
placeholder: Custom text for the upload area. Overrides default upload messages when provided. Accepts new lines and `#` to designate a heading.
|
||||
mirror_webcam: If True webcam will be mirrored. Default is True.
|
||||
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
|
||||
crop_size: The size of the crop box in pixels. If a tuple, the first value is the width and the second value is the height. If a string, the value must be a ratio in the form `width:height` (e.g. "16:9").
|
||||
@ -257,6 +259,7 @@ class ImageEditor(Component):
|
||||
self.layers = layers
|
||||
self.canvas_size = canvas_size
|
||||
self.show_fullscreen_button = show_fullscreen_button
|
||||
self.placeholder = placeholder
|
||||
|
||||
super().__init__(
|
||||
label=label,
|
||||
|
@ -115,6 +115,7 @@ class Sketchpad(components.ImageEditor):
|
||||
elem_classes: list[str] | str | None = None,
|
||||
render: bool = True,
|
||||
key: int | str | None = None,
|
||||
placeholder: str | None = None,
|
||||
mirror_webcam: bool = True,
|
||||
show_share_button: bool | None = None,
|
||||
_selectable: bool = False,
|
||||
@ -150,6 +151,7 @@ class Sketchpad(components.ImageEditor):
|
||||
elem_classes=elem_classes,
|
||||
render=render,
|
||||
key=key,
|
||||
placeholder=placeholder,
|
||||
mirror_webcam=mirror_webcam,
|
||||
show_share_button=show_share_button,
|
||||
_selectable=_selectable,
|
||||
@ -210,6 +212,7 @@ class Paint(components.ImageEditor):
|
||||
layers: bool = True,
|
||||
canvas_size: tuple[int, int] | None = None,
|
||||
show_fullscreen_button: bool = True,
|
||||
placeholder: str | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
value=value,
|
||||
@ -243,6 +246,7 @@ class Paint(components.ImageEditor):
|
||||
layers=layers,
|
||||
canvas_size=canvas_size,
|
||||
show_fullscreen_button=show_fullscreen_button,
|
||||
placeholder=placeholder,
|
||||
)
|
||||
|
||||
|
||||
@ -285,6 +289,7 @@ class ImageMask(components.ImageEditor):
|
||||
elem_classes: list[str] | str | None = None,
|
||||
render: bool = True,
|
||||
key: int | str | None = None,
|
||||
placeholder: str | None = None,
|
||||
mirror_webcam: bool = True,
|
||||
show_share_button: bool | None = None,
|
||||
_selectable: bool = False,
|
||||
@ -320,6 +325,7 @@ class ImageMask(components.ImageEditor):
|
||||
elem_classes=elem_classes,
|
||||
render=render,
|
||||
key=key,
|
||||
placeholder=placeholder,
|
||||
mirror_webcam=mirror_webcam,
|
||||
show_share_button=show_share_button,
|
||||
_selectable=_selectable,
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { I18nFormatter } from "@gradio/utils";
|
||||
import { Upload as UploadIcon, ImagePaste } from "@gradio/icons";
|
||||
import { inject } from "./utils/parse_placeholder";
|
||||
|
||||
export let type:
|
||||
| "video"
|
||||
| "image"
|
||||
@ -13,6 +15,7 @@
|
||||
export let message: string | undefined = undefined;
|
||||
export let mode: "full" | "short" = "full";
|
||||
export let hovered = false;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
|
||||
const defs = {
|
||||
image: "upload_text.drop_image",
|
||||
@ -23,6 +26,8 @@
|
||||
gallery: "upload_text.drop_gallery",
|
||||
clipboard: "upload_text.paste_clipboard"
|
||||
};
|
||||
|
||||
$: [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
@ -34,15 +39,33 @@
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{i18n(defs[type] || defs.file)}
|
||||
{#if heading || paragraph}
|
||||
{#if heading}
|
||||
<h2>{heading}</h2>
|
||||
{/if}
|
||||
{#if paragraph}
|
||||
<p>{paragraph}</p>
|
||||
{/if}
|
||||
{:else}
|
||||
{i18n(defs[type] || defs.file)}
|
||||
|
||||
{#if mode !== "short"}
|
||||
<span class="or">- {i18n("common.or")} -</span>
|
||||
{message || i18n("upload_text.click_to_upload")}
|
||||
{#if mode !== "short"}
|
||||
<span class="or">- {i18n("common.or")} -</span>
|
||||
{message || i18n("upload_text.click_to_upload")}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h2 {
|
||||
font-size: var(--text-xl) !important;
|
||||
}
|
||||
|
||||
p,
|
||||
h2 {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -53,6 +76,8 @@
|
||||
line-height: var(--line-md);
|
||||
height: 100%;
|
||||
padding-top: var(--size-3);
|
||||
text-align: center;
|
||||
margin: auto var(--spacing-lg);
|
||||
}
|
||||
|
||||
.or {
|
||||
|
27
js/atoms/src/utils/parse_placeholder.ts
Normal file
27
js/atoms/src/utils/parse_placeholder.ts
Normal file
@ -0,0 +1,27 @@
|
||||
const RE_HEADING = /^(#\s*)(.+)$/m;
|
||||
|
||||
export function inject(text: string): [string | false, string | false] {
|
||||
const trimmed_text = text.trim();
|
||||
|
||||
const heading_match = trimmed_text.match(RE_HEADING);
|
||||
if (!heading_match) {
|
||||
return [false, trimmed_text || false];
|
||||
}
|
||||
|
||||
const [full_match, , heading_content] = heading_match;
|
||||
const _heading = heading_content.trim();
|
||||
|
||||
if (trimmed_text === full_match) {
|
||||
return [_heading, false];
|
||||
}
|
||||
|
||||
const heading_end_index =
|
||||
heading_match.index !== undefined
|
||||
? heading_match.index + full_match.length
|
||||
: 0;
|
||||
const remaining_text = trimmed_text.substring(heading_end_index).trim();
|
||||
|
||||
const _paragraph = remaining_text || false;
|
||||
|
||||
return [_heading, _paragraph];
|
||||
}
|
@ -29,7 +29,8 @@
|
||||
["swim", "swim"],
|
||||
["jump", "jump"]
|
||||
],
|
||||
label: "Single-select Dropdown"
|
||||
label: "Single-select Dropdown",
|
||||
info: "This is a single-select dropdown"
|
||||
}}
|
||||
/>
|
||||
<Story
|
||||
|
@ -16,6 +16,8 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let md = `# a heading! /n a new line! `;
|
||||
</script>
|
||||
|
||||
<Template let:args>
|
||||
@ -28,20 +30,7 @@
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="static with label and download button"
|
||||
args={{
|
||||
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: true,
|
||||
show_download_button: true
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="static with label and download button"
|
||||
name="static with label, info and download button"
|
||||
args={{
|
||||
value: {
|
||||
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
|
||||
@ -49,6 +38,7 @@
|
||||
orig_name: "cheetah.jpg"
|
||||
},
|
||||
show_label: true,
|
||||
placeholder: "This is a cheetah",
|
||||
show_download_button: true
|
||||
}}
|
||||
play={async ({ canvasElement }) => {
|
||||
@ -85,7 +75,8 @@
|
||||
},
|
||||
show_label: false,
|
||||
show_download_button: false,
|
||||
interactive: true
|
||||
interactive: true,
|
||||
placeholder: md
|
||||
}}
|
||||
play={async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
@ -51,6 +51,7 @@
|
||||
export let streaming: boolean;
|
||||
export let pending: boolean;
|
||||
export let mirror_webcam: boolean;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let show_fullscreen_button: boolean;
|
||||
|
||||
export let gradio: Gradio<{
|
||||
@ -201,7 +202,7 @@
|
||||
stream_handler={gradio.client.stream}
|
||||
>
|
||||
{#if active_source === "upload" || !active_source}
|
||||
<UploadText i18n={gradio.i18n} type="image" />
|
||||
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
||||
{:else if active_source === "clipboard"}
|
||||
<UploadText i18n={gradio.i18n} type="clipboard" mode="short" />
|
||||
{:else}
|
||||
|
@ -214,6 +214,7 @@
|
||||
orig_name: "cheetah.jpg"
|
||||
},
|
||||
type: "pil",
|
||||
placeholder: "Upload an image of a cat",
|
||||
sources: ["upload", "webcam"],
|
||||
interactive: "true",
|
||||
brush: {
|
||||
|
@ -44,6 +44,7 @@
|
||||
"webcam"
|
||||
];
|
||||
export let interactive: boolean;
|
||||
export let placeholder: string | undefined;
|
||||
|
||||
export let brush: Brush;
|
||||
export let eraser: Eraser;
|
||||
@ -214,6 +215,7 @@
|
||||
status={loading_status?.status}
|
||||
upload={gradio.client.upload}
|
||||
stream_handler={gradio.client.stream}
|
||||
{placeholder}
|
||||
></InteractiveImageEditor>
|
||||
</Block>
|
||||
{/if}
|
||||
|
@ -25,6 +25,7 @@
|
||||
import { Tools, Crop, Brush, Sources } from "./tools";
|
||||
import { BlockLabel } from "@gradio/atoms";
|
||||
import { Image as ImageIcon } from "@gradio/icons";
|
||||
import { inject } from "./utils/parse_placeholder";
|
||||
|
||||
export let brush: IBrush | null;
|
||||
export let eraser: Eraser | null;
|
||||
@ -49,6 +50,7 @@
|
||||
export let upload: Client["upload"];
|
||||
export let stream_handler: Client["stream"];
|
||||
export let dragging: boolean;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
clear?: never;
|
||||
@ -197,6 +199,8 @@
|
||||
|
||||
let active_mode: "webcam" | "color" | null = null;
|
||||
let editor_height = 0;
|
||||
|
||||
$: [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
|
||||
</script>
|
||||
|
||||
<BlockLabel
|
||||
@ -254,13 +258,22 @@
|
||||
{#if !bg && !history && active_mode !== "webcam" && status !== "error"}
|
||||
<div class="empty wrap" style:height={`${editor_height}px`}>
|
||||
{#if sources && sources.length}
|
||||
<div>Upload an image</div>
|
||||
{#if heading || paragraph}
|
||||
{#if heading}
|
||||
<h2>{heading}</h2>
|
||||
{/if}
|
||||
{#if paragraph}
|
||||
<p>{paragraph}</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<div>Upload an image</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if sources && sources.length && brush}
|
||||
{#if sources && sources.length && brush && !placeholder}
|
||||
<div class="or">or</div>
|
||||
{/if}
|
||||
{#if brush}
|
||||
{#if brush && !placeholder}
|
||||
<div>select the draw tool to start</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -268,6 +281,15 @@
|
||||
</ImageEditor>
|
||||
|
||||
<style>
|
||||
h2 {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
|
||||
p,
|
||||
h2 {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -292,7 +314,7 @@
|
||||
align-items: center;
|
||||
color: var(--block-label-text-color);
|
||||
line-height: var(--line-md);
|
||||
font-size: var(--text-lg);
|
||||
font-size: var(--text-md);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
27
js/imageeditor/shared/utils/parse_placeholder.ts
Normal file
27
js/imageeditor/shared/utils/parse_placeholder.ts
Normal file
@ -0,0 +1,27 @@
|
||||
const RE_HEADING = /^(#\s*)(.+)$/m;
|
||||
|
||||
export function inject(text: string): [string | false, string | false] {
|
||||
const trimmed_text = text.trim();
|
||||
|
||||
const heading_match = trimmed_text.match(RE_HEADING);
|
||||
if (!heading_match) {
|
||||
return [false, trimmed_text || false];
|
||||
}
|
||||
|
||||
const [full_match, , heading_content] = heading_match;
|
||||
const _heading = heading_content.trim();
|
||||
|
||||
if (trimmed_text === full_match) {
|
||||
return [_heading, false];
|
||||
}
|
||||
|
||||
const heading_end_index =
|
||||
heading_match.index !== undefined
|
||||
? heading_match.index + full_match.length
|
||||
: 0;
|
||||
const remaining_text = trimmed_text.substring(heading_end_index).trim();
|
||||
|
||||
const _paragraph = remaining_text || false;
|
||||
|
||||
return [_heading, _paragraph];
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let interactive: boolean;
|
||||
export let root: string;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
@ -100,7 +101,7 @@
|
||||
{label}
|
||||
{show_label}
|
||||
>
|
||||
<UploadText i18n={gradio.i18n} type="image" />
|
||||
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
||||
</ImageUploader>
|
||||
</Block>
|
||||
{/if}
|
||||
|
@ -54,6 +54,7 @@ class TestImage:
|
||||
"key": None,
|
||||
"streamable": False,
|
||||
"type": "pil",
|
||||
"placeholder": None,
|
||||
}
|
||||
assert image_input.preprocess(None) is None
|
||||
image_input = gr.Image()
|
||||
|
@ -69,6 +69,7 @@ class TestImageEditor:
|
||||
"format": "webp",
|
||||
"layers": True,
|
||||
"canvas_size": None,
|
||||
"placeholder": None,
|
||||
"show_fullscreen_button": True,
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user