@gradio/image 0.27.0 → 0.28.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.
@@ -1,27 +1,44 @@
1
1
  <script lang="ts">
2
+ import type { HTMLImgAttributes } from "svelte/elements";
3
+
2
4
  let {
3
5
  src = "",
4
6
  restProps = {},
5
7
  data_testid,
6
8
  class_names = [],
9
+ onload,
7
10
  ...imgProps
8
11
  }: {
9
12
  src?: string;
10
13
  restProps?: Record<string, any>;
11
14
  data_testid?: string;
12
15
  class_names?: string[];
16
+ onload?: HTMLImgAttributes["onload"];
13
17
  [key: string]: any;
14
18
  } = $props();
19
+
20
+ const without_class = ({
21
+ class: _class,
22
+ ...props
23
+ }: Record<string, any>): Record<string, any> => props;
24
+
25
+ let rest_img_props = $derived(without_class(restProps));
26
+ let direct_img_props = $derived(without_class(imgProps));
27
+ let classes = $derived(
28
+ [class_names.join(" "), restProps.class, imgProps.class]
29
+ .filter(Boolean)
30
+ .join(" ")
31
+ );
15
32
  </script>
16
33
 
17
34
  <!-- svelte-ignore a11y-missing-attribute -->
18
35
  <img
19
36
  {src}
20
- class={class_names.join(" ")}
37
+ class={classes}
21
38
  data-testid={data_testid}
22
- {...restProps}
23
- {...imgProps}
24
- on:load
39
+ {...rest_img_props}
40
+ {...direct_img_props}
41
+ {onload}
25
42
  />
26
43
 
27
44
  <style>
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onMount } from "svelte";
3
2
  import type { SelectData } from "@gradio/utils";
4
3
  import { uploadToHuggingFace } from "@gradio/utils";
