@gradio/image 0.24.0 → 0.25.1
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 +48 -0
- package/Image.stories.svelte +136 -87
- package/Image.test.ts +6 -3
- package/ImageExample.stories.svelte +20 -11
- package/Index.svelte +7 -2
- package/dist/Index.svelte +7 -2
- package/dist/shared/ImagePreview.svelte +8 -5
- package/dist/shared/ImagePreview.svelte.d.ts +3 -1
- package/dist/shared/ImageUploader.svelte +5 -8
- package/dist/shared/ImageUploader.svelte.d.ts +3 -2
- package/dist/shared/Webcam.svelte +0 -1
- package/dist/shared/types.d.ts +5 -1
- package/package.json +9 -9
- package/shared/ImagePreview.svelte +8 -5
- package/shared/ImageUploader.svelte +5 -8
- package/shared/Webcam.svelte +0 -1
- package/shared/stream_utils.test.ts +47 -15
- package/shared/types.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
1
|
# @gradio/image
|
|
2
|
+
|
|
3
|
+
## 0.25.1
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- [#12800](https://github.com/gradio-app/gradio/pull/12800) [`7a1c321`](https://github.com/gradio-app/gradio/commit/7a1c321b6546ba05a353488f5133e8262c4a8a39) - Bump svelte/kit for security reasons. Thanks @freddyaboulton!
|
|
8
|
+
- [#12779](https://github.com/gradio-app/gradio/pull/12779) [`ea2d3e9`](https://github.com/gradio-app/gradio/commit/ea2d3e985a8b42d188e551f517c5825c00790628) - Migrate Audio + Upload + Atoms to Svelte 5. Thanks @dawoodkhan82!
|
|
9
|
+
|
|
10
|
+
### Dependency updates
|
|
11
|
+
|
|
12
|
+
- @gradio/statustracker@0.12.2
|
|
13
|
+
- @gradio/atoms@0.20.1
|
|
14
|
+
- @gradio/utils@0.11.2
|
|
15
|
+
- @gradio/icons@0.15.1
|
|
16
|
+
- @gradio/upload@0.17.4
|
|
17
|
+
- @gradio/client@2.0.3
|
|
18
|
+
|
|
19
|
+
## 0.25.0
|
|
20
|
+
|
|
21
|
+
### Dependency updates
|
|
22
|
+
|
|
23
|
+
- @gradio/utils@0.11.1
|
|
24
|
+
- @gradio/client@2.0.2
|
|
25
|
+
|
|
26
|
+
## 0.25.0
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
- [#12539](https://github.com/gradio-app/gradio/pull/12539) [`f1d83fa`](https://github.com/gradio-app/gradio/commit/f1d83fac3d6e4bad60cf896a026fa2d572f26073) - Add ability to add custom buttons to components. Thanks @abidlabs!
|
|
31
|
+
|
|
32
|
+
### Fixes
|
|
33
|
+
|
|
34
|
+
- [#12575](https://github.com/gradio-app/gradio/pull/12575) [`7498fac`](https://github.com/gradio-app/gradio/commit/7498fac6a1c575ff04920ad5178853843a3b270e) - Fix image buttons default value. Thanks @freddyaboulton!
|
|
35
|
+
|
|
36
|
+
### Dependency updates
|
|
37
|
+
|
|
38
|
+
- @gradio/atoms@0.20.0
|
|
39
|
+
- @gradio/utils@0.11.0
|
|
40
|
+
- @gradio/client@2.0.1
|
|
41
|
+
- @gradio/statustracker@0.12.1
|
|
42
|
+
- @gradio/upload@0.17.3
|
|
43
|
+
|
|
44
|
+
## 0.24.0
|
|
45
|
+
|
|
46
|
+
### Dependency updates
|
|
47
|
+
|
|
48
|
+
- @gradio/utils@0.10.4
|
|
49
|
+
|
|
2
50
|
## 0.24.0
|
|
3
51
|
|
|
4
52
|
### Features
|
package/Image.stories.svelte
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import {
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
3
|
import StaticImage from "./Index.svelte";
|
|
4
|
-
import { userEvent, within } from "
|
|
4
|
+
import { userEvent, within } from "storybook/test";
|
|
5
5
|
import { allModes } from "../storybook/modes";
|
|
6
|
-
import
|
|
7
|
-
import image_file_100x1000 from "../storybook/test_files/image_100x100.webp";
|
|
6
|
+
import { wrapProps } from "../storybook/wrapProps";
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
const cheetah = "/cheetah.jpg";
|
|
9
|
+
const lion = "/lion.jpg";
|
|
10
|
+
|
|
11
|
+
const { Story } = defineMeta({
|
|
10
12
|
title: "Components/Image",
|
|
11
13
|
component: StaticImage,
|
|
12
14
|
parameters: {
|
|
@@ -17,177 +19,224 @@
|
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
|
-
};
|
|
22
|
+
});
|
|
21
23
|
|
|
22
24
|
let md = `# a heading! /n a new line! `;
|
|
23
25
|
</script>
|
|
24
26
|
|
|
25
|
-
<Template let:args>
|
|
26
|
-
<div
|
|
27
|
-
class="image-container"
|
|
28
|
-
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
29
|
-
>
|
|
30
|
-
<StaticImage {...args} />
|
|
31
|
-
</div>
|
|
32
|
-
</Template>
|
|
33
|
-
|
|
34
27
|
<Story
|
|
35
28
|
name="static with label, info and download button"
|
|
36
29
|
args={{
|
|
37
30
|
value: {
|
|
38
|
-
path:
|
|
39
|
-
url:
|
|
31
|
+
path: cheetah,
|
|
32
|
+
url: cheetah,
|
|
40
33
|
orig_name: "cheetah.jpg"
|
|
41
34
|
},
|
|
42
35
|
show_label: true,
|
|
43
36
|
placeholder: "This is a cheetah",
|
|
44
|
-
|
|
45
|
-
webcam_options: {
|
|
46
|
-
mirror: true,
|
|
47
|
-
constraints: null
|
|
48
|
-
}
|
|
37
|
+
buttons: ["fullscreen", "download"],
|
|
38
|
+
webcam_options: { mirror: true, constraints: null }
|
|
49
39
|
}}
|
|
50
40
|
play={async ({ canvasElement }) => {
|
|
51
41
|
const canvas = within(canvasElement);
|
|
52
|
-
|
|
53
|
-
const expand_btn = canvas.getByRole("button", {
|
|
54
|
-
name: "Fullscreen"
|
|
55
|
-
});
|
|
42
|
+
const expand_btn = canvas.getByRole("button", { name: "Fullscreen" });
|
|
56
43
|
await userEvent.click(expand_btn);
|
|
57
44
|
}}
|
|
58
|
-
|
|
45
|
+
>
|
|
46
|
+
{#snippet template(args)}
|
|
47
|
+
<div
|
|
48
|
+
class="image-container"
|
|
49
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
50
|
+
>
|
|
51
|
+
<StaticImage {...wrapProps(args)} />
|
|
52
|
+
</div>
|
|
53
|
+
{/snippet}
|
|
54
|
+
</Story>
|
|
59
55
|
|
|
60
56
|
<Story
|
|
61
57
|
name="static with no label or download button"
|
|
62
58
|
args={{
|
|
63
59
|
value: {
|
|
64
|
-
path:
|
|
65
|
-
url:
|
|
60
|
+
path: cheetah,
|
|
61
|
+
url: cheetah,
|
|
66
62
|
orig_name: "cheetah.jpg"
|
|
67
63
|
},
|
|
68
64
|
show_label: false,
|
|
69
|
-
|
|
70
|
-
webcam_options: {
|
|
71
|
-
mirror: true,
|
|
72
|
-
constraints: null
|
|
73
|
-
}
|
|
65
|
+
buttons: [],
|
|
66
|
+
webcam_options: { mirror: true, constraints: null }
|
|
74
67
|
}}
|
|
75
|
-
|
|
68
|
+
>
|
|
69
|
+
{#snippet template(args)}
|
|
70
|
+
<div
|
|
71
|
+
class="image-container"
|
|
72
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
73
|
+
>
|
|
74
|
+
<StaticImage {...wrapProps(args)} />
|
|
75
|
+
</div>
|
|
76
|
+
{/snippet}
|
|
77
|
+
</Story>
|
|
76
78
|
|
|
77
79
|
<Story
|
|
78
80
|
name="static with a vertically long image"
|
|
79
81
|
args={{
|
|
80
82
|
value: {
|
|
81
|
-
path:
|
|
82
|
-
url:
|
|
83
|
-
orig_name: "
|
|
83
|
+
path: lion,
|
|
84
|
+
url: lion,
|
|
85
|
+
orig_name: "lion.jpg"
|
|
84
86
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
constraints: null
|
|
88
|
-
}
|
|
87
|
+
buttons: [],
|
|
88
|
+
webcam_options: { mirror: true, constraints: null }
|
|
89
89
|
}}
|
|
90
|
-
|
|
90
|
+
>
|
|
91
|
+
{#snippet template(args)}
|
|
92
|
+
<div
|
|
93
|
+
class="image-container"
|
|
94
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
95
|
+
>
|
|
96
|
+
<StaticImage {...wrapProps(args)} />
|
|
97
|
+
</div>
|
|
98
|
+
{/snippet}
|
|
99
|
+
</Story>
|
|
91
100
|
|
|
92
101
|
<Story
|
|
93
102
|
name="static with a vertically long image and a fixed height"
|
|
94
103
|
args={{
|
|
95
104
|
value: {
|
|
96
|
-
path:
|
|
97
|
-
url:
|
|
98
|
-
orig_name: "
|
|
105
|
+
path: lion,
|
|
106
|
+
url: lion,
|
|
107
|
+
orig_name: "lion.jpg"
|
|
99
108
|
},
|
|
100
109
|
height: "500px",
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
constraints: null
|
|
104
|
-
}
|
|
110
|
+
buttons: [],
|
|
111
|
+
webcam_options: { mirror: true, constraints: null }
|
|
105
112
|
}}
|
|
106
|
-
|
|
113
|
+
>
|
|
114
|
+
{#snippet template(args)}
|
|
115
|
+
<div
|
|
116
|
+
class="image-container"
|
|
117
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
118
|
+
>
|
|
119
|
+
<StaticImage {...wrapProps(args)} />
|
|
120
|
+
</div>
|
|
121
|
+
{/snippet}
|
|
122
|
+
</Story>
|
|
107
123
|
|
|
108
124
|
<Story
|
|
109
125
|
name="static with a small image and a fixed height"
|
|
110
126
|
args={{
|
|
111
127
|
value: {
|
|
112
|
-
path:
|
|
113
|
-
url:
|
|
114
|
-
orig_name: "
|
|
128
|
+
path: cheetah,
|
|
129
|
+
url: cheetah,
|
|
130
|
+
orig_name: "cheetah.jpg"
|
|
115
131
|
},
|
|
116
132
|
height: "500px",
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
constraints: null
|
|
120
|
-
}
|
|
133
|
+
buttons: [],
|
|
134
|
+
webcam_options: { mirror: true, constraints: null }
|
|
121
135
|
}}
|
|
122
|
-
|
|
136
|
+
>
|
|
137
|
+
{#snippet template(args)}
|
|
138
|
+
<div
|
|
139
|
+
class="image-container"
|
|
140
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
141
|
+
>
|
|
142
|
+
<StaticImage {...wrapProps(args)} />
|
|
143
|
+
</div>
|
|
144
|
+
{/snippet}
|
|
145
|
+
</Story>
|
|
123
146
|
|
|
124
147
|
<Story
|
|
125
148
|
name="interactive with upload, clipboard, and webcam"
|
|
126
149
|
args={{
|
|
127
150
|
sources: ["upload", "clipboard", "webcam"],
|
|
128
151
|
value: {
|
|
129
|
-
path:
|
|
130
|
-
url:
|
|
152
|
+
path: cheetah,
|
|
153
|
+
url: cheetah,
|
|
131
154
|
orig_name: "cheetah.jpg"
|
|
132
155
|
},
|
|
133
156
|
show_label: false,
|
|
134
|
-
show_download_button: false,
|
|
135
157
|
interactive: true,
|
|
136
158
|
placeholder: md,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
constraints: null
|
|
140
|
-
}
|
|
159
|
+
buttons: [],
|
|
160
|
+
webcam_options: { mirror: true, constraints: null }
|
|
141
161
|
}}
|
|
162
|
+
parameters={{ chromatic: { disableSnapshot: true } }}
|
|
142
163
|
play={async ({ canvasElement }) => {
|
|
143
164
|
const canvas = within(canvasElement);
|
|
144
|
-
|
|
145
165
|
const webcamButton = await canvas.findByLabelText("Capture from camera");
|
|
146
166
|
userEvent.click(webcamButton);
|
|
147
|
-
|
|
148
167
|
userEvent.click(await canvas.findByTitle("grant webcam access"));
|
|
149
168
|
userEvent.click(await canvas.findByLabelText("Upload file"));
|
|
150
169
|
userEvent.click(await canvas.findByLabelText("Paste from clipboard"));
|
|
151
170
|
}}
|
|
152
|
-
|
|
171
|
+
>
|
|
172
|
+
{#snippet template(args)}
|
|
173
|
+
<div
|
|
174
|
+
class="image-container"
|
|
175
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
176
|
+
>
|
|
177
|
+
<StaticImage {...wrapProps(args)} />
|
|
178
|
+
</div>
|
|
179
|
+
{/snippet}
|
|
180
|
+
</Story>
|
|
153
181
|
|
|
154
182
|
<Story
|
|
155
183
|
name="interactive with webcam"
|
|
156
184
|
args={{
|
|
157
185
|
sources: ["webcam"],
|
|
158
|
-
show_download_button: true,
|
|
159
186
|
interactive: true,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
constraints: null
|
|
163
|
-
}
|
|
187
|
+
buttons: ["download"],
|
|
188
|
+
webcam_options: { mirror: true, constraints: null }
|
|
164
189
|
}}
|
|
165
|
-
|
|
190
|
+
>
|
|
191
|
+
{#snippet template(args)}
|
|
192
|
+
<div
|
|
193
|
+
class="image-container"
|
|
194
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
195
|
+
>
|
|
196
|
+
<StaticImage {...wrapProps(args)} />
|
|
197
|
+
</div>
|
|
198
|
+
{/snippet}
|
|
199
|
+
</Story>
|
|
166
200
|
|
|
167
201
|
<Story
|
|
168
202
|
name="interactive with clipboard"
|
|
169
203
|
args={{
|
|
170
204
|
sources: ["clipboard"],
|
|
171
|
-
|
|
172
|
-
|
|
205
|
+
interactive: true,
|
|
206
|
+
buttons: ["download"]
|
|
173
207
|
}}
|
|
174
|
-
|
|
208
|
+
>
|
|
209
|
+
{#snippet template(args)}
|
|
210
|
+
<div
|
|
211
|
+
class="image-container"
|
|
212
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
213
|
+
>
|
|
214
|
+
<StaticImage {...wrapProps(args)} />
|
|
215
|
+
</div>
|
|
216
|
+
{/snippet}
|
|
217
|
+
</Story>
|
|
175
218
|
|
|
176
219
|
<Story
|
|
177
220
|
name="interactive webcam with streaming"
|
|
178
221
|
args={{
|
|
179
222
|
sources: ["webcam"],
|
|
180
|
-
show_download_button: true,
|
|
181
223
|
interactive: true,
|
|
182
224
|
value: {
|
|
183
|
-
path:
|
|
184
|
-
url:
|
|
225
|
+
path: cheetah,
|
|
226
|
+
url: cheetah,
|
|
185
227
|
orig_name: "cheetah.jpg"
|
|
186
228
|
},
|
|
187
229
|
streaming: true,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
constraints: null
|
|
191
|
-
}
|
|
230
|
+
buttons: ["download"],
|
|
231
|
+
webcam_options: { mirror: true, constraints: null }
|
|
192
232
|
}}
|
|
193
|
-
|
|
233
|
+
>
|
|
234
|
+
{#snippet template(args)}
|
|
235
|
+
<div
|
|
236
|
+
class="image-container"
|
|
237
|
+
style="width: 300px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
238
|
+
>
|
|
239
|
+
<StaticImage {...wrapProps(args)} />
|
|
240
|
+
</div>
|
|
241
|
+
{/snippet}
|
|
242
|
+
</Story>
|
package/Image.test.ts
CHANGED
|
@@ -29,10 +29,12 @@ describe("Image", () => {
|
|
|
29
29
|
window.HTMLMediaElement.prototype.play = vi.fn();
|
|
30
30
|
window.HTMLMediaElement.prototype.pause = vi.fn();
|
|
31
31
|
});
|
|
32
|
-
beforeEach(
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await setupi18n();
|
|
34
|
+
});
|
|
33
35
|
afterEach(() => cleanup());
|
|
34
36
|
|
|
35
|
-
test("image change event trigger fires when value is changed and only fires once", async () => {
|
|
37
|
+
test.skip("image change event trigger fires when value is changed and only fires once", async () => {
|
|
36
38
|
const { component, listen } = await render(Image, {
|
|
37
39
|
show_label: true,
|
|
38
40
|
loading_status,
|
|
@@ -50,7 +52,8 @@ describe("Image", () => {
|
|
|
50
52
|
// brush_color: "#000000",
|
|
51
53
|
// brush_radius: 5,
|
|
52
54
|
// mask_opacity: 0.5,
|
|
53
|
-
interactive: true
|
|
55
|
+
interactive: true,
|
|
56
|
+
buttons: ["download", "share", "fullscreen"]
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
const mock = listen("change");
|
|
@@ -1,28 +1,37 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
3
|
import Image from "./Example.svelte";
|
|
4
|
-
</script>
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const cheetah = "/cheetah.jpg";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: "Components/Image/Example",
|
|
9
|
+
component: Image
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
11
12
|
|
|
12
13
|
<Story
|
|
13
14
|
name="Image file"
|
|
14
15
|
args={{
|
|
15
16
|
value: {
|
|
16
|
-
path:
|
|
17
|
-
url:
|
|
17
|
+
path: cheetah,
|
|
18
|
+
url: cheetah,
|
|
18
19
|
orig_name: "cheetah.jpg"
|
|
19
20
|
}
|
|
20
21
|
}}
|
|
21
|
-
|
|
22
|
+
>
|
|
23
|
+
{#snippet template(args)}
|
|
24
|
+
<Image {...args} />
|
|
25
|
+
{/snippet}
|
|
26
|
+
</Story>
|
|
22
27
|
|
|
23
28
|
<Story
|
|
24
29
|
name="Null"
|
|
25
30
|
args={{
|
|
26
31
|
value: null
|
|
27
32
|
}}
|
|
28
|
-
|
|
33
|
+
>
|
|
34
|
+
{#snippet template(args)}
|
|
35
|
+
<Image {...args} />
|
|
36
|
+
{/snippet}
|
|
37
|
+
</Story>
|
package/Index.svelte
CHANGED
|
@@ -119,6 +119,9 @@
|
|
|
119
119
|
selectable={gradio.props._selectable}
|
|
120
120
|
i18n={gradio.i18n}
|
|
121
121
|
buttons={gradio.props.buttons}
|
|
122
|
+
on_custom_button_click={(id) => {
|
|
123
|
+
gradio.dispatch("custom_button_click", { id });
|
|
124
|
+
}}
|
|
122
125
|
/>
|
|
123
126
|
</Block>
|
|
124
127
|
{:else}
|
|
@@ -162,7 +165,9 @@
|
|
|
162
165
|
{fullscreen}
|
|
163
166
|
show_fullscreen_button={gradio.props.buttons === null
|
|
164
167
|
? true
|
|
165
|
-
: gradio.props.buttons.
|
|
168
|
+
: gradio.props.buttons.some(
|
|
169
|
+
(btn) => typeof btn === "string" && btn === "fullscreen"
|
|
170
|
+
)}
|
|
166
171
|
on:edit={() => gradio.dispatch("edit")}
|
|
167
172
|
on:clear={() => {
|
|
168
173
|
fullscreen = false;
|
|
@@ -180,7 +185,7 @@
|
|
|
180
185
|
}}
|
|
181
186
|
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
|
182
187
|
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
|
183
|
-
|
|
188
|
+
onerror={(detail) => {
|
|
184
189
|
gradio.shared.loading_status.status = "error";
|
|
185
190
|
gradio.dispatch("error", detail);
|
|
186
191
|
}}
|
package/dist/Index.svelte
CHANGED
|
@@ -119,6 +119,9 @@
|
|
|
119
119
|
selectable={gradio.props._selectable}
|
|
120
120
|
i18n={gradio.i18n}
|
|
121
121
|
buttons={gradio.props.buttons}
|
|
122
|
+
on_custom_button_click={(id) => {
|
|
123
|
+
gradio.dispatch("custom_button_click", { id });
|
|
124
|
+
}}
|
|
122
125
|
/>
|
|
123
126
|
</Block>
|
|
124
127
|
{:else}
|
|
@@ -162,7 +165,9 @@
|
|
|
162
165
|
{fullscreen}
|
|
163
166
|
show_fullscreen_button={gradio.props.buttons === null
|
|
164
167
|
? true
|
|
165
|
-
: gradio.props.buttons.
|
|
168
|
+
: gradio.props.buttons.some(
|
|
169
|
+
(btn) => typeof btn === "string" && btn === "fullscreen"
|
|
170
|
+
)}
|
|
166
171
|
on:edit={() => gradio.dispatch("edit")}
|
|
167
172
|
on:clear={() => {
|
|
168
173
|
fullscreen = false;
|
|
@@ -180,7 +185,7 @@
|
|
|
180
185
|
}}
|
|
181
186
|
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
|
182
187
|
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
|
183
|
-
|
|
188
|
+
onerror={(detail) => {
|
|
184
189
|
gradio.shared.loading_status.status = "error";
|
|
185
190
|
gradio.dispatch("error", detail);
|
|
186
191
|
}}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
FullscreenButton,
|
|
12
12
|
DownloadLink
|
|
13
13
|
} from "@gradio/atoms";
|
|
14
|
+
import type { CustomButton as CustomButtonType } from "@gradio/utils";
|
|
14
15
|
import { Download, Image as ImageIcon } from "@gradio/icons";
|
|
15
16
|
import { get_coordinates_of_clicked_image } from "./utils";
|
|
16
17
|
import Image from "./Image.svelte";
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
export let value: null | FileData;
|
|
22
23
|
export let label: string | undefined = undefined;
|
|
23
24
|
export let show_label: boolean;
|
|
24
|
-
export let buttons: string
|
|
25
|
+
export let buttons: (string | CustomButtonType)[] = [];
|
|
26
|
+
export let on_custom_button_click: ((id: number) => void) | null = null;
|
|
25
27
|
export let selectable = false;
|
|
26
28
|
export let i18n: I18nFormatter;
|
|
27
29
|
export let display_icon_button_wrapper_top_corner = false;
|
|
@@ -56,17 +58,18 @@
|
|
|
56
58
|
<IconButtonWrapper
|
|
57
59
|
display_top_corner={display_icon_button_wrapper_top_corner}
|
|
58
60
|
show_background={show_button_background}
|
|
61
|
+
{buttons}
|
|
62
|
+
{on_custom_button_click}
|
|
59
63
|
>
|
|
60
|
-
{#if buttons ===
|
|
64
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "fullscreen")}
|
|
61
65
|
<FullscreenButton {fullscreen} on:fullscreen />
|
|
62
66
|
{/if}
|
|
63
|
-
|
|
64
|
-
{#if buttons === null ? true : buttons.includes("download")}
|
|
67
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "download")}
|
|
65
68
|
<DownloadLink href={value.url} download={value.orig_name || "image"}>
|
|
66
69
|
<IconButton Icon={Download} label={i18n("common.download")} />
|
|
67
70
|
</DownloadLink>
|
|
68
71
|
{/if}
|
|
69
|
-
{#if buttons ===
|
|
72
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
|
|
70
73
|
<ShareButton
|
|
71
74
|
{i18n}
|
|
72
75
|
on:share
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SelectData } from "@gradio/utils";
|
|
2
|
+
import type { CustomButton as CustomButtonType } from "@gradio/utils";
|
|
2
3
|
import type { I18nFormatter } from "@gradio/utils";
|
|
3
4
|
import type { FileData } from "@gradio/client";
|
|
4
5
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
@@ -18,7 +19,8 @@ declare const ImagePreview: $$__sveltets_2_IsomorphicComponent<{
|
|
|
18
19
|
value: null | FileData;
|
|
19
20
|
label?: string | undefined;
|
|
20
21
|
show_label: boolean;
|
|
21
|
-
buttons?: string
|
|
22
|
+
buttons?: (string | CustomButtonType)[];
|
|
23
|
+
on_custom_button_click?: ((id: number) => void) | null;
|
|
22
24
|
selectable?: boolean;
|
|
23
25
|
i18n: I18nFormatter;
|
|
24
26
|
display_icon_button_wrapper_top_corner?: boolean;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
export let show_fullscreen_button = true;
|
|
39
39
|
export let stream_state: "open" | "waiting" | "closed" = "closed";
|
|
40
40
|
export let upload_promise: Promise<any> | null = null;
|
|
41
|
+
export let onerror: ((error: string) => void) | undefined = undefined;
|
|
41
42
|
|
|
42
43
|
let upload_input: Upload;
|
|
43
44
|
export let uploading = false;
|
|
@@ -47,9 +48,7 @@
|
|
|
47
48
|
let files: FileData[] = [];
|
|
48
49
|
let upload_id: string;
|
|
49
50
|
|
|
50
|
-
async function handle_upload({
|
|
51
|
-
detail
|
|
52
|
-
}: CustomEvent<FileData>): Promise<void> {
|
|
51
|
+
async function handle_upload(detail: FileData): Promise<void> {
|
|
53
52
|
if (!streaming) {
|
|
54
53
|
if (detail.path?.toLowerCase().endsWith(".svg") && detail.url) {
|
|
55
54
|
const response = await fetch(detail.url);
|
|
@@ -82,7 +81,6 @@
|
|
|
82
81
|
img_blob: Blob | any,
|
|
83
82
|
event: "change" | "stream" | "upload"
|
|
84
83
|
): Promise<void> {
|
|
85
|
-
console.log("handle_save", { event, img_blob });
|
|
86
84
|
if (event === "stream") {
|
|
87
85
|
dispatch("stream", {
|
|
88
86
|
value: { url: img_blob } as Base64File,
|
|
@@ -104,7 +102,6 @@
|
|
|
104
102
|
];
|
|
105
103
|
pending = true;
|
|
106
104
|
const f = await upload_input.load_files([f_], upload_id);
|
|
107
|
-
console.log("uploaded file", f);
|
|
108
105
|
if (event === "change" || event === "upload") {
|
|
109
106
|
value = f?.[0] || null;
|
|
110
107
|
await tick();
|
|
@@ -192,7 +189,7 @@
|
|
|
192
189
|
<IconButton
|
|
193
190
|
Icon={Clear}
|
|
194
191
|
label="Remove Image"
|
|
195
|
-
|
|
192
|
+
onclick={handle_remove_image_click}
|
|
196
193
|
/>
|
|
197
194
|
{/if}
|
|
198
195
|
</IconButtonWrapper>
|
|
@@ -211,8 +208,8 @@
|
|
|
211
208
|
bind:uploading
|
|
212
209
|
bind:dragging
|
|
213
210
|
filetype={active_source === "clipboard" ? "clipboard" : "image/*"}
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
onload={handle_upload}
|
|
212
|
+
{onerror}
|
|
216
213
|
{root}
|
|
217
214
|
{max_file_size}
|
|
218
215
|
disable_click={!sources.includes("upload") || value !== null}
|
|
@@ -38,6 +38,7 @@ declare const ImageUploader: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_P
|
|
|
38
38
|
show_fullscreen_button?: boolean;
|
|
39
39
|
stream_state?: "open" | "waiting" | "closed";
|
|
40
40
|
upload_promise?: Promise<any> | null;
|
|
41
|
+
onerror?: ((error: string) => void) | undefined;
|
|
41
42
|
uploading?: boolean;
|
|
42
43
|
active_source?: "upload" | "clipboard" | "microphone" | "webcam" | null;
|
|
43
44
|
fullscreen?: boolean;
|
|
@@ -45,8 +46,8 @@ declare const ImageUploader: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_P
|
|
|
45
46
|
}, {
|
|
46
47
|
default: {};
|
|
47
48
|
}>, {
|
|
48
|
-
fullscreen:
|
|
49
|
-
error: CustomEvent<
|
|
49
|
+
fullscreen: any;
|
|
50
|
+
error: CustomEvent<string>;
|
|
50
51
|
drag: CustomEvent<any>;
|
|
51
52
|
close_stream: CustomEvent<undefined>;
|
|
52
53
|
change?: CustomEvent<undefined> | undefined;
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
2
2
|
import type { FileData } from "@gradio/client";
|
|
3
|
+
import type { CustomButton } from "@gradio/utils";
|
|
3
4
|
export interface Base64File {
|
|
4
5
|
url: string;
|
|
5
6
|
alt_text: string;
|
|
@@ -15,7 +16,7 @@ export interface ImageProps {
|
|
|
15
16
|
width: number;
|
|
16
17
|
webcam_options: WebcamOptions;
|
|
17
18
|
value: FileData | null;
|
|
18
|
-
buttons: string[];
|
|
19
|
+
buttons: (string | CustomButton)[];
|
|
19
20
|
pending: boolean;
|
|
20
21
|
streaming: boolean;
|
|
21
22
|
stream_every: number;
|
|
@@ -35,4 +36,7 @@ export interface ImageEvents {
|
|
|
35
36
|
error: any;
|
|
36
37
|
close_stream: void;
|
|
37
38
|
edit: void;
|
|
39
|
+
custom_button_click: {
|
|
40
|
+
id: number;
|
|
41
|
+
};
|
|
38
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/image",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
"cropperjs": "^2.0.1",
|
|
11
11
|
"lazy-brush": "^2.0.2",
|
|
12
12
|
"resize-observer-polyfill": "^1.5.1",
|
|
13
|
-
"@gradio/atoms": "^0.
|
|
14
|
-
"@gradio/client": "^2.0.
|
|
15
|
-
"@gradio/icons": "^0.15.
|
|
16
|
-
"@gradio/
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
13
|
+
"@gradio/atoms": "^0.20.1",
|
|
14
|
+
"@gradio/client": "^2.0.3",
|
|
15
|
+
"@gradio/icons": "^0.15.1",
|
|
16
|
+
"@gradio/statustracker": "^0.12.2",
|
|
17
|
+
"@gradio/upload": "^0.17.4",
|
|
18
|
+
"@gradio/utils": "^0.11.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@gradio/preview": "^0.15.
|
|
21
|
+
"@gradio/preview": "^0.15.2"
|
|
22
22
|
},
|
|
23
23
|
"main_changeset": true,
|
|
24
24
|
"main": "./Index.svelte",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"svelte": "^5.
|
|
49
|
+
"svelte": "^5.48.0"
|
|
50
50
|
},
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
FullscreenButton,
|
|
12
12
|
DownloadLink
|
|
13
13
|
} from "@gradio/atoms";
|
|
14
|
+
import type { CustomButton as CustomButtonType } from "@gradio/utils";
|
|
14
15
|
import { Download, Image as ImageIcon } from "@gradio/icons";
|
|
15
16
|
import { get_coordinates_of_clicked_image } from "./utils";
|
|
16
17
|
import Image from "./Image.svelte";
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
export let value: null | FileData;
|
|
22
23
|
export let label: string | undefined = undefined;
|
|
23
24
|
export let show_label: boolean;
|
|
24
|
-
export let buttons: string
|
|
25
|
+
export let buttons: (string | CustomButtonType)[] = [];
|
|
26
|
+
export let on_custom_button_click: ((id: number) => void) | null = null;
|
|
25
27
|
export let selectable = false;
|
|
26
28
|
export let i18n: I18nFormatter;
|
|
27
29
|
export let display_icon_button_wrapper_top_corner = false;
|
|
@@ -56,17 +58,18 @@
|
|
|
56
58
|
<IconButtonWrapper
|
|
57
59
|
display_top_corner={display_icon_button_wrapper_top_corner}
|
|
58
60
|
show_background={show_button_background}
|
|
61
|
+
{buttons}
|
|
62
|
+
{on_custom_button_click}
|
|
59
63
|
>
|
|
60
|
-
{#if buttons ===
|
|
64
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "fullscreen")}
|
|
61
65
|
<FullscreenButton {fullscreen} on:fullscreen />
|
|
62
66
|
{/if}
|
|
63
|
-
|
|
64
|
-
{#if buttons === null ? true : buttons.includes("download")}
|
|
67
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "download")}
|
|
65
68
|
<DownloadLink href={value.url} download={value.orig_name || "image"}>
|
|
66
69
|
<IconButton Icon={Download} label={i18n("common.download")} />
|
|
67
70
|
</DownloadLink>
|
|
68
71
|
{/if}
|
|
69
|
-
{#if buttons ===
|
|
72
|
+
{#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
|
|
70
73
|
<ShareButton
|
|
71
74
|
{i18n}
|
|
72
75
|
on:share
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
export let show_fullscreen_button = true;
|
|
39
39
|
export let stream_state: "open" | "waiting" | "closed" = "closed";
|
|
40
40
|
export let upload_promise: Promise<any> | null = null;
|
|
41
|
+
export let onerror: ((error: string) => void) | undefined = undefined;
|
|
41
42
|
|
|
42
43
|
let upload_input: Upload;
|
|
43
44
|
export let uploading = false;
|
|
@@ -47,9 +48,7 @@
|
|
|
47
48
|
let files: FileData[] = [];
|
|
48
49
|
let upload_id: string;
|
|
49
50
|
|
|
50
|
-
async function handle_upload({
|
|
51
|
-
detail
|
|
52
|
-
}: CustomEvent<FileData>): Promise<void> {
|
|
51
|
+
async function handle_upload(detail: FileData): Promise<void> {
|
|
53
52
|
if (!streaming) {
|
|
54
53
|
if (detail.path?.toLowerCase().endsWith(".svg") && detail.url) {
|
|
55
54
|
const response = await fetch(detail.url);
|
|
@@ -82,7 +81,6 @@
|
|
|
82
81
|
img_blob: Blob | any,
|
|
83
82
|
event: "change" | "stream" | "upload"
|
|
84
83
|
): Promise<void> {
|
|
85
|
-
console.log("handle_save", { event, img_blob });
|
|
86
84
|
if (event === "stream") {
|
|
87
85
|
dispatch("stream", {
|
|
88
86
|
value: { url: img_blob } as Base64File,
|
|
@@ -104,7 +102,6 @@
|
|
|
104
102
|
];
|
|
105
103
|
pending = true;
|
|
106
104
|
const f = await upload_input.load_files([f_], upload_id);
|
|
107
|
-
console.log("uploaded file", f);
|
|
108
105
|
if (event === "change" || event === "upload") {
|
|
109
106
|
value = f?.[0] || null;
|
|
110
107
|
await tick();
|
|
@@ -192,7 +189,7 @@
|
|
|
192
189
|
<IconButton
|
|
193
190
|
Icon={Clear}
|
|
194
191
|
label="Remove Image"
|
|
195
|
-
|
|
192
|
+
onclick={handle_remove_image_click}
|
|
196
193
|
/>
|
|
197
194
|
{/if}
|
|
198
195
|
</IconButtonWrapper>
|
|
@@ -211,8 +208,8 @@
|
|
|
211
208
|
bind:uploading
|
|
212
209
|
bind:dragging
|
|
213
210
|
filetype={active_source === "clipboard" ? "clipboard" : "image/*"}
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
onload={handle_upload}
|
|
212
|
+
{onerror}
|
|
216
213
|
{root}
|
|
217
214
|
{max_file_size}
|
|
218
215
|
disable_click={!sources.includes("upload") || value !== null}
|
package/shared/Webcam.svelte
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
set_available_devices,
|
|
6
6
|
set_local_stream
|
|
7
7
|
} from "./stream_utils";
|
|
8
|
-
import * as stream_utils from "./stream_utils";
|
|
9
8
|
|
|
10
9
|
let test_device: MediaDeviceInfo = {
|
|
11
10
|
deviceId: "test-device",
|
|
@@ -31,7 +30,41 @@ const mock_getUserMedia = vi.fn(async () => {
|
|
|
31
30
|
});
|
|
32
31
|
});
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
class MockMediaStream extends EventTarget {
|
|
34
|
+
id: string;
|
|
35
|
+
active: boolean;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
this.id = "mock-stream-id";
|
|
40
|
+
this.active = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getTracks(): MediaStreamTrack[] {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getAudioTracks(): MediaStreamTrack[] {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getVideoTracks(): MediaStreamTrack[] {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addTrack(): void {}
|
|
56
|
+
removeTrack(): void {}
|
|
57
|
+
getTrackById(): MediaStreamTrack | null {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clone(): MediaStream {
|
|
62
|
+
return this as unknown as MediaStream;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// @ts-ignore - Override global MediaStream for testing
|
|
67
|
+
window.MediaStream = MockMediaStream as any;
|
|
35
68
|
|
|
36
69
|
Object.defineProperty(global.navigator, "mediaDevices", {
|
|
37
70
|
value: {
|
|
@@ -47,10 +80,10 @@ describe("stream_utils", () => {
|
|
|
47
80
|
});
|
|
48
81
|
|
|
49
82
|
test("set_local_stream should set the local stream to the video source", () => {
|
|
50
|
-
const mock_stream =
|
|
83
|
+
const mock_stream = new MockMediaStream() as unknown as MediaStream;
|
|
51
84
|
|
|
52
85
|
const mock_video_source = {
|
|
53
|
-
srcObject: null,
|
|
86
|
+
srcObject: null as MediaStream | null,
|
|
54
87
|
muted: false,
|
|
55
88
|
play: vi.fn()
|
|
56
89
|
};
|
|
@@ -64,28 +97,27 @@ describe("stream_utils", () => {
|
|
|
64
97
|
});
|
|
65
98
|
|
|
66
99
|
test("get_video_stream requests user media with the correct constraints and sets the local stream", async () => {
|
|
67
|
-
const mock_video_source =
|
|
68
|
-
|
|
100
|
+
const mock_video_source = {
|
|
101
|
+
srcObject: null as MediaStream | null,
|
|
102
|
+
muted: false,
|
|
103
|
+
play: vi.fn().mockResolvedValue(undefined)
|
|
104
|
+
} as unknown as HTMLVideoElement;
|
|
105
|
+
const mock_stream = new MockMediaStream() as unknown as MediaStream;
|
|
69
106
|
|
|
70
107
|
global.navigator.mediaDevices.getUserMedia = vi
|
|
71
108
|
.fn()
|
|
72
109
|
.mockResolvedValue(mock_stream);
|
|
73
110
|
|
|
74
|
-
await get_video_stream(true, mock_video_source);
|
|
111
|
+
await get_video_stream(true, mock_video_source, null);
|
|
75
112
|
|
|
76
113
|
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({
|
|
77
114
|
video: { width: { ideal: 1920 }, height: { ideal: 1440 } },
|
|
78
115
|
audio: true
|
|
79
116
|
});
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expect(spy_set_local_stream).toHaveBeenCalledWith(
|
|
85
|
-
mock_stream,
|
|
86
|
-
mock_video_source
|
|
87
|
-
);
|
|
88
|
-
spy_set_local_stream.mockRestore();
|
|
118
|
+
expect(mock_video_source.srcObject).toBe(mock_stream);
|
|
119
|
+
expect(mock_video_source.muted).toBe(true);
|
|
120
|
+
expect(mock_video_source.play).toHaveBeenCalled();
|
|
89
121
|
});
|
|
90
122
|
|
|
91
123
|
test("set_available_devices should return only video input devices", () => {
|
package/shared/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
2
2
|
import type { FileData } from "@gradio/client";
|
|
3
|
+
import type { CustomButton } from "@gradio/utils";
|
|
3
4
|
|
|
4
5
|
export interface Base64File {
|
|
5
6
|
url: string;
|
|
@@ -18,7 +19,7 @@ export interface ImageProps {
|
|
|
18
19
|
width: number;
|
|
19
20
|
webcam_options: WebcamOptions;
|
|
20
21
|
value: FileData | null;
|
|
21
|
-
buttons: string[];
|
|
22
|
+
buttons: (string | CustomButton)[];
|
|
22
23
|
pending: boolean;
|
|
23
24
|
streaming: boolean;
|
|
24
25
|
stream_every: number;
|
|
@@ -39,4 +40,5 @@ export interface ImageEvents {
|
|
|
39
40
|
error: any;
|
|
40
41
|
close_stream: void;
|
|
41
42
|
edit: void;
|
|
43
|
+
custom_button_click: { id: number };
|
|
42
44
|
}
|