@gradio/gallery 0.16.2 → 0.17.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @gradio/gallery
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Features
6
+
7
+ - [#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!
8
+
9
+ ### Dependency updates
10
+
11
+ - @gradio/atoms@0.21.0
12
+ - @gradio/client@2.0.4
13
+ - @gradio/statustracker@0.12.3
14
+ - @gradio/file@0.14.2
15
+ - @gradio/image@0.25.2
16
+ - @gradio/upload@0.17.5
17
+ - @gradio/video@0.20.2
18
+
3
19
  ## 0.16.2
4
20
 
5
21
  ### Fixes
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;
@@ -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;
@@ -410,10 +414,7 @@
410
414
  {/if}
411
415
 
412
416
  {#if show_fullscreen_button}
413
- <FullscreenButton
414
- {fullscreen}
415
- on:fullscreen={(e) => onfullscreen(e)}
416
- />
417
+ <FullscreenButton {fullscreen} onclick={(e) => onfullscreen(e)} />
417
418
  {/if}
418
419
 
419
420
  {#if show_share_button}
@@ -541,6 +542,7 @@
541
542
  {i18n}
542
543
  onclear={() => {
543
544
  value = [];
545
+ onsource_change("upload");
544
546
  onclear();
545
547
  }}
546
548
  >
@@ -564,6 +566,33 @@
564
566
  />
565
567
  </IconButton>
566
568
  {/if}
569
+ {#if sources.includes("webcam")}
570
+ <IconButton
571
+ Icon={Webcam}
572
+ label={i18n("common.webcam")}
573
+ onclick={() => {
574
+ onsource_change("webcam");
575
+ }}
576
+ />
577
+ {/if}
578
+ {#if sources.includes("webcam-video")}
579
+ <IconButton
580
+ Icon={VideoIcon}
581
+ label={i18n("common.video")}
582
+ onclick={() => {
583
+ onsource_change("webcam-video");
584
+ }}
585
+ />
586
+ {/if}
587
+ {#if sources.includes("clipboard")}
588
+ <IconButton
589
+ label={i18n("upload_text.paste_clipboard")}
590
+ onclick={() => {
591
+ onsource_change("clipboard");
592
+ }}
593
+ Icon={ImagePaste}
594
+ />
595
+ {/if}
567
596
  </ModifyUpload>
568
597
  {/if}
569
598
  <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.0",
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.21.0",
12
+ "@gradio/client": "^2.0.4",
13
+ "@gradio/file": "^0.14.2",
14
+ "@gradio/image": "^0.25.2",
15
+ "@gradio/statustracker": "^0.12.3",
13
16
  "@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
17
  "@gradio/utils": "^0.11.2",
19
- "@gradio/video": "^0.20.1"
18
+ "@gradio/video": "^0.20.2",
19
+ "@gradio/upload": "^0.17.5"
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;
@@ -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;
@@ -410,10 +414,7 @@
410
414
  {/if}
411
415
 
412
416
  {#if show_fullscreen_button}
413
- <FullscreenButton
414
- {fullscreen}
415
- on:fullscreen={(e) => onfullscreen(e)}
416
- />
417
+ <FullscreenButton {fullscreen} onclick={(e) => onfullscreen(e)} />
417
418
  {/if}
418
419
 
419
420
  {#if show_share_button}
@@ -541,6 +542,7 @@
541
542
  {i18n}
542
543
  onclear={() => {
543
544
  value = [];
545
+ onsource_change("upload");
544
546
  onclear();
545
547
  }}
546
548
  >
@@ -564,6 +566,33 @@
564
566
  />
565
567
  </IconButton>
566
568
  {/if}
569
+ {#if sources.includes("webcam")}
570
+ <IconButton
571
+ Icon={Webcam}
572
+ label={i18n("common.webcam")}
573
+ onclick={() => {
574
+ onsource_change("webcam");
575
+ }}
576
+ />
577
+ {/if}
578
+ {#if sources.includes("webcam-video")}
579
+ <IconButton
580
+ Icon={VideoIcon}
581
+ label={i18n("common.video")}
582
+ onclick={() => {
583
+ onsource_change("webcam-video");
584
+ }}
585
+ />
586
+ {/if}
587
+ {#if sources.includes("clipboard")}
588
+ <IconButton
589
+ label={i18n("upload_text.paste_clipboard")}
590
+ onclick={() => {
591
+ onsource_change("clipboard");
592
+ }}
593
+ Icon={ImagePaste}
594
+ />
595
+ {/if}
567
596
  </ModifyUpload>
568
597
  {/if}
569
598
  <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
  }