5
4
  import {
@@ -19,27 +18,44 @@
19
18
  import type { I18nFormatter } from "@gradio/utils";
20
19
  import type { FileData } from "@gradio/client";
21
20
 
22
- export let value: null | FileData;
23
- export let label: string | undefined = undefined;
24
- export let show_label: boolean;
25
- export let buttons: (string | CustomButtonType)[] = [];
26
- export let on_custom_button_click: ((id: number) => void) | null = null;
27
- export let selectable = false;
28
- export let i18n: I18nFormatter;
29
- export let display_icon_button_wrapper_top_corner = false;
30
- export let fullscreen = false;
31
- export let show_button_background = true;
32
-
33
- const dispatch = createEventDispatcher<{
34
- change: string;
35
- select: SelectData;
36
- fullscreen: boolean;
37
- }>();
21
+ let {
22
+ value,
23
+ label = undefined,
24
+ show_label,
25
+ buttons = [],
26
+ on_custom_button_click = null,
27
+ selectable = false,
28
+ i18n,
29
+ display_icon_button_wrapper_top_corner = false,
30
+ fullscreen = $bindable(false),
31
+ show_button_background = true,
32
+ onselect,
33
+ onfullscreen,
34
+ onshare,
35
+ onerror,
36
+ onload
37
+ }: {
38
+ value: null | FileData;
39
+ label?: string;
40
+ show_label: boolean;
41
+ buttons?: (string | CustomButtonType)[];
42
+ on_custom_button_click?: ((id: number) => void) | null;
43
+ selectable?: boolean;
44
+ i18n: I18nFormatter;
45
+ display_icon_button_wrapper_top_corner?: boolean;
46
+ fullscreen?: boolean;
47
+ show_button_background?: boolean;
48
+ onselect?: (value: SelectData) => void;
49
+ onfullscreen?: (fullscreen: boolean) => void;
50
+ onshare?: (detail: unknown) => void;
51
+ onerror?: (error: string) => void;
52
+ onload?: () => void;
53
+ } = $props();
38
54
 
39
55
  const handle_click = (evt: MouseEvent): void => {
40
56
  let coordinates = get_coordinates_of_clicked_image(evt);
41
57
  if (coordinates) {
42
- dispatch("select", { index: coordinates, value: null });
58
+ onselect?.({ index: coordinates, value: null });
43
59
  }
44
60
  };
45
61
 
@@ -66,7 +82,7 @@
66
82
  {fullscreen}
67
83
  onclick={(is_fullscreen) => {
68
84
  fullscreen = is_fullscreen;
69
- dispatch("fullscreen", is_fullscreen);
85
+ onfullscreen?.(is_fullscreen);
70
86
  }}
71
87
  />
72
88
  {/if}
@@ -78,8 +94,8 @@
78
94
  {#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
79
95
  <ShareButton
80
96
  {i18n}
81
- on:share
82
- on:error
97
+ onshare={(detail) => onshare?.(detail)}
98
+ onerror={(detail) => onerror?.(detail)}
83
99
  formatter={async (value) => {
84
100
  if (!value) return "";
85
101
  let url = await uploadToHuggingFace(value, "url");
@@ -89,12 +105,12 @@
89
105
  />
90
106
  {/if}
91
107
  </IconButtonWrapper>
92
- <button on:click={handle_click}>
108
+ <button onclick={handle_click}>
93
109
  <div class:selectable class="image-frame">
94
110
  <Image
95
111
  src={value.url}
96
112
  restProps={{ loading: "lazy", alt: "" }}
97
- on:load
113
+ {onload}
98
114
  />
99
115
  </div>
100
116
  </button>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, tick } from "svelte";
2
+ import { tick, type Snippet } from "svelte";
3
3
  import { BlockLabel, IconButtonWrapper, IconButton } from "@gradio/atoms";
4
4
  import { Clear, Image as ImageIcon } from "@gradio/icons";
5
5
  import { FullscreenButton } from "@gradio/atoms";
@@ -17,36 +17,80 @@
17
17
  import Image from "./Image.svelte";
18
18
  import type { Base64File, WebcamOptions } from "./types";
19
19
 
20
- export let value: null | FileData | Base64File = null;
21
- export let label: string | undefined = undefined;
22
- export let show_label: boolean;
23
-
24
20
  type source_type = "upload" | "webcam" | "clipboard" | "microphone" | null;
25
21
 
26
- export let sources: source_type[] = ["upload", "clipboard", "webcam"];
27
- export let streaming = false;
28
- export let pending = false;
29
- export let webcam_options: WebcamOptions;
30
- export let selectable = false;
31
- export let root: string;
32
- export let i18n: I18nFormatter;
33
- export let max_file_size: number | null = null;
34
- export let upload: Client["upload"];
35
- export let stream_handler: Client["stream"];
36
- export let stream_every: number;
37
- export let time_limit: number;
38
- export let show_fullscreen_button = true;
39
- export let stream_state: "open" | "waiting" | "closed" = "closed";
40
- export let upload_promise: Promise<any> | null = null;
41
- export let onerror: ((error: string) => void) | undefined = undefined;
22
+ let {
23
+ value = $bindable<null | FileData | Base64File>(null),
24
+ label = undefined,
25
+ show_label,
26
+ sources = ["upload", "clipboard", "webcam"],
27
+ streaming = false,
28
+ pending = $bindable(false),
29
+ webcam_options,
30
+ selectable = false,
31
+ root,
32
+ i18n,
33
+ max_file_size = null,
34
+ upload,
35
+ stream_handler,
36
+ stream_every,
37
+ time_limit,
38
+ show_fullscreen_button = true,
39
+ stream_state = "closed",
40
+ upload_promise = $bindable<Promise<any> | null>(null),
41
+ onerror,
42
+ uploading = $bindable(false),
43
+ active_source = $bindable<source_type>(null),
44
+ fullscreen = $bindable(false),
45
+ dragging = $bindable(false),
46
+ onchange,
47
+ onstream,
48
+ onclear,
49
+ ondrag,
50
+ onupload,
51
+ onselect,
52
+ onfullscreen,
53
+ onclose_stream,
54
+ children
55
+ }: {
56
+ value?: null | FileData | Base64File;
57
+ label?: string;
58
+ show_label: boolean;
59
+ sources?: source_type[];
60
+ streaming?: boolean;
61
+ pending?: boolean;
62
+ webcam_options: WebcamOptions;
63
+ selectable?: boolean;
64
+ root: string;
65
+ i18n: I18nFormatter;
66
+ max_file_size?: number | null;
67
+ upload: Client["upload"];
68
+ stream_handler: Client["stream"];
69
+ stream_every: number;
70
+ time_limit: number;
71
+ show_fullscreen_button?: boolean;
72
+ stream_state?: "open" | "waiting" | "closed";
73
+ upload_promise?: Promise<any> | null;
74
+ onerror?: (error: string) => void;
75
+ uploading?: boolean;
76
+ active_source?: source_type;
77
+ fullscreen?: boolean;
78
+ dragging?: boolean;
79
+ onchange?: (value?: null | FileData | Base64File) => void;
80
+ onstream?: (value: ValueData) => void;
81
+ onclear?: () => void;
82
+ ondrag?: (dragging: boolean) => void;
83
+ onupload?: () => void;
84
+ onselect?: (value: SelectData) => void;
85
+ onfullscreen?: (fullscreen: boolean) => void;
86
+ onclose_stream?: () => void;
87
+ children?: Snippet;
88
+ } = $props();
42
89
 
43
90
  let upload_input: Upload;
44
- export let uploading = false;
45
- export let active_source: source_type = null;
46
- export let fullscreen = false;
47
91
 
48
- let files: FileData[] = [];
49
- let upload_id: string;
92
+ let files = $state<FileData[]>([]);
93
+ let upload_id = $state("");
50
94
 
51
95
  async function handle_upload(detail: FileData): Promise<void> {
52
96
  if (!streaming) {
@@ -62,14 +106,14 @@
62
106
  }
63
107
 
64
108
  await tick();
65
- dispatch("upload");
109
+ onupload?.();
66
110
  }
67
111
  }
68
112
 
69
113
  function handle_clear(): void {
70
114
  value = null;
71
- dispatch("clear");
72
- dispatch("change", null);
115
+ onclear?.();
116
+ onchange?.(null);
73
117
  }
74
118
 
75
119
  function handle_remove_image_click(event: MouseEvent): void {
@@ -82,7 +126,7 @@
82
126
  event: "change" | "stream" | "upload"
83
127
  ): Promise<void> {
84
128
  if (event === "stream") {
85
- dispatch("stream", {
129
+ onstream?.({
86
130
  value: { url: img_blob } as Base64File,
87
131
  is_value_data: true
88
132
  });
@@ -105,39 +149,31 @@
105
149
  if (event === "change" || event === "upload") {
106
150
  value = f?.[0] || null;
107
151
  await tick();
108
- dispatch("change");
152
+ onchange?.();
109
153
  }
110
154
  pending = false;
111
155
  }
112
156
 
113
- $: active_streaming = streaming && active_source === "webcam";
114
- $: if (uploading && !active_streaming) value = null;
115
-
116
- const dispatch = createEventDispatcher<{
117
- change?: never;
118
- stream: ValueData;
119
- clear?: never;
120
- drag: boolean;
121
- upload?: never;
122
- select: SelectData;
123
- end_stream: never;
124
- fullscreen: boolean;
125
- }>();
126
-
127
- export let dragging = false;
128
-
129
- $: dispatch("drag", dragging);
157
+ let active_streaming = $derived(streaming && active_source === "webcam");
158
+ $effect(() => {
159
+ if (uploading && !active_streaming) value = null;
160
+ });
161
+ $effect(() => {
162
+ ondrag?.(dragging);
163
+ });
130
164
 
131
165
  function handle_click(evt: MouseEvent): void {
132
166
  let coordinates = get_coordinates_of_clicked_image(evt);
133
167
  if (coordinates) {
134
- dispatch("select", { index: coordinates, value: null });
168
+ onselect?.({ index: coordinates, value: null });
135
169
  }
136
170
  }
137
171
 
138
- $: if (!active_source && sources) {
139
- active_source = sources[0];
140
- }
172
+ $effect(() => {
173
+ if (!active_source && sources) {
174
+ active_source = sources[0];
175
+ }
176
+ });
141
177
 
142
178
  async function handle_select_source(
143
179
  source: (typeof sources)[number]
@@ -189,7 +225,7 @@
189
225
  {fullscreen}
190
226
  onclick={(is_fullscreen) => {
191
227
  fullscreen = is_fullscreen;
192
- dispatch("fullscreen", is_fullscreen);
228
+ onfullscreen?.(is_fullscreen);
193
229
  }}
194
230
  />
195
231
  {/if}
@@ -205,8 +241,8 @@
205
241
  class="upload-container"
206
242
  class:reduced-height={sources.length > 1}
207
243
  style:width={value ? "auto" : "100%"}
208
- on:dragover={on_drag_over}
209
- on:drop={on_drop}
244
+ ondragover={on_drag_over}
245
+ ondrop={on_drop}
210
246
  >
211
247
  <Upload
212
248
  bind:upload_promise
@@ -225,7 +261,7 @@
225
261
  aria_label={i18n("image.drop_to_upload")}
226
262
  >
227
263
  {#if value === null}
228
- <slot />
264
+ {#if children}{@render children()}{/if}
229
265
  {/if}
230
266
  </Upload>
231
267
  {#if active_source === "webcam" && !streaming && pending}
@@ -234,12 +270,10 @@
234
270
  <Webcam
235
271
  {root}
236
272
  {value}
237
- on:capture={(e) => handle_save(e.detail, "change")}
238
- on:stream={(e) => handle_save(e.detail, "stream")}
239
- on:error
240
- on:drag
241
- on:upload={(e) => handle_save(e.detail, "upload")}
242
- on:close_stream
273
+ oncapture={(detail) => handle_save(detail, "change")}
274
+ onstream={(detail) => handle_save(detail, "stream")}
275
+ {onerror}
276
+ {onclose_stream}
243
277
  {stream_state}
244
278
  mirror_webcam={webcam_options.mirror}
245
279
  {stream_every}
@@ -254,7 +288,7 @@
254
288
  {:else if value !== null && !streaming}
255
289
  <!-- svelte-ignore a11y-click-events-have-key-events-->
256
290
  <!-- svelte-ignore a11y-no-static-element-interactions-->
257
- <div class:selectable class="image-frame" on:click={handle_click}>
291
+ <div class:selectable class="image-frame" onclick={handle_click}>
258
292
  <Image src={value.url} restProps={{ alt: value.alt_text }} />
259
293
  </div>
260
294
  {/if}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onDestroy, onMount } from "svelte";
2
+ import { onDestroy, onMount } from "svelte";
3
3
  import {
4
4
  Camera,
5
5
  Circle,
@@ -20,33 +20,51 @@
20
20
  import type { Base64File } from "./types";
21
21
 
22
22
  let video_source: HTMLVideoElement;
23
- let available_video_devices: MediaDeviceInfo[] = [];
24
- let selected_device: MediaDeviceInfo | null = null;
25
-
26
- export let stream_state: "open" | "waiting" | "closed" = "closed";
23
+ let available_video_devices = $state<MediaDeviceInfo[]>([]);
24
+ let selected_device = $state<MediaDeviceInfo | null>(null);
27
25
 
28
26
  let canvas: HTMLCanvasElement;
29
- export let streaming = false;
30
- export let pending = false;
31
- export let root = "";
32
- export let stream_every = 1;
33
-
34
- export let mode: "image" | "video" = "image";
35
- export let mirror_webcam: boolean;
36
- export let include_audio: boolean;
37
- export let webcam_constraints: { [key: string]: any } | null = null;
38
- export let i18n: I18nFormatter;
39
- export let upload: Client["upload"];
40
- export let value: FileData | null | Base64File = null;
41
- export let time_limit: number | null = null;
42
- const dispatch = createEventDispatcher<{
43
- stream: Blob | string;
44
- capture: FileData | Blob | null;
45
- error: string;
46
- start_recording: undefined;
47
- stop_recording: undefined;
48
- close_stream: undefined;
49
- }>();
27
+ let {
28
+ stream_state = "closed",
29
+ streaming = false,
30
+ pending = false,
31
+ root = "",
32
+ stream_every = 1,
33
+ mode = "image",
34
+ mirror_webcam,
35
+ include_audio,
36
+ webcam_constraints = null,
37
+ i18n,
38
+ upload,
39
+ value = null,
40
+ time_limit = null,
41
+ onstream,
42
+ oncapture,
43
+ onerror,
44
+ onstart_recording,
45
+ onstop_recording,
46
+ onclose_stream
47
+ }: {
48
+ stream_state?: "open" | "waiting" | "closed";
49
+ streaming?: boolean;
50
+ pending?: boolean;
51
+ root?: string;
52
+ stream_every?: number;
53
+ mode?: "image" | "video";
54
+ mirror_webcam: boolean;
55
+ include_audio: boolean;
56
+ webcam_constraints?: { [key: string]: any } | null;
57
+ i18n: I18nFormatter;
58
+ upload: Client["upload"];
59
+ value?: FileData | null | Base64File;
60
+ time_limit?: number | null;
61
+ onstream?: (value: Blob | string) => void;
62
+ oncapture?: (value: FileData | Blob | null) => void;
63
+ onerror?: (error: string) => void;
64
+ onstart_recording?: () => void;
65
+ onstop_recording?: () => void;
66
+ onclose_stream?: () => void;
67
+ } = $props();
50
68
 
51
69
  onMount(() => {
52
70
  canvas = document.createElement("canvas");
@@ -101,11 +119,11 @@
101
119
  });
102
120
 
103
121
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
104
- dispatch("error", i18n("image.no_webcam_support"));
122
+ onerror?.(i18n("image.no_webcam_support"));
105
123
  }
106
124
  } catch (err) {
107
125
  if (err instanceof DOMException && err.name == "NotAllowedError") {
108
- dispatch("error", i18n("image.allow_webcam_access"));
126
+ onerror?.(i18n("image.allow_webcam_access"));
109
127
  } else {
110
128
  throw err;
111
129
  }
@@ -139,13 +157,17 @@
139
157
  }
140
158
  if (streaming) {
141
159
  const image_data = canvas.toDataURL("image/jpeg");
142
- dispatch("stream", image_data);
160
+ onstream?.(image_data);
143
161
  return;
144
162
  }
145
163
 
146
164
  canvas.toBlob(
147
165
  (blob) => {
148
- dispatch(streaming ? "stream" : "capture", blob);
166
+ if (streaming) {
167
+ onstream?.(blob as Blob);
168
+ } else {
169
+ oncapture?.(blob);
170
+ }
149
171
  },
150
172
  `image/${streaming ? "jpeg" : "png"}`,
151
173
  0.8
@@ -153,7 +175,7 @@
153
175
  }
154
176
  }
155
177
 
156
- let recording = false;
178
+ let recording = $state(false);
157
179
  let recorded_blobs: BlobPart[] = [];
158
180
  let stream: MediaStream;
159
181
  let mimeType: string;
@@ -174,13 +196,13 @@
174
196
  let val_ = (
175
197
  (await upload(val, root))?.filter(Boolean) as FileData[]
176
198
  )[0];
177
- dispatch("capture", val_);
178
- dispatch("stop_recording");
199
+ oncapture?.(val_);
200
+ onstop_recording?.();
179
201
  }
180
202
  };
181
203
  ReaderObj.readAsDataURL(video_blob);
182
204
  } else if (typeof MediaRecorder !== "undefined") {
183
- dispatch("start_recording");
205
+ onstart_recording?.();
184
206
  recorded_blobs = [];
185
207
  let validMimeTypes = ["video/webm", "video/mp4"];
186
208
  for (let validMimeType of validMimeTypes) {
@@ -204,7 +226,7 @@
204
226
  recording = !recording;
205
227
  }
206
228
 
207
- let webcam_accessed = false;
229
+ let webcam_accessed = $state(false);
208
230
 
209
231
  function record_video_or_photo({
210
232
  destroy
@@ -222,11 +244,11 @@
222
244
  }
223
245
 
224
246
  if (!recording && stream) {
225
- dispatch("close_stream");
247
+ onclose_stream?.();
226
248
  }
227
249
  }
228
250
 
229
- let options_open = false;
251
+ let options_open = $state(false);
230
252
 
231
253
  export function click_outside(node: Node, cb: any): any {
232
254
  const handle_click = (event: MouseEvent): void => {
@@ -281,12 +303,12 @@
281
303
  title="grant webcam access"
282
304
  style="height: 100%"
283
305
  >
284
- <WebcamPermissions on:click={async () => access_webcam()} />
306
+ <WebcamPermissions onclick={async () => access_webcam()} />
285
307
  </div>
286
308
  {:else}
287
309
  <div class="button-wrap">
288
310
  <button
289
- on:click={() => record_video_or_photo()}
311
+ onclick={() => record_video_or_photo()}
290
312
  aria-label={mode === "image" ? "capture photo" : "start recording"}
291
313
  >
292
314
  {#if mode === "video" || streaming}
@@ -321,7 +343,7 @@
321
343
  {#if !recording}
322
344
  <button
323
345
  class="icon"
324
- on:click={() => (options_open = true)}
346
+ onclick={() => (options_open = true)}
325
347
  aria-label="select input source"
326
348
  >
327
349
  <DropdownArrow />
@@ -333,14 +355,8 @@
333
355
  class="select-wrap"
334
356
  aria-label="select source"
335
357
  use:click_outside={handle_click_outside}
336
- on:change={handle_device_change}
358
+ onchange={handle_device_change}
337
359
  >
338
- <!-- <button
339
- class="inset-icon"
340
- on:click|stopPropagation={() => (options_open = false)}
341
- >
342
- <DropdownArrow />
343
- </button> -->
344
360
  {#if available_video_devices.length === 0}
345
361
  <option value="">{i18n("common.no_devices")}</option>
346
362
  {:else}
@@ -1,13 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { Webcam } from "@gradio/icons";
3
- import { createEventDispatcher } from "svelte";
4
3
 
5
- const dispatch = createEventDispatcher<{
6
- click: undefined;
7
- }>();
4
+ let { onclick }: { onclick?: () => void } = $props();
8
5
  </script>
9
6
 
10
- <button style:height="100%" on:click={() => dispatch("click")}>
7
+ <button style:height="100%" {onclick}>
11
8
  <div class="wrap">
12
9
  <span class="icon-wrap">
13
10
  <Webcam />