@gradio/gallery 0.16.2 → 0.17.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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @gradio/gallery
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Fixes
6
+
7
+ - [#12865](https://github.com/gradio-app/gradio/pull/12865) [`db7ab39`](https://github.com/gradio-app/gradio/commit/db7ab39c7e6ea3d1a2f0ce1991b5dbc0483b1e27) - Fix Gallery fullscreen button not working in preview mode. Thanks @veeceey!
8
+ - [#12866](https://github.com/gradio-app/gradio/pull/12866) [`9810396`](https://github.com/gradio-app/gradio/commit/981039647a4212c649a33dc4b56a4714980519eb) - Fix Gallery preview=True parameter not working on initial load. Thanks @veeceey!
9
+
10
+ ### Dependency updates
11
+
12
+ - @gradio/utils@0.11.3
13
+ - @gradio/atoms@0.22.0
14
+ - @gradio/statustracker@0.12.4
15
+ - @gradio/upload@0.17.6
16
+ - @gradio/video@0.20.3
17
+ - @gradio/file@0.14.3
18
+ - @gradio/image@0.25.3
19
+
20
+ ## 0.17.0
21
+
22
+ ### Features
23
+
24
+ - [#12804](https://github.com/gradio-app/gradio/pull/12804) [`fc366b4`](https://github.com/gradio-app/gradio/commit/fc366b485325c900b0c2b85ba05b7d23a81a0dee) - Allow webcam uploads and clipboard paste for gallery. Thanks @freddyaboulton!
25
+
26
+ ### Dependency updates
27
+
28
+ - @gradio/atoms@0.21.0
29
+ - @gradio/client@2.0.4
30
+ - @gradio/statustracker@0.12.3
31
+ - @gradio/file@0.14.2
32
+ - @gradio/image@0.25.2
33
+ - @gradio/upload@0.17.5
34
+ - @gradio/video@0.20.2
35
+
3
36
  ## 0.16.2
4
37
 
5
38
  ### Fixes
@@ -154,13 +154,16 @@
154
154
  args={{
155
155
  label: "My Cheetah Gallery",
156
156
  show_label: true,
157
- buttons: ["fullscreen", "download", "share"]
157
+ buttons: ["fullscreen", "download", "share"],
158
+ sources: ["upload"]
158
159
  }}
159
160
  play={async ({ canvasElement }) => {
160
161
  const canvas = within(canvasElement);
161
- const image = canvas.getByLabelText("Thumbnail 1 of 8");
162
+ const image = await canvas.findByLabelText("Thumbnail 1 of 8");
162
163
  await userEvent.click(image);
163
- const expand_btn = canvas.getByRole("button", { name: "Fullscreen" });
164
+ const expand_btn = await canvas.findByRole("button", {
165
+ name: "Fullscreen"
166
+ });
164
167
  await userEvent.click(expand_btn);
165
168
  }}
166
169
  >
@@ -171,7 +174,12 @@
171
174
 
172
175
  <Story
173
176
  name="Gallery without label"
174
- args={{ label: "My Cheetah Gallery", show_label: false, buttons: [] }}
177
+ args={{
178
+ label: "My Cheetah Gallery",
179
+ show_label: false,
180
+ buttons: [],
181
+ sources: ["upload"]
182
+ }}
175
183
  >
176
184
  {#snippet template(args)}
177
185
  <Gallery {...wrapProps({ value: galleryValue, ...args })} />
@@ -185,7 +193,8 @@
185
193
  show_label: true,
186
194
  rows: 3,
187
195
  columns: 3,
188
- buttons: []
196
+ buttons: [],
197
+ sources: ["upload"]
189
198
  }}
190
199
  >
191
200
  {#snippet template(args)}
@@ -199,7 +208,8 @@
199
208
  label: "My Cheetah Gallery",
200
209
  show_label: true,
201
210
  columns: 4,
202
- buttons: []
211
+ buttons: [],
212
+ sources: ["upload"]
203
213
  }}
204
214
  >
205
215
  {#snippet template(args)}
@@ -209,7 +219,12 @@
209
219
 
210
220
  <Story
211
221
  name="Gallery with height=600"
212
- args={{ label: "My Cheetah Gallery", height: 600, buttons: [] }}
222
+ args={{
223
+ label: "My Cheetah Gallery",
224
+ height: 600,
225
+ buttons: [],
226
+ sources: ["upload"]
227
+ }}
213
228
  >
214
229
  {#snippet template(args)}
215
230
  <Gallery {...wrapProps({ value: galleryValue, ...args })} />
@@ -222,7 +237,8 @@
222
237
  label: "My Cheetah Gallery",
223
238
  show_label: true,
224
239
  allow_preview: false,
225
- buttons: []
240
+ buttons: [],
241
+ sources: ["upload"]
226
242
  }}
227
243
  >
228
244
  {#snippet template(args)}
@@ -236,7 +252,8 @@
236
252
  label: "My Cheetah Gallery",
237
253
  show_label: true,
238
254
  preview: true,
239
- buttons: []
255
+ buttons: [],
256
+ sources: ["upload"]
240
257
  }}
241
258
  >
242
259
  {#snippet template(args)}
@@ -250,7 +267,8 @@
250
267
  label: "My Cheetah Gallery",
251
268
  show_label: true,
252
269
  object_fit: "scale-down",
253
- buttons: []
270
+ buttons: [],
271
+ sources: ["upload"]
254
272
  }}
255
273
  >
256
274
  {#snippet template(args)}
@@ -264,7 +282,8 @@
264
282
  label: "My Cheetah Gallery",
265
283
  show_label: true,
266
284
  object_fit: "contain",
267
- buttons: []
285
+ buttons: [],
286
+ sources: ["upload"]
268
287
  }}
269
288
  >
270
289
  {#snippet template(args)}
@@ -278,7 +297,8 @@
278
297
  label: "My Cheetah Gallery",
279
298
  show_label: true,
280
299
  object_fit: "cover",
281
- buttons: []
300
+ buttons: [],
301
+ sources: ["upload"]
282
302
  }}
283
303
  >
284
304
  {#snippet template(args)}
@@ -292,7 +312,8 @@
292
312
  label: "My Cheetah Gallery",
293
313
  show_label: true,
294
314
  object_fit: "none",
295
- buttons: []
315
+ buttons: [],
316
+ sources: ["upload"]
296
317
  }}
297
318
  >
298
319
  {#snippet template(args)}
@@ -306,7 +327,8 @@
306
327
  label: "My Cheetah Gallery",
307
328
  show_label: true,
308
329
  object_fit: "fill",
309
- buttons: []
330
+ buttons: [],
331
+ sources: ["upload"]
310
332
  }}
311
333
  >
312
334
  {#snippet template(args)}
@@ -316,7 +338,12 @@
316
338
 
317
339
  <Story
318
340
  name="Gallery with share button"
319
- args={{ label: "My Cheetah Gallery", show_label: true, buttons: ["share"] }}
341
+ args={{
342
+ label: "My Cheetah Gallery",
343
+ show_label: true,
344
+ buttons: ["share"],
345
+ sources: ["upload"]
346
+ }}
320
347
  >
321
348
  {#snippet template(args)}
322
349
  <Gallery {...wrapProps({ value: galleryValue, ...args })} />
@@ -331,7 +358,8 @@
331
358
  rows: 2,
332
359
  columns: 2,
333
360
  height: 400,
334
- buttons: []
361
+ buttons: [],
362
+ sources: ["upload"]
335
363
  }}
336
364
  >
337
365
  {#snippet template(args)}
@@ -347,7 +375,8 @@
347
375
  rows: 2,
348
376
  columns: 2,
349
377
  height: 200,
350
- buttons: []
378
+ buttons: [],
379
+ sources: ["upload"]
351
380
  }}
352
381
  >
353
382
  {#snippet template(args)}
@@ -361,7 +390,8 @@
361
390
  label: "My Cheetah Gallery",
362
391
  rows: 2,
363
392
  height: 400,
364
- buttons: ["download"]
393
+ buttons: ["download"],
394
+ sources: ["upload"]
365
395
  }}
366
396
  >
367
397
  {#snippet template(args)}
@@ -375,7 +405,8 @@
375
405
  label: "My Cheetah Gallery",
376
406
  columns: 10,
377
407
  buttons: ["download"],
378
- fit_columns: false
408
+ fit_columns: false,
409
+ sources: ["upload"]
379
410
  }}
380
411
  >
381
412
  {#snippet template(args)}
@@ -389,7 +420,8 @@
389
420
  label: "My Cheetah Gallery",
390
421
  columns: 10,
391
422
  buttons: ["download"],
392
- fit_columns: true
423
+ fit_columns: true,
424
+ sources: ["upload"]
393
425
  }}
394
426
  >
395
427
  {#snippet template(args)}
package/Index.svelte CHANGED
@@ -6,12 +6,14 @@
6
6
  <script lang="ts">
7
7
  import { tick } from "svelte";
8
8
  import type { FileData } from "@gradio/client";
9
- import { Block, UploadText } from "@gradio/atoms";
9
+ import { Block, UploadText, SelectSource } from "@gradio/atoms";
10
10
  import Gallery from "./shared/Gallery.svelte";
11
11
  import { StatusTracker } from "@gradio/statustracker";
12
12
  import { Gradio } from "@gradio/utils";
13
13
  import { BaseFileUpload } from "@gradio/file";
14
+ import { Webcam } from "@gradio/image";
14
15
  import type { GalleryProps, GalleryEvents, GalleryData } from "./types";
16
+ import { handle_save } from "./shared/utils";
15
17
 
16
18
  let upload_promise = $state<Promise<(FileData | null)[]>>();
17
19
 
@@ -29,15 +31,12 @@
29
31
 
30
32
  const props = $props();
31
33
  const gradio = new GalleryGradio<GalleryEvents, GalleryProps>(props, {
32
- selected_index: null
34
+ selected_index: null,
35
+ file_types: ["image", "video"]
33
36
  });
34
37
 
35
38
  let fullscreen = $state(false);
36
39
 
37
- let no_value = $derived(
38
- gradio.props.value === null ? true : gradio.props.value.length === 0
39
- );
40
-
41
40
  function handle_delete(
42
41
  event: CustomEvent<{ file: FileData; index: number }>
43
42
  ): void {
@@ -71,6 +70,82 @@
71
70
  : { image: x, caption: null }
72
71
  );
73
72
  }
73
+
74
+ let upload_input: BaseFileUpload;
75
+
76
+ let active_source = $state<
77
+ "upload" | "webcam" | "webcam-video" | "clipboard" | null
78
+ >(gradio.props.sources ? gradio.props.sources[0] : "upload");
79
+
80
+ let no_value = $derived(
81
+ gradio.props.value === null ? true : gradio.props.value.length === 0
82
+ );
83
+
84
+ let sources = $derived.by(() => {
85
+ if (
86
+ gradio.props.file_types?.includes("video") &&
87
+ gradio.props.sources.includes("webcam")
88
+ ) {
89
+ return gradio.props.sources.concat(["webcam-video"]);
90
+ } else {
91
+ return gradio.props.sources;
92
+ }
93
+ });
94
+
95
+ async function paste_clipboard(): Promise<void> {
96
+ navigator.clipboard.read().then(async (items) => {
97
+ let file: File | null = null;
98
+ for (let i = 0; i < items.length; i++) {
99
+ const type = items[i].types.find((t) =>
100
+ (gradio.props.file_types || ["image"]).some((ft) =>
101
+ t.startsWith(ft + "/")
102
+ )
103
+ );
104
+ if (type) {
105
+ const blob = await items[i].getType(type);
106
+ file = new File([blob], `clipboard.${type.replace("image/", "")}`);
107
+ break;
108
+ }
109
+ }
110
+ if (file) {
111
+ const f = await handle_save(
112
+ file,
113
+ (f) => gradio.shared.client.upload(f, gradio.shared.root),
114
+ "clipboard_upload"
115
+ );
116
+ const processed_files = await process_upload_files(f);
117
+ gradio.props.value?.push(...processed_files);
118
+ gradio.dispatch("change", gradio.props.value);
119
+ active_source = null;
120
+ } else {
121
+ gradio.dispatch("warning", "No image or video found in clipboard");
122
+ }
123
+ });
124
+ }
125
+
126
+ async function handle_select_source(
127
+ source: "upload" | "webcam" | "clipboard"
128
+ ): Promise<void> {
129
+ switch (source) {
130
+ case "clipboard":
131
+ await paste_clipboard();
132
+ break;
133
+ default:
134
+ break;
135
+ }
136
+ }
137
+
138
+ async function onsource_change(
139
+ source: "upload" | "webcam" | "webcam-video" | "clipboard"
140
+ ): Promise<void> {
141
+ await tick();
142
+ if (source === "clipboard") {
143
+ await paste_clipboard();
144
+ } else {
145
+ active_source = source;
146
+ no_value = true;
147
+ }
148
+ }
74
149
  </script>
75
150
 
76
151
  <Block
@@ -96,31 +171,96 @@
96
171
  gradio.dispatch("clear_status", gradio.shared.loading_status)}
97
172
  />
98
173
  {#if gradio.shared.interactive && no_value}
99
- <BaseFileUpload
100
- bind:upload_promise
101
- value={null}
102
- root={gradio.shared.root}
103
- label={gradio.shared.label}
104
- max_file_size={gradio.shared.max_file_size}
105
- file_count={"multiple"}
106
- file_types={gradio.props.file_types}
107
- i18n={gradio.i18n}
108
- upload={(...args) => gradio.shared.client.upload(...args)}
109
- stream_handler={(...args) => gradio.shared.client.stream(...args)}
110
- onupload={async (event_data) => {
111
- const files = Array.isArray(event_data) ? event_data : [event_data];
112
- gradio.props.value = await process_upload_files(files);
113
- gradio.dispatch("upload", gradio.props.value);
114
- gradio.dispatch("change", gradio.props.value);
115
- }}
116
- onerror={(event_data) => {
117
- gradio.shared.loading_status = gradio.shared.loading_status || {};
118
- gradio.shared.loading_status.status = "error";
119
- gradio.dispatch("error", event_data);
120
- }}
174
+ <div
175
+ class={!gradio.props.value ||
176
+ (active_source && active_source.includes("webcam"))
177
+ ? "hidden-upload-input"
178
+ : ""}
121
179
  >
122
- <UploadText i18n={gradio.i18n} type="gallery" />
123
- </BaseFileUpload>
180
+ <BaseFileUpload
181
+ bind:upload_promise
182
+ bind:this={upload_input}
183
+ value={null}
184
+ root={gradio.shared.root}
185
+ label={gradio.shared.label}
186
+ max_file_size={gradio.shared.max_file_size}
187
+ file_count={"multiple"}
188
+ file_types={gradio.props.file_types}
189
+ i18n={gradio.i18n}
190
+ upload={(...args) => gradio.shared.client.upload(...args)}
191
+ stream_handler={(...args) => gradio.shared.client.stream(...args)}
192
+ onupload={async (e) => {
193
+ const files = Array.isArray(e) ? e : [e];
194
+ gradio.props.value = await process_upload_files(files);
195
+ active_source = null;
196
+ gradio.dispatch("upload", gradio.props.value);
197
+ gradio.dispatch("change", gradio.props.value);
198
+ }}
199
+ onerror={({ detail }) => {
200
+ gradio.shared.loading_status = gradio.shared.loading_status || {};
201
+ gradio.shared.loading_status.status = "error";
202
+ gradio.dispatch("error", detail);
203
+ }}
204
+ >
205
+ <UploadText i18n={gradio.i18n} type="gallery" />
206
+ </BaseFileUpload>
207
+ </div>
208
+ {#if active_source === "webcam"}
209
+ <Webcam
210
+ root={gradio.shared.root}
211
+ value={null}
212
+ on:capture={async (e) => {
213
+ const f = await handle_save(
214
+ e.detail,
215
+ (f) => gradio.shared.client.upload(f, gradio.shared.root),
216
+ "webcam_upload"
217
+ );
218
+ const processed_files = await process_upload_files(f);
219
+ gradio.props.value?.push(...processed_files);
220
+ active_source = null;
221
+ gradio.dispatch("change", gradio.props.value);
222
+ }}
223
+ on:error
224
+ on:drag
225
+ on:close_stream
226
+ mirror_webcam={true}
227
+ streaming={false}
228
+ mode="image"
229
+ include_audio={false}
230
+ i18n={gradio.i18n}
231
+ upload={(...args) => gradio.shared.client.upload(...args)}
232
+ />
233
+ {:else if active_source === "webcam-video"}
234
+ <Webcam
235
+ root={gradio.shared.root}
236
+ value={null}
237
+ on:capture={async (e) => {
238
+ const f = { ...e.detail };
239
+ f.mime_type = "video/webm";
240
+ const processed_files = await process_upload_files([f]);
241
+ gradio.props.value?.push(...processed_files);
242
+ active_source = null;
243
+ gradio.dispatch("change", gradio.props.value);
244
+ }}
245
+ on:error
246
+ on:drag
247
+ on:close_stream
248
+ mirror_webcam={true}
249
+ streaming={false}
250
+ mode="video"
251
+ include_audio={false}
252
+ i18n={gradio.i18n}
253
+ upload={(...args) => gradio.shared.client.upload(...args)}
254
+ />
255
+ {/if}
256
+ {#if sources.length > 1 || sources.includes("clipboard")}
257
+ <SelectSource
258
+ {sources}
259
+ bind:active_source
260
+ handle_clear={() => gradio.dispatch("clear")}
261
+ handle_select={handle_select_source}
262
+ />
263
+ {/if}
124
264
  {:else}
125
265
  <Gallery
126
266
  onchange={() => gradio.dispatch("change")}
@@ -145,6 +285,8 @@
145
285
  gradio.dispatch("upload", new_value);
146
286
  gradio.dispatch("change", gradio.props.value);
147
287
  }}
288
+ {sources}
289
+ {onsource_change}
148
290
  label={gradio.shared.label}
149
291
  show_label={gradio.shared.show_label}
150
292
  columns={gradio.props.columns}
@@ -181,3 +323,9 @@
181
323
  />
182
324
  {/if}
183
325
  </Block>
326
+
327
+ <style>
328
+ .hidden-upload-input {
329
+ display: none;
330
+ }
331
+ </style>
package/dist/Index.svelte CHANGED
@@ -6,12 +6,14 @@
6
6
  <script lang="ts">
7
7
  import { tick } from "svelte";
8
8
  import type { FileData } from "@gradio/client";
9
- import { Block, UploadText } from "@gradio/atoms";
9
+ import { Block, UploadText, SelectSource } from "@gradio/atoms";
10
10
  import Gallery from "./shared/Gallery.svelte";
11
11
  import { StatusTracker } from "@gradio/statustracker";
12
12
  import { Gradio } from "@gradio/utils";
13
13
  import { BaseFileUpload } from "@gradio/file";
14
+ import { Webcam } from "@gradio/image";
14
15
  import type { GalleryProps, GalleryEvents, GalleryData } from "./types";
16
+ import { handle_save } from "./shared/utils";
15
17
 
16
18
  let upload_promise = $state<Promise<(FileData | null)[]>>();
17
19
 
@@ -29,15 +31,12 @@
29
31
 
30
32
  const props = $props();
31
33
  const gradio = new GalleryGradio<GalleryEvents, GalleryProps>(props, {
32
- selected_index: null
34
+ selected_index: null,
35
+ file_types: ["image", "video"]
33
36
  });
34
37
 
35
38
  let fullscreen = $state(false);
36
39
 
37
- let no_value = $derived(
38
- gradio.props.value === null ? true : gradio.props.value.length === 0
39
- );
40
-
41
40
  function handle_delete(
42
41
  event: CustomEvent<{ file: FileData; index: number }>
43
42
  ): void {
@@ -71,6 +70,82 @@
71
70
  : { image: x, caption: null }
72
71
  );
73
72
  }
73
+
74
+ let upload_input: BaseFileUpload;
75
+
76
+ let active_source = $state<
77
+ "upload" | "webcam" | "webcam-video" | "clipboard" | null
78
+ >(gradio.props.sources ? gradio.props.sources[0] : "upload");
79
+
80
+ let no_value = $derived(
81
+ gradio.props.value === null ? true : gradio.props.value.length === 0
82
+ );
83
+
84
+ let sources = $derived.by(() => {
85
+ if (
86
+ gradio.props.file_types?.includes("video") &&
87
+ gradio.props.sources.includes("webcam")
88
+ ) {
89
+ return gradio.props.sources.concat(["webcam-video"]);
90
+ } else {
91
+ return gradio.props.sources;
92
+ }
93
+ });
94
+
95
+ async function paste_clipboard(): Promise<void> {
96
+ navigator.clipboard.read().then(async (items) => {
97
+ let file: File | null = null;
98
+ for (let i = 0; i < items.length; i++) {
99
+ const type = items[i].types.find((t) =>
100
+ (gradio.props.file_types || ["image"]).some((ft) =>
101
+ t.startsWith(ft + "/")
102
+ )
103
+ );
104
+ if (type) {
105
+ const blob = await items[i].getType(type);
106
+ file = new File([blob], `clipboard.${type.replace("image/", "")}`);
107
+ break;
108
+ }
109
+ }
110
+ if (file) {
111
+ const f = await handle_save(
112
+ file,
113
+ (f) => gradio.shared.client.upload(f, gradio.shared.root),
114
+ "clipboard_upload"
115
+ );
116
+ const processed_files = await process_upload_files(f);
117
+ gradio.props.value?.push(...processed_files);
118
+ gradio.dispatch("change", gradio.props.value);
119
+ active_source = null;
120
+ } else {
121
+ gradio.dispatch("warning", "No image or video found in clipboard");
122
+ }
123
+ });
124
+ }
125
+
126
+ async function handle_select_source(
127
+ source: "upload" | "webcam" | "clipboard"
128
+ ): Promise<void> {
129
+ switch (source) {
130
+ case "clipboard":
131
+ await paste_clipboard();
132
+ break;
133
+ default:
134
+ break;
135
+ }
136
+ }
137
+
138
+ async function onsource_change(
139
+ source: "upload" | "webcam" | "webcam-video" | "clipboard"
140
+ ): Promise<void> {
141
+ await tick();
142
+ if (source === "clipboard") {
143
+ await paste_clipboard();
144
+ } else {
145
+ active_source = source;
146
+ no_value = true;
147
+ }
148
+ }
74
149
  </script>
75
150
 
76
151
  <Block
@@ -96,31 +171,96 @@
96
171
  gradio.dispatch("clear_status", gradio.shared.loading_status)}
97
172
  />
98
173
  {#if gradio.shared.interactive && no_value}
99
- <BaseFileUpload
100
- bind:upload_promise
101
- value={null}
102
- root={gradio.shared.root}
103
- label={gradio.shared.label}
104
- max_file_size={gradio.shared.max_file_size}
105
- file_count={"multiple"}
106
- file_types={gradio.props.file_types}
107
- i18n={gradio.i18n}
108
- upload={(...args) => gradio.shared.client.upload(...args)}
109
- stream_handler={(...args) => gradio.shared.client.stream(...args)}
110
- onupload={async (event_data) => {
111
- const files = Array.isArray(event_data) ? event_data : [event_data];
112
- gradio.props.value = await process_upload_files(files);
113
- gradio.dispatch("upload", gradio.props.value);
114
- gradio.dispatch("change", gradio.props.value);
115
- }}
116
- onerror={(event_data) => {
117
- gradio.shared.loading_status = gradio.shared.loading_status || {};
118
- gradio.shared.loading_status.status = "error";
119
- gradio.dispatch("error", event_data);
120
- }}
174
+ <div
175
+ class={!gradio.props.value ||
176
+ (active_source && active_source.includes("webcam"))
177
+ ? "hidden-upload-input"
178
+ : ""}
121
179
  >
122
- <UploadText i18n={gradio.i18n} type="gallery" />
123
- </BaseFileUpload>
180
+ <BaseFileUpload
181
+ bind:upload_promise
182
+ bind:this={upload_input}
183
+ value={null}
184
+ root={gradio.shared.root}
185
+ label={gradio.shared.label}
186
+ max_file_size={gradio.shared.max_file_size}
187
+ file_count={"multiple"}
188
+ file_types={gradio.props.file_types}
189
+ i18n={gradio.i18n}
190
+ upload={(...args) => gradio.shared.client.upload(...args)}
191
+ stream_handler={(...args) => gradio.shared.client.stream(...args)}
192
+ onupload={async (e) => {
193
+ const files = Array.isArray(e) ? e : [e];
194
+ gradio.props.value = await process_upload_files(files);
195
+ active_source = null;
196
+ gradio.dispatch("upload", gradio.props.value);
197
+ gradio.dispatch("change", gradio.props.value);
198
+ }}
199
+ onerror={({ detail }) => {
200
+ gradio.shared.loading_status = gradio.shared.loading_status || {};
201
+ gradio.shared.loading_status.status = "error";
202
+ gradio.dispatch("error", detail);
203
+ }}
204
+ >
205
+ <UploadText i18n={gradio.i18n} type="gallery" />
206
+ </BaseFileUpload>
207
+ </div>
208
+ {#if active_source === "webcam"}
209
+ <Webcam
210
+ root={gradio.shared.root}
211
+ value={null}
212
+ on:capture={async (e) => {
213
+ const f = await handle_save(
214
+ e.detail,
215
+ (f) => gradio.shared.client.upload(f, gradio.shared.root),
216
+ "webcam_upload"
217
+ );
218
+ const processed_files = await process_upload_files(f);
219
+ gradio.props.value?.push(...processed_files);
220
+ active_source = null;
221
+ gradio.dispatch("change", gradio.props.value);
222
+ }}
223
+ on:error
224
+ on:drag
225
+ on:close_stream
226
+ mirror_webcam={true}
227
+ streaming={false}
228
+ mode="image"
229
+ include_audio={false}
230
+ i18n={gradio.i18n}
231
+ upload={(...args) => gradio.shared.client.upload(...args)}
232
+ />
233
+ {:else if active_source === "webcam-video"}
234
+ <Webcam
235
+ root={gradio.shared.root}
236
+ value={null}
237
+ on:capture={async (e) => {
238
+ const f = { ...e.detail };
239
+ f.mime_type = "video/webm";
240
+ const processed_files = await process_upload_files([f]);
241
+ gradio.props.value?.push(...processed_files);
242
+ active_source = null;
243
+ gradio.dispatch("change", gradio.props.value);
244
+ }}
245
+ on:error
246
+ on:drag
247
+ on:close_stream
248
+ mirror_webcam={true}
249
+ streaming={false}
250
+ mode="video"
251
+ include_audio={false}
252
+ i18n={gradio.i18n}
253
+ upload={(...args) => gradio.shared.client.upload(...args)}
254
+ />
255
+ {/if}
256
+ {#if sources.length > 1 || sources.includes("clipboard")}
257
+ <SelectSource
258
+ {sources}
259
+ bind:active_source
260
+ handle_clear={() => gradio.dispatch("clear")}
261
+ handle_select={handle_select_source}
262
+ />
263
+ {/if}
124
264
  {:else}
125
265
  <Gallery
126
266
  onchange={() => gradio.dispatch("change")}
@@ -145,6 +285,8 @@
145
285
  gradio.dispatch("upload", new_value);
146
286
  gradio.dispatch("change", gradio.props.value);
147
287
  }}
288
+ {sources}
289
+ {onsource_change}
148
290
  label={gradio.shared.label}
149
291
  show_label={gradio.shared.show_label}
150
292
  columns={gradio.props.columns}
@@ -181,3 +323,9 @@
181
323
  />
182
324
  {/if}
183
325
  </Block>
326
+
327
+ <style>
328
+ .hidden-upload-input {
329
+ display: none;
330
+ }
331
+ </style>
@@ -1,5 +1,24 @@
1
1
  export { default as BaseGallery } from "./shared/Gallery.svelte";
2
2
  export { default as BaseExample } from "./Example.svelte";
3
- declare const Index: import("svelte").Component<$$ComponentProps, {}, "">;
4
- type Index = ReturnType<typeof Index>;
3
+ 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> {
4
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
5
+ $$bindings?: Bindings;
6
+ } & Exports;
7
+ (internal: unknown, props: Props & {
8
+ $$events?: Events;
9
+ $$slots?: Slots;
10
+ }): Exports & {
11
+ $set?: any;
12
+ $on?: any;
13
+ };
14
+ z_$$bindings?: Bindings;
15
+ }
16
+ declare const Index: $$__sveltets_2_IsomorphicComponent<$$ComponentProps, {
17
+ error: CustomEvent<string>;
18
+ drag: CustomEvent<any>;
19
+ close_stream: CustomEvent<undefined>;
20
+ } & {
21
+ [evt: string]: CustomEvent<any>;
22
+ }, {}, {}, "">;
23
+ type Index = InstanceType<typeof Index>;
5
24
  export default Index;
@@ -7,7 +7,7 @@
7
7
  IconButtonWrapper,
8
8
  FullscreenButton
9
9
  } from "@gradio/atoms";
10
- import type { CustomButton as CustomButtonType } from "@gradio/utils";
10
+ import { type CustomButton as CustomButtonType } from "@gradio/utils";
11
11
  import { ModifyUpload, Upload as UploadComponent } from "@gradio/upload";
12
12
  import { Image } from "@gradio/image/shared";
13
13
  import { Video } from "@gradio/video/shared";
@@ -21,13 +21,15 @@
21
21
  Image as ImageIcon,
22
22
  Clear,
23
23
  Play,
24
- Upload as UploadIcon
24
+ Upload as UploadIcon,
25
+ Webcam,
26
+ Video as VideoIcon,
27
+ ImagePaste
25
28
  } from "@gradio/icons";
26
29
  import { FileData } from "@gradio/client";
27
30
  import type { Client } from "@gradio/client";
28
31
  import { format_gallery_for_sharing } from "./utils";
29
32
  import type { I18nFormatter } from "@gradio/utils";
30
- import { on } from "svelte/events";
31
33
 
32
34
  type GalleryData = GalleryImage | GalleryVideo;
33
35
 
@@ -51,6 +53,7 @@
51
53
  fullscreen = false,
52
54
  root = "",
53
55
  file_types = ["image", "video"],
56
+ sources,
54
57
  max_file_size = null,
55
58
  upload,
56
59
  stream_handler,
@@ -66,7 +69,8 @@
66
69
  onpreview_close = () => {},
67
70
  onfullscreen = (data) => {},
68
71
  ondelete = () => {},
69
- onupload = () => {}
72
+ onupload = () => {},
73
+ onsource_change = () => {}
70
74
  }: {
71
75
  show_label: boolean;
72
76
  label: string;
@@ -87,6 +91,7 @@
87
91
  fullscreen: boolean;
88
92
  root: string;
89
93
  file_types: string[] | null;
94
+ sources: ("upload" | "webcam" | "clipboard" | "webcam-video")[];
90
95
  max_file_size: number | null;
91
96
  upload: Client["upload"] | undefined;
92
97
  stream_handler: Client["stream"] | undefined;
@@ -103,6 +108,7 @@
103
108
  onfullscreen: (data: any) => void;
104
109
  ondelete: (data: any) => void;
105
110
  onupload: (data: FileData | FileData[]) => void;
111
+ onsource_change: (source: string) => void;
106
112
  } = $props();
107
113
 
108
114
  let upload_promise: Promise<any> | null = null;
@@ -151,9 +157,9 @@
151
157
  );
152
158
 
153
159
  let prev_value: GalleryData[] | null = $state(value);
154
- // if (selected_index == null && preview && value?.length) {
155
- // selected_index = 0;
156
- // }
160
+ if (selected_index == null && preview && value?.length) {
161
+ selected_index = 0;
162
+ }
157
163
  let old_selected_index: number | null = $state(selected_index);
158
164
 
159
165
  $effect(() => {
@@ -189,8 +195,6 @@
189
195
  () => ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0)
190
196
  );
191
197
 
192
- $inspect("selected_index", selected_index);
193
-
194
198
  function handle_preview_click(event: MouseEvent): void {
195
199
  const element = event.target as HTMLElement;
196
200
  const x = event.offsetX;
@@ -412,7 +416,8 @@
412
416
  {#if show_fullscreen_button}
413
417
  <FullscreenButton
414
418
  {fullscreen}
415
- on:fullscreen={(e) => onfullscreen(e)}
419
+ onclick={(is_fullscreen) =>
420
+ onfullscreen({ detail: is_fullscreen })}
416
421
  />
417
422
  {/if}
418
423
 
@@ -541,6 +546,7 @@
541
546
  {i18n}
542
547
  onclear={() => {
543
548
  value = [];
549
+ onsource_change("upload");
544
550
  onclear();
545
551
  }}
546
552
  >
@@ -564,6 +570,33 @@
564
570
  />
565
571
  </IconButton>
566
572
  {/if}
573
+ {#if sources.includes("webcam")}
574
+ <IconButton
575
+ Icon={Webcam}
576
+ label={i18n("common.webcam")}
577
+ onclick={() => {
578
+ onsource_change("webcam");
579
+ }}
580
+ />
581
+ {/if}
582
+ {#if sources.includes("webcam-video")}
583
+ <IconButton
584
+ Icon={VideoIcon}
585
+ label={i18n("common.video")}
586
+ onclick={() => {
587
+ onsource_change("webcam-video");
588
+ }}
589
+ />
590
+ {/if}
591
+ {#if sources.includes("clipboard")}
592
+ <IconButton
593
+ label={i18n("upload_text.paste_clipboard")}
594
+ onclick={() => {
595
+ onsource_change("clipboard");
596
+ }}
597
+ Icon={ImagePaste}
598
+ />
599
+ {/if}
567
600
  </ModifyUpload>
568
601
  {/if}
569
602
  <div
@@ -1,4 +1,4 @@
1
- import type { CustomButton as CustomButtonType } from "@gradio/utils";
1
+ import { type CustomButton as CustomButtonType } from "@gradio/utils";
2
2
  import type { GalleryImage, GalleryVideo } from "../types";
3
3
  import { FileData } from "@gradio/client";
4
4
  import type { Client } from "@gradio/client";
@@ -24,6 +24,7 @@ type $$ComponentProps = {
24
24
  fullscreen: boolean;
25
25
  root: string;
26
26
  file_types: string[] | null;
27
+ sources: ("upload" | "webcam" | "clipboard" | "webcam-video")[];
27
28
  max_file_size: number | null;
28
29
  upload: Client["upload"] | undefined;
29
30
  stream_handler: Client["stream"] | undefined;
@@ -40,6 +41,7 @@ type $$ComponentProps = {
40
41
  onfullscreen: (data: any) => void;
41
42
  ondelete: (data: any) => void;
42
43
  onupload: (data: FileData | FileData[]) => void;
44
+ onsource_change: (source: string) => void;
43
45
  };
44
46
  declare const Gallery: import("svelte").Component<$$ComponentProps, {}, "value" | "selected_index">;
45
47
  type Gallery = ReturnType<typeof Gallery>;
@@ -1,2 +1,3 @@
1
- import type { FileData } from "@gradio/client";
1
+ import { FileData, Client } from "@gradio/client";
2
2
  export declare function format_gallery_for_sharing(value: [FileData, string | null][] | null): Promise<string>;
3
+ export declare function handle_save(img_blob: Blob, upload: Client["upload"], filename?: string): Promise<FileData[]>;
@@ -1,4 +1,5 @@
1
1
  import { uploadToHuggingFace } from "@gradio/utils";
2
+ import { FileData, Client } from "@gradio/client";
2
3
  export async function format_gallery_for_sharing(value) {
3
4
  if (!value)
4
5
  return "";
@@ -11,3 +12,18 @@ export async function format_gallery_for_sharing(value) {
11
12
  .map((url) => `<img src="${url}" style="height: 400px" />`)
12
13
  .join("")}</div>`;
13
14
  }
15
+ export async function handle_save(img_blob, upload, filename = "uploaded_file") {
16
+ const ext = img_blob.type.split("/")[1] || "png";
17
+ const f_ = new File([img_blob], `${filename}.${ext}`);
18
+ const files = [
19
+ new FileData({
20
+ path: f_.name,
21
+ orig_name: f_.name,
22
+ blob: f_,
23
+ size: f_.size,
24
+ mime_type: f_.type,
25
+ is_stream: false
26
+ })
27
+ ];
28
+ return (await upload(...files));
29
+ }
package/dist/types.d.ts CHANGED
@@ -22,6 +22,7 @@ export interface GalleryProps {
22
22
  buttons: (string | CustomButton)[];
23
23
  type: "numpy" | "pil" | "filepath";
24
24
  fit_columns: boolean;
25
+ sources: ("upload" | "webcam-video" | "webcam" | "clipboard")[];
25
26
  }
26
27
  export interface GalleryEvents {
27
28
  change: GalleryData[] | null;
@@ -39,4 +40,5 @@ export interface GalleryEvents {
39
40
  custom_button_click: {
40
41
  id: number;
41
42
  };
43
+ warning: string;
42
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/gallery",
3
- "version": "0.16.2",
3
+ "version": "0.17.1",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -8,15 +8,15 @@
8
8
  "private": false,
9
9
  "dependencies": {
10
10
  "dequal": "^2.0.3",
11
- "@gradio/atoms": "^0.20.1",
12
- "@gradio/client": "^2.0.3",
11
+ "@gradio/atoms": "^0.22.0",
12
+ "@gradio/client": "^2.0.4",
13
+ "@gradio/file": "^0.14.3",
14
+ "@gradio/image": "^0.25.3",
13
15
  "@gradio/icons": "^0.15.1",
14
- "@gradio/file": "^0.14.1",
15
- "@gradio/statustracker": "^0.12.2",
16
- "@gradio/image": "^0.25.1",
17
- "@gradio/upload": "^0.17.4",
18
- "@gradio/utils": "^0.11.2",
19
- "@gradio/video": "^0.20.1"
16
+ "@gradio/statustracker": "^0.12.4",
17
+ "@gradio/upload": "^0.17.6",
18
+ "@gradio/utils": "^0.11.3",
19
+ "@gradio/video": "^0.20.3"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@gradio/preview": "^0.15.2"
@@ -7,7 +7,7 @@
7
7
  IconButtonWrapper,
8
8
  FullscreenButton
9
9
  } from "@gradio/atoms";
10
- import type { CustomButton as CustomButtonType } from "@gradio/utils";
10
+ import { type CustomButton as CustomButtonType } from "@gradio/utils";
11
11
  import { ModifyUpload, Upload as UploadComponent } from "@gradio/upload";
12
12
  import { Image } from "@gradio/image/shared";
13
13
  import { Video } from "@gradio/video/shared";
@@ -21,13 +21,15 @@
21
21
  Image as ImageIcon,
22
22
  Clear,
23
23
  Play,
24
- Upload as UploadIcon
24
+ Upload as UploadIcon,
25
+ Webcam,
26
+ Video as VideoIcon,
27
+ ImagePaste
25
28
  } from "@gradio/icons";
26
29
  import { FileData } from "@gradio/client";
27
30
  import type { Client } from "@gradio/client";
28
31
  import { format_gallery_for_sharing } from "./utils";
29
32
  import type { I18nFormatter } from "@gradio/utils";
30
- import { on } from "svelte/events";
31
33
 
32
34
  type GalleryData = GalleryImage | GalleryVideo;
33
35
 
@@ -51,6 +53,7 @@
51
53
  fullscreen = false,
52
54
  root = "",
53
55
  file_types = ["image", "video"],
56
+ sources,
54
57
  max_file_size = null,
55
58
  upload,
56
59
  stream_handler,
@@ -66,7 +69,8 @@
66
69
  onpreview_close = () => {},
67
70
  onfullscreen = (data) => {},
68
71
  ondelete = () => {},
69
- onupload = () => {}
72
+ onupload = () => {},
73
+ onsource_change = () => {}
70
74
  }: {
71
75
  show_label: boolean;
72
76
  label: string;
@@ -87,6 +91,7 @@
87
91
  fullscreen: boolean;
88
92
  root: string;
89
93
  file_types: string[] | null;
94
+ sources: ("upload" | "webcam" | "clipboard" | "webcam-video")[];
90
95
  max_file_size: number | null;
91
96
  upload: Client["upload"] | undefined;
92
97
  stream_handler: Client["stream"] | undefined;
@@ -103,6 +108,7 @@
103
108
  onfullscreen: (data: any) => void;
104
109
  ondelete: (data: any) => void;
105
110
  onupload: (data: FileData | FileData[]) => void;
111
+ onsource_change: (source: string) => void;
106
112
  } = $props();
107
113
 
108
114
  let upload_promise: Promise<any> | null = null;
@@ -151,9 +157,9 @@
151
157
  );
152
158
 
153
159
  let prev_value: GalleryData[] | null = $state(value);
154
- // if (selected_index == null && preview && value?.length) {
155
- // selected_index = 0;
156
- // }
160
+ if (selected_index == null && preview && value?.length) {
161
+ selected_index = 0;
162
+ }
157
163
  let old_selected_index: number | null = $state(selected_index);
158
164
 
159
165
  $effect(() => {
@@ -189,8 +195,6 @@
189
195
  () => ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0)
190
196
  );
191
197
 
192
- $inspect("selected_index", selected_index);
193
-
194
198
  function handle_preview_click(event: MouseEvent): void {
195
199
  const element = event.target as HTMLElement;
196
200
  const x = event.offsetX;
@@ -412,7 +416,8 @@
412
416
  {#if show_fullscreen_button}
413
417
  <FullscreenButton
414
418
  {fullscreen}
415
- on:fullscreen={(e) => onfullscreen(e)}
419
+ onclick={(is_fullscreen) =>
420
+ onfullscreen({ detail: is_fullscreen })}
416
421
  />
417
422
  {/if}
418
423
 
@@ -541,6 +546,7 @@
541
546
  {i18n}
542
547
  onclear={() => {
543
548
  value = [];
549
+ onsource_change("upload");
544
550
  onclear();
545
551
  }}
546
552
  >
@@ -564,6 +570,33 @@
564
570
  />
565
571
  </IconButton>
566
572
  {/if}
573
+ {#if sources.includes("webcam")}
574
+ <IconButton
575
+ Icon={Webcam}
576
+ label={i18n("common.webcam")}
577
+ onclick={() => {
578
+ onsource_change("webcam");
579
+ }}
580
+ />
581
+ {/if}
582
+ {#if sources.includes("webcam-video")}
583
+ <IconButton
584
+ Icon={VideoIcon}
585
+ label={i18n("common.video")}
586
+ onclick={() => {
587
+ onsource_change("webcam-video");
588
+ }}
589
+ />
590
+ {/if}
591
+ {#if sources.includes("clipboard")}
592
+ <IconButton
593
+ label={i18n("upload_text.paste_clipboard")}
594
+ onclick={() => {
595
+ onsource_change("clipboard");
596
+ }}
597
+ Icon={ImagePaste}
598
+ />
599
+ {/if}
567
600
  </ModifyUpload>
568
601
  {/if}
569
602
  <div
package/shared/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { uploadToHuggingFace } from "@gradio/utils";
2
- import type { FileData } from "@gradio/client";
2
+ import { FileData, Client } from "@gradio/client";
3
3
 
4
4
  export async function format_gallery_for_sharing(
5
5
  value: [FileData, string | null][] | null
@@ -16,3 +16,23 @@ export async function format_gallery_for_sharing(
16
16
  .map((url) => `<img src="${url}" style="height: 400px" />`)
17
17
  .join("")}</div>`;
18
18
  }
19
+
20
+ export async function handle_save(
21
+ img_blob: Blob,
22
+ upload: Client["upload"],
23
+ filename: string = "uploaded_file"
24
+ ): Promise<FileData[]> {
25
+ const ext = img_blob.type.split("/")[1] || "png";
26
+ const f_ = new File([img_blob], `${filename}.${ext}`);
27
+ const files = [
28
+ new FileData({
29
+ path: f_.name,
30
+ orig_name: f_.name,
31
+ blob: f_,
32
+ size: f_.size,
33
+ mime_type: f_.type,
34
+ is_stream: false
35
+ })
36
+ ];
37
+ return (await upload(...files)) as FileData[];
38
+ }
package/types.ts CHANGED
@@ -26,6 +26,7 @@ export interface GalleryProps {
26
26
  buttons: (string | CustomButton)[];
27
27
  type: "numpy" | "pil" | "filepath";
28
28
  fit_columns: boolean;
29
+ sources: ("upload" | "webcam-video" | "webcam" | "clipboard")[];
29
30
  }
30
31
 
31
32
  export interface GalleryEvents {
@@ -39,4 +40,5 @@ export interface GalleryEvents {
39
40
  share: any;
40
41
  error: any;
41
42
  custom_button_click: { id: number };
43
+ warning: string;
42
44
  }