@gradio/image 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/Image.stories.svelte +51 -2
- package/Image.test.ts +1 -2
- package/Index.svelte +34 -1
- package/package.json +14 -9
- package/shared/ImagePreview.svelte +102 -29
- package/shared/ImageUploader.svelte +12 -5
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,41 @@
|
|
1
1
|
# @gradio/image
|
2
2
|
|
3
|
+
## 0.15.0
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
- [#9031](https://github.com/gradio-app/gradio/pull/9031) [`04b7d32`](https://github.com/gradio-app/gradio/commit/04b7d327ec1227a693fc2dfea51b1e2729851bde) - Allow drag and replace image in `gr.Image` and Multimodal textbox. Thanks @hannahblair!
|
8
|
+
- [#8930](https://github.com/gradio-app/gradio/pull/8930) [`41d5ab9`](https://github.com/gradio-app/gradio/commit/41d5ab987ba9728753be4509490c79041655809b) - Add `placeholder` param to Image and ImageEditor to replace upload image text. Thanks @hannahblair!
|
9
|
+
- [#9118](https://github.com/gradio-app/gradio/pull/9118) [`e1c404d`](https://github.com/gradio-app/gradio/commit/e1c404da1143fb52b659d03e028bdba1badf443d) - setup npm-previews of all packages. Thanks @pngwn!
|
10
|
+
|
11
|
+
### Fixes
|
12
|
+
|
13
|
+
- [#9116](https://github.com/gradio-app/gradio/pull/9116) [`ba6322e`](https://github.com/gradio-app/gradio/commit/ba6322ec2bb975f15389fe0700816bf671c6819d) - Fix image height content fit. Thanks @hannahblair!
|
14
|
+
|
15
|
+
### Dependency updates
|
16
|
+
|
17
|
+
- @gradio/utils@0.6.0
|
18
|
+
- @gradio/upload@0.12.3
|
19
|
+
- @gradio/atoms@0.8.0
|
20
|
+
- @gradio/client@1.5.1
|
21
|
+
- @gradio/statustracker@0.7.5
|
22
|
+
- @gradio/wasm@0.13.0
|
23
|
+
- @gradio/icons@0.7.1
|
24
|
+
|
25
|
+
## 0.14.0
|
26
|
+
|
27
|
+
### Features
|
28
|
+
|
29
|
+
- [#8964](https://github.com/gradio-app/gradio/pull/8964) [`bf6bbd9`](https://github.com/gradio-app/gradio/commit/bf6bbd971acddbf78f03bea431ed7d1e0003ccf9) - Add min/max-imize button to gr.Image and gr.Gallery. Thanks @hannahblair!
|
30
|
+
|
31
|
+
### Dependency updates
|
32
|
+
|
33
|
+
- @gradio/atoms@0.7.9
|
34
|
+
- @gradio/statustracker@0.7.4
|
35
|
+
- @gradio/client@1.5.0
|
36
|
+
- @gradio/icons@0.7.0
|
37
|
+
- @gradio/upload@0.12.2
|
38
|
+
|
3
39
|
## 0.13.1
|
4
40
|
|
5
41
|
### Dependency updates
|
package/Image.stories.svelte
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
import StaticImage from "./Index.svelte";
|
4
4
|
import { userEvent, within } from "@storybook/test";
|
5
5
|
import { allModes } from "../storybook/modes";
|
6
|
+
import image_file_100x100 from "../storybook/test_files/image_100x100.webp";
|
7
|
+
import image_file_100x1000 from "../storybook/test_files/image_100x100.webp";
|
6
8
|
|
7
9
|
export const meta = {
|
8
10
|
title: "Components/Image",
|
@@ -16,6 +18,8 @@
|
|
16
18
|
}
|
17
19
|
}
|
18
20
|
};
|
21
|
+
|
22
|
+
let md = `# a heading! /n a new line! `;
|
19
23
|
</script>
|
20
24
|
|
21
25
|
<Template let:args>
|
@@ -28,7 +32,7 @@
|
|
28
32
|
</Template>
|
29
33
|
|
30
34
|
<Story
|
31
|
-
name="static with label and download button"
|
35
|
+
name="static with label, info and download button"
|
32
36
|
args={{
|
33
37
|
value: {
|
34
38
|
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
|
@@ -36,8 +40,17 @@
|
|
36
40
|
orig_name: "cheetah.jpg"
|
37
41
|
},
|
38
42
|
show_label: true,
|
43
|
+
placeholder: "This is a cheetah",
|
39
44
|
show_download_button: true
|
40
45
|
}}
|
46
|
+
play={async ({ canvasElement }) => {
|
47
|
+
const canvas = within(canvasElement);
|
48
|
+
|
49
|
+
const expand_btn = canvas.getByRole("button", {
|
50
|
+
name: "View in full screen"
|
51
|
+
});
|
52
|
+
await userEvent.click(expand_btn);
|
53
|
+
}}
|
41
54
|
/>
|
42
55
|
|
43
56
|
<Story
|
@@ -53,6 +66,41 @@
|
|
53
66
|
}}
|
54
67
|
/>
|
55
68
|
|
69
|
+
<Story
|
70
|
+
name="static with a vertically long image"
|
71
|
+
args={{
|
72
|
+
value: {
|
73
|
+
path: image_file_100x1000,
|
74
|
+
url: image_file_100x1000,
|
75
|
+
orig_name: "image.webp"
|
76
|
+
}
|
77
|
+
}}
|
78
|
+
/>
|
79
|
+
|
80
|
+
<Story
|
81
|
+
name="static with a vertically long image and a fixed height"
|
82
|
+
args={{
|
83
|
+
value: {
|
84
|
+
path: image_file_100x1000,
|
85
|
+
url: image_file_100x1000,
|
86
|
+
orig_name: "image.webp"
|
87
|
+
},
|
88
|
+
height: "500px"
|
89
|
+
}}
|
90
|
+
/>
|
91
|
+
|
92
|
+
<Story
|
93
|
+
name="static with a small image and a fixed height"
|
94
|
+
args={{
|
95
|
+
value: {
|
96
|
+
path: image_file_100x100,
|
97
|
+
url: image_file_100x100,
|
98
|
+
orig_name: "image.webp"
|
99
|
+
},
|
100
|
+
height: "500px"
|
101
|
+
}}
|
102
|
+
/>
|
103
|
+
|
56
104
|
<Story
|
57
105
|
name="interactive with upload, clipboard, and webcam"
|
58
106
|
args={{
|
@@ -64,7 +112,8 @@
|
|
64
112
|
},
|
65
113
|
show_label: false,
|
66
114
|
show_download_button: false,
|
67
|
-
interactive: true
|
115
|
+
interactive: true,
|
116
|
+
placeholder: md
|
68
117
|
}}
|
69
118
|
play={async ({ canvasElement }) => {
|
70
119
|
const canvas = within(canvasElement);
|
package/Image.test.ts
CHANGED
@@ -7,9 +7,8 @@ import {
|
|
7
7
|
beforeAll,
|
8
8
|
beforeEach
|
9
9
|
} from "vitest";
|
10
|
-
import { spy } from "tinyspy";
|
11
10
|
import { cleanup, render } from "@gradio/tootils";
|
12
|
-
import { setupi18n } from "../
|
11
|
+
import { setupi18n } from "../core/src/i18n";
|
13
12
|
|
14
13
|
import Image from "./Index.svelte";
|
15
14
|
import type { LoadingStatus } from "@gradio/statustracker";
|
package/Index.svelte
CHANGED
@@ -51,6 +51,8 @@
|
|
51
51
|
export let streaming: boolean;
|
52
52
|
export let pending: boolean;
|
53
53
|
export let mirror_webcam: boolean;
|
54
|
+
export let placeholder: string | undefined = undefined;
|
55
|
+
export let show_fullscreen_button: boolean;
|
54
56
|
|
55
57
|
export let gradio: Gradio<{
|
56
58
|
input: never;
|
@@ -81,6 +83,30 @@
|
|
81
83
|
|
82
84
|
let dragging: boolean;
|
83
85
|
let active_source: sources = null;
|
86
|
+
let upload_component: ImageUploader;
|
87
|
+
const handle_drag_event = (event: Event): void => {
|
88
|
+
const drag_event = event as DragEvent;
|
89
|
+
drag_event.preventDefault();
|
90
|
+
drag_event.stopPropagation();
|
91
|
+
if (drag_event.type === "dragenter" || drag_event.type === "dragover") {
|
92
|
+
dragging = true;
|
93
|
+
} else if (drag_event.type === "dragleave") {
|
94
|
+
dragging = false;
|
95
|
+
}
|
96
|
+
};
|
97
|
+
|
98
|
+
const handle_drop = (event: Event): void => {
|
99
|
+
if (interactive) {
|
100
|
+
const drop_event = event as DragEvent;
|
101
|
+
drop_event.preventDefault();
|
102
|
+
drop_event.stopPropagation();
|
103
|
+
dragging = false;
|
104
|
+
|
105
|
+
if (upload_component) {
|
106
|
+
upload_component.loadFilesFromDrop(drop_event);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
};
|
84
110
|
</script>
|
85
111
|
|
86
112
|
{#if !interactive}
|
@@ -114,6 +140,7 @@
|
|
114
140
|
selectable={_selectable}
|
115
141
|
{show_share_button}
|
116
142
|
i18n={gradio.i18n}
|
143
|
+
{show_fullscreen_button}
|
117
144
|
/>
|
118
145
|
</Block>
|
119
146
|
{:else}
|
@@ -130,6 +157,10 @@
|
|
130
157
|
{container}
|
131
158
|
{scale}
|
132
159
|
{min_width}
|
160
|
+
on:dragenter={handle_drag_event}
|
161
|
+
on:dragleave={handle_drag_event}
|
162
|
+
on:dragover={handle_drag_event}
|
163
|
+
on:drop={handle_drop}
|
133
164
|
>
|
134
165
|
<StatusTracker
|
135
166
|
autoscroll={gradio.autoscroll}
|
@@ -139,8 +170,10 @@
|
|
139
170
|
/>
|
140
171
|
|
141
172
|
<ImageUploader
|
173
|
+
bind:this={upload_component}
|
142
174
|
bind:active_source
|
143
175
|
bind:value
|
176
|
+
bind:dragging
|
144
177
|
selectable={_selectable}
|
145
178
|
{root}
|
146
179
|
{sources}
|
@@ -169,7 +202,7 @@
|
|
169
202
|
stream_handler={gradio.client.stream}
|
170
203
|
>
|
171
204
|
{#if active_source === "upload" || !active_source}
|
172
|
-
<UploadText i18n={gradio.i18n} type="image" />
|
205
|
+
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
173
206
|
{:else if active_source === "clipboard"}
|
174
207
|
<UploadText i18n={gradio.i18n} type="clipboard" mode="short" />
|
175
208
|
{:else}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@gradio/image",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.15.0",
|
4
4
|
"description": "Gradio UI packages",
|
5
5
|
"type": "module",
|
6
6
|
"author": "",
|
@@ -10,16 +10,16 @@
|
|
10
10
|
"cropperjs": "^1.5.12",
|
11
11
|
"lazy-brush": "^1.0.1",
|
12
12
|
"resize-observer-polyfill": "^1.5.1",
|
13
|
-
"@gradio/atoms": "^0.
|
14
|
-
"@gradio/icons": "^0.
|
15
|
-
"@gradio/statustracker": "^0.7.
|
16
|
-
"@gradio/
|
17
|
-
"@gradio/
|
18
|
-
"@gradio/wasm": "^0.
|
19
|
-
"@gradio/
|
13
|
+
"@gradio/atoms": "^0.8.0",
|
14
|
+
"@gradio/icons": "^0.7.1",
|
15
|
+
"@gradio/statustracker": "^0.7.5",
|
16
|
+
"@gradio/client": "^1.5.1",
|
17
|
+
"@gradio/utils": "^0.6.0",
|
18
|
+
"@gradio/wasm": "^0.13.0",
|
19
|
+
"@gradio/upload": "^0.12.3"
|
20
20
|
},
|
21
21
|
"devDependencies": {
|
22
|
-
"@gradio/preview": "^0.
|
22
|
+
"@gradio/preview": "^0.11.0"
|
23
23
|
},
|
24
24
|
"main_changeset": true,
|
25
25
|
"main": "./Index.svelte",
|
@@ -29,5 +29,10 @@
|
|
29
29
|
"./example": "./Example.svelte",
|
30
30
|
"./base": "./shared/ImagePreview.svelte",
|
31
31
|
"./package.json": "./package.json"
|
32
|
+
},
|
33
|
+
"repository": {
|
34
|
+
"type": "git",
|
35
|
+
"url": "git+https://github.com/gradio-app/gradio.git",
|
36
|
+
"directory": "js/image"
|
32
37
|
}
|
33
38
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<script lang="ts">
|
2
|
-
import { createEventDispatcher } from "svelte";
|
2
|
+
import { createEventDispatcher, onMount } from "svelte";
|
3
3
|
import type { SelectData } from "@gradio/utils";
|
4
4
|
import { uploadToHuggingFace } from "@gradio/utils";
|
5
5
|
import { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
|
@@ -7,6 +7,7 @@
|
|
7
7
|
import { get_coordinates_of_clicked_image } from "./utils";
|
8
8
|
import Image from "./Image.svelte";
|
9
9
|
import { DownloadLink } from "@gradio/wasm/svelte";
|
10
|
+
import { Maximize, Minimize } from "@gradio/icons";
|
10
11
|
|
11
12
|
import { Image as ImageIcon } from "@gradio/icons";
|
12
13
|
import { type FileData } from "@gradio/client";
|
@@ -19,6 +20,7 @@
|
|
19
20
|
export let selectable = false;
|
20
21
|
export let show_share_button = false;
|
21
22
|
export let i18n: I18nFormatter;
|
23
|
+
export let show_fullscreen_button = true;
|
22
24
|
|
23
25
|
const dispatch = createEventDispatcher<{
|
24
26
|
change: string;
|
@@ -31,6 +33,24 @@
|
|
31
33
|
dispatch("select", { index: coordinates, value: null });
|
32
34
|
}
|
33
35
|
};
|
36
|
+
|
37
|
+
let is_full_screen = false;
|
38
|
+
let image_container: HTMLElement;
|
39
|
+
|
40
|
+
onMount(() => {
|
41
|
+
document.addEventListener("fullscreenchange", () => {
|
42
|
+
is_full_screen = !!document.fullscreenElement;
|
43
|
+
});
|
44
|
+
});
|
45
|
+
|
46
|
+
const toggle_full_screen = async (): Promise<void> => {
|
47
|
+
if (!is_full_screen) {
|
48
|
+
await image_container.requestFullscreen();
|
49
|
+
} else {
|
50
|
+
await document.exitFullscreen();
|
51
|
+
is_full_screen = !is_full_screen;
|
52
|
+
}
|
53
|
+
};
|
34
54
|
</script>
|
35
55
|
|
36
56
|
<BlockLabel
|
@@ -41,44 +61,78 @@
|
|
41
61
|
{#if value === null || !value.url}
|
42
62
|
<Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
|
43
63
|
{:else}
|
44
|
-
<div class="
|
45
|
-
|
46
|
-
|
47
|
-
<IconButton
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
<div class="image-container" bind:this={image_container}>
|
65
|
+
<div class="icon-buttons">
|
66
|
+
{#if !is_full_screen && show_fullscreen_button}
|
67
|
+
<IconButton
|
68
|
+
Icon={Maximize}
|
69
|
+
label={is_full_screen ? "Exit full screen" : "View in full screen"}
|
70
|
+
on:click={toggle_full_screen}
|
71
|
+
/>
|
72
|
+
{/if}
|
73
|
+
|
74
|
+
{#if is_full_screen && show_fullscreen_button}
|
75
|
+
<IconButton
|
76
|
+
Icon={Minimize}
|
77
|
+
label={is_full_screen ? "Exit full screen" : "View in full screen"}
|
78
|
+
on:click={toggle_full_screen}
|
79
|
+
/>
|
80
|
+
{/if}
|
81
|
+
|
82
|
+
{#if show_download_button}
|
83
|
+
<DownloadLink href={value.url} download={value.orig_name || "image"}>
|
84
|
+
<IconButton Icon={Download} label={i18n("common.download")} />
|
85
|
+
</DownloadLink>
|
86
|
+
{/if}
|
87
|
+
{#if show_share_button}
|
88
|
+
<ShareButton
|
89
|
+
{i18n}
|
90
|
+
on:share
|
91
|
+
on:error
|
92
|
+
formatter={async (value) => {
|
93
|
+
if (!value) return "";
|
94
|
+
let url = await uploadToHuggingFace(value, "url");
|
95
|
+
return `<img src="${url}" />`;
|
96
|
+
}}
|
97
|
+
{value}
|
98
|
+
/>
|
99
|
+
{/if}
|
67
100
|
</div>
|
68
|
-
|
101
|
+
<button on:click={handle_click}>
|
102
|
+
<div class:selectable class="image-frame">
|
103
|
+
<Image src={value.url} alt="" loading="lazy" on:load />
|
104
|
+
</div>
|
105
|
+
</button>
|
106
|
+
</div>
|
69
107
|
{/if}
|
70
108
|
|
71
109
|
<style>
|
72
110
|
.image-container {
|
73
111
|
height: 100%;
|
112
|
+
position: relative;
|
74
113
|
}
|
75
|
-
|
76
|
-
button {
|
114
|
+
|
115
|
+
.image-container button {
|
77
116
|
width: var(--size-full);
|
78
117
|
height: var(--size-full);
|
79
|
-
object-fit: contain;
|
80
|
-
display: block;
|
81
118
|
border-radius: var(--radius-lg);
|
119
|
+
|
120
|
+
display: flex;
|
121
|
+
align-items: center;
|
122
|
+
justify-content: center;
|
123
|
+
}
|
124
|
+
|
125
|
+
.image-frame {
|
126
|
+
width: auto;
|
127
|
+
height: 100%;
|
128
|
+
display: flex;
|
129
|
+
align-items: center;
|
130
|
+
justify-content: center;
|
131
|
+
}
|
132
|
+
.image-frame :global(img) {
|
133
|
+
width: var(--size-full);
|
134
|
+
height: var(--size-full);
|
135
|
+
object-fit: scale-down;
|
82
136
|
}
|
83
137
|
|
84
138
|
.selectable {
|
@@ -91,5 +145,24 @@
|
|
91
145
|
top: 6px;
|
92
146
|
right: 6px;
|
93
147
|
gap: var(--size-1);
|
148
|
+
z-index: 1;
|
149
|
+
}
|
150
|
+
|
151
|
+
:global(.fullscreen-controls svg) {
|
152
|
+
position: relative;
|
153
|
+
top: 0px;
|
154
|
+
}
|
155
|
+
|
156
|
+
:global(.image-container:fullscreen) {
|
157
|
+
background-color: black;
|
158
|
+
display: flex;
|
159
|
+
justify-content: center;
|
160
|
+
align-items: center;
|
161
|
+
}
|
162
|
+
|
163
|
+
:global(.image-container:fullscreen img) {
|
164
|
+
max-width: 90vw;
|
165
|
+
max-height: 90vh;
|
166
|
+
object-fit: scale-down;
|
94
167
|
}
|
95
168
|
</style>
|
@@ -70,7 +70,7 @@
|
|
70
70
|
select: SelectData;
|
71
71
|
}>();
|
72
72
|
|
73
|
-
let dragging = false;
|
73
|
+
export let dragging = false;
|
74
74
|
|
75
75
|
$: dispatch("drag", dragging);
|
76
76
|
|
@@ -109,7 +109,11 @@
|
|
109
109
|
}}
|
110
110
|
/>
|
111
111
|
{/if}
|
112
|
-
<div
|
112
|
+
<div
|
113
|
+
class="upload-container"
|
114
|
+
class:reduced-height={sources.length > 1}
|
115
|
+
style:width={value ? "auto" : "100%"}
|
116
|
+
>
|
113
117
|
<Upload
|
114
118
|
hidden={value !== null || active_source === "webcam"}
|
115
119
|
bind:this={upload_input}
|
@@ -120,7 +124,7 @@
|
|
120
124
|
on:error
|
121
125
|
{root}
|
122
126
|
{max_file_size}
|
123
|
-
disable_click={!sources.includes("upload")}
|
127
|
+
disable_click={!sources.includes("upload") || value !== null}
|
124
128
|
{upload}
|
125
129
|
{stream_handler}
|
126
130
|
>
|
@@ -165,7 +169,7 @@
|
|
165
169
|
.image-frame :global(img) {
|
166
170
|
width: var(--size-full);
|
167
171
|
height: var(--size-full);
|
168
|
-
object-fit:
|
172
|
+
object-fit: scale-down;
|
169
173
|
}
|
170
174
|
|
171
175
|
.image-frame {
|
@@ -175,10 +179,13 @@
|
|
175
179
|
}
|
176
180
|
|
177
181
|
.upload-container {
|
182
|
+
display: flex;
|
183
|
+
align-items: center;
|
184
|
+
justify-content: center;
|
185
|
+
|
178
186
|
height: 100%;
|
179
187
|
flex-shrink: 1;
|
180
188
|
max-height: 100%;
|
181
|
-
width: 100%;
|
182
189
|
}
|
183
190
|
|
184
191
|
.reduced-height {
|