@gradio/gallery 0.16.1 → 0.16.2

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.
@@ -9,11 +9,10 @@
9
9
  } from "@gradio/atoms";
10
10
  import type { CustomButton as CustomButtonType } from "@gradio/utils";
11
11
  import { ModifyUpload, Upload as UploadComponent } from "@gradio/upload";
12
- import type { SelectData } from "@gradio/utils";
13
12
  import { Image } from "@gradio/image/shared";
14
13
  import { Video } from "@gradio/video/shared";
15
14
  import { dequal } from "dequal";
16
- import { createEventDispatcher, onMount } from "svelte";
15
+ import { onMount } from "svelte";
17
16
  import { tick } from "svelte";
18
17
  import type { GalleryImage, GalleryVideo } from "../types";
19
18
 
@@ -28,62 +27,93 @@
28
27
  import type { Client } from "@gradio/client";
29
28
  import { format_gallery_for_sharing } from "./utils";
30
29
  import type { I18nFormatter } from "@gradio/utils";
30
+ import { on } from "svelte/events";
31
31
 
32
32
  type GalleryData = GalleryImage | GalleryVideo;
33
33
 
34
- export let show_label = true;
35
- export let label: string;
36
- export let value: GalleryData[] | null = null;
37
- export let columns: number | number[] | undefined = [2];
38
- export let rows: number | number[] | undefined = undefined;
39
- export let height: number | "auto" = "auto";
40
- export let preview: boolean;
41
- export let allow_preview = true;
42
- export let object_fit: "contain" | "cover" | "fill" | "none" | "scale-down" =
43
- "cover";
44
- export let show_share_button = false;
45
- export let show_download_button = false;
46
- export let i18n: I18nFormatter;
47
- export let selected_index: number | null = null;
48
- export let interactive: boolean;
49
- export let _fetch: typeof fetch;
50
- export let mode: "normal" | "minimal" = "normal";
51
- export let show_fullscreen_button = true;
52
- export let display_icon_button_wrapper_top_corner = false;
53
- export let fullscreen = false;
54
- export let root = "";
55
- export let file_types: string[] | null = ["image", "video"];
56
- export let max_file_size: number | null = null;
57
- export let upload: Client["upload"] | undefined = undefined;
58
- export let stream_handler: Client["stream"] | undefined = undefined;
59
- export let fit_columns = true;
60
- export let upload_promise: Promise<any> | null = null;
61
- export let buttons: (string | CustomButtonType)[] | null = null;
62
- export let on_custom_button_click: ((id: number) => void) | null = null;
63
-
34
+ let {
35
+ show_label = true,
36
+ label,
37
+ value = $bindable(),
38
+ columns = [2],
39
+ rows = undefined,
40
+ height = "auto",
41
+ preview,
42
+ allow_preview = true,
43
+ object_fit = "cover",
44
+ show_share_button = false,
45
+ show_download_button = false,
46
+ i18n,
47
+ selected_index = $bindable(),
48
+ interactive,
49
+ _fetch,
50
+ show_fullscreen_button = true,
51
+ fullscreen = false,
52
+ root = "",
53
+ file_types = ["image", "video"],
54
+ max_file_size = null,
55
+ upload,
56
+ stream_handler,
57
+ fit_columns = true,
58
+ buttons = null,
59
+ oncustom_button_click = null,
60
+ onpreview_open = () => {},
61
+ onchange = () => {},
62
+ onclear = () => {},
63
+ onselect = (data) => {},
64
+ onshare = (data) => {},
65
+ onerror = (error) => {},
66
+ onpreview_close = () => {},
67
+ onfullscreen = (data) => {},
68
+ ondelete = () => {},
69
+ onupload = () => {}
70
+ }: {
71
+ show_label: boolean;
72
+ label: string;
73
+ value: GalleryData[] | null;
74
+ columns: number | number[] | undefined;
75
+ rows: number | number[] | undefined;
76
+ height: number | "auto";
77
+ preview: boolean;
78
+ allow_preview: boolean;
79
+ object_fit: "contain" | "cover" | "fill" | "none" | "scale-down";
80
+ show_share_button: boolean;
81
+ show_download_button: boolean;
82
+ i18n: I18nFormatter;
83
+ selected_index: number | null;
84
+ interactive: boolean;
85
+ _fetch: typeof fetch;
86
+ show_fullscreen_button: boolean;
87
+ fullscreen: boolean;
88
+ root: string;
89
+ file_types: string[] | null;
90
+ max_file_size: number | null;
91
+ upload: Client["upload"] | undefined;
92
+ stream_handler: Client["stream"] | undefined;
93
+ fit_columns: boolean;
94
+ buttons: (string | CustomButtonType)[] | null;
95
+ oncustom_button_click: ((id: number) => void) | null;
96
+ onpreview_open: () => void;
97
+ onchange: () => void;
98
+ onclear: () => void;
99
+ onselect: (data: any) => void;
100
+ onshare: (data: any) => void;
101
+ onerror: (error: any) => void;
102
+ onpreview_close: () => void;
103
+ onfullscreen: (data: any) => void;
104
+ ondelete: (data: any) => void;
105
+ onupload: (data: FileData | FileData[]) => void;
106
+ } = $props();
107
+
108
+ let upload_promise: Promise<any> | null = null;
109
+ let mode: "normal" | "minimal" = "normal";
110
+ let display_icon_button_wrapper_top_corner = false;
64
111
  let is_full_screen = false;
65
112
  let image_container: HTMLElement;
66
113
 
67
- const dispatch = createEventDispatcher<{
68
- change: undefined;
69
- select: SelectData;
70
- preview_open: undefined;
71
- preview_close: undefined;
72
- fullscreen: boolean;
73
- delete: { file: FileData; index: number };
74
- upload: FileData | FileData[];
75
- error: string;
76
- clear: undefined;
77
- }>();
78
-
79
- // tracks whether the value of the gallery was reset
80
- let was_reset = true;
81
-
82
- $: was_reset = value == null || value.length === 0 ? true : was_reset;
114
+ let was_reset: boolean = $state(false);
83
115
 
84
- let resolved_value: GalleryData[] | null = null;
85
-
86
- $: resolved_value =
116
+ let resolved_value = $derived.by(() =>
87
117
  value == null
88
118
  ? null
89
119
  : (value.map((data) => {
@@ -96,55 +126,70 @@
96
126
  return { image: data.image as FileData, caption: data.caption };
97
127
  }
98
128
  return {};
99
- }) as GalleryData[]);
100
-
101
- let effective_columns: number | number[] | undefined = columns;
102
-
103
- $: {
129
+ }) as GalleryData[])
130
+ );
131
+
132
+ function resolve_effective_columns(
133
+ resolved_value: GalleryData[] | null,
134
+ columns: number | number[] | undefined,
135
+ fit_columns: boolean
136
+ ) {
104
137
  if (resolved_value && columns && fit_columns) {
105
138
  const item_count = resolved_value.length;
106
139
  if (Array.isArray(columns)) {
107
- effective_columns = columns.map((col) => Math.min(col, item_count));
140
+ return columns.map((col) => Math.min(col, item_count));
108
141
  } else {
109
- effective_columns = Math.min(columns, item_count);
142
+ return Math.min(columns, item_count);
110
143
  }
111
144
  } else {
112
- effective_columns = columns;
145
+ return columns;
113
146
  }
114
147
  }
115
148
 
116
- let prev_value: GalleryData[] | null = value;
117
- if (selected_index == null && preview && value?.length) {
118
- selected_index = 0;
119
- }
120
- let old_selected_index: number | null = selected_index;
121
-
122
- $: if (!dequal(prev_value, value)) {
123
- // When value is falsy (clear button or first load),
124
- // preview determines the selected image
125
- if (was_reset) {
126
- selected_index = preview && value?.length ? 0 : null;
127
- was_reset = false;
128
- // Otherwise we keep the selected_index the same if the
129
- // gallery has at least as many elements as it did before
130
- } else {
131
- if (selected_index !== null && value !== null) {
132
- selected_index = Math.max(
133
- 0,
134
- Math.min(selected_index, value.length - 1)
135
- );
149
+ let effective_columns: number | number[] | undefined = $derived.by(() =>
150
+ resolve_effective_columns(resolved_value, columns, fit_columns)
151
+ );
152
+
153
+ let prev_value: GalleryData[] | null = $state(value);
154
+ // if (selected_index == null && preview && value?.length) {
155
+ // selected_index = 0;
156
+ // }
157
+ let old_selected_index: number | null = $state(selected_index);
158
+
159
+ $effect(() => {
160
+ if (!dequal(prev_value, value)) {
161
+ // When value is falsy (clear button or first load),
162
+ // preview determines the selected image
163
+ if (was_reset) {
164
+ selected_index = preview && value?.length ? 0 : null;
165
+ was_reset = false;
166
+ // Otherwise we keep the selected_index the same if the
167
+ // gallery has at least as many elements as it did before
136
168
  } else {
137
- selected_index = null;
169
+ if (selected_index !== null && value !== null) {
170
+ selected_index = Math.max(
171
+ 0,
172
+ Math.min(selected_index, value.length - 1)
173
+ );
174
+ } else {
175
+ selected_index = null;
176
+ }
138
177
  }
178
+ onchange();
179
+ prev_value = value;
139
180
  }
140
- dispatch("change");
141
- prev_value = value;
142
- }
181
+ });
182
+
183
+ let previous = $derived.by(
184
+ () =>
185
+ ((selected_index ?? 0) + (resolved_value?.length ?? 0) - 1) %
186
+ (resolved_value?.length ?? 0)
187
+ );
188
+ let next = $derived.by(
189
+ () => ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0)
190
+ );
143
191
 
144
- $: previous =
145
- ((selected_index ?? 0) + (resolved_value?.length ?? 0) - 1) %
146
- (resolved_value?.length ?? 0);
147
- $: next = ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0);
192
+ $inspect("selected_index", selected_index);
148
193
 
149
194
  function handle_preview_click(event: MouseEvent): void {
150
195
  const element = event.target as HTMLElement;
@@ -164,7 +209,7 @@
164
209
  case "Escape":
165
210
  e.preventDefault();
166
211
  selected_index = null;
167
- dispatch("preview_close");
212
+ onpreview_close();
168
213
  break;
169
214
  case "ArrowLeft":
170
215
  e.preventDefault();
@@ -179,7 +224,7 @@
179
224
  }
180
225
  }
181
226
 
182
- $: {
227
+ $effect(() => {
183
228
  if (selected_index !== old_selected_index) {
184
229
  old_selected_index = selected_index;
185
230
  if (selected_index !== null) {
@@ -189,26 +234,29 @@
189
234
  Math.min(selected_index, resolved_value.length - 1)
190
235
  );
191
236
  }
192
- dispatch("select", {
237
+ onselect({
193
238
  index: selected_index,
194
239
  value: resolved_value?.[selected_index]
195
240
  });
196
241
  }
197
242
  }
198
- }
243
+ });
199
244
 
200
- $: if (allow_preview) {
201
- scroll_to_img(selected_index);
202
- }
245
+ $effect(() => {
246
+ if (allow_preview && container_element) {
247
+ scroll_to_img(selected_index);
248
+ }
249
+ });
203
250
 
204
251
  let el: HTMLButtonElement[] = [];
205
- let container_element: HTMLDivElement;
252
+ let container_element: HTMLDivElement | undefined = $state();
206
253
 
207
254
  async function scroll_to_img(index: number | null): Promise<void> {
208
255
  if (typeof index !== "number") return;
209
256
  await tick();
210
257
 
211
258
  if (el[index] === undefined) return;
259
+ if (!container_element) return;
212
260
 
213
261
  el[index]?.focus();
214
262
 
@@ -262,10 +310,11 @@
262
310
  URL.revokeObjectURL(url);
263
311
  }
264
312
 
265
- $: selected_media =
313
+ let selected_media = $derived.by(() =>
266
314
  selected_index != null && resolved_value != null
267
315
  ? resolved_value[selected_index]
268
- : null;
316
+ : null
317
+ );
269
318
 
270
319
  let thumbnails_overflow = false;
271
320
 
@@ -286,10 +335,13 @@
286
335
  window.removeEventListener("resize", check_thumbnails_overflow);
287
336
  });
288
337
 
289
- $: (resolved_value, check_thumbnails_overflow());
290
- $: if (container_element) {
338
+ $effect(() => {
339
+ resolved_value;
291
340
  check_thumbnails_overflow();
292
- }
341
+ if (container_element) {
342
+ check_thumbnails_overflow();
343
+ }
344
+ });
293
345
 
294
346
  function handle_item_delete(index: number): void {
295
347
  if (!value || !resolved_value) return;
@@ -310,7 +362,7 @@
310
362
  }
311
363
 
312
364
  if (deleted_file_data) {
313
- dispatch("delete", deleted_file_data);
365
+ ondelete(deleted_file_data);
314
366
  }
315
367
  }
316
368
 
@@ -335,13 +387,13 @@
335
387
  <IconButtonWrapper
336
388
  display_top_corner={display_icon_button_wrapper_top_corner}
337
389
  {buttons}
338
- {on_custom_button_click}
390
+ on_custom_button_click={oncustom_button_click}
339
391
  >
340
392
  {#if show_download_button}
341
393
  <IconButton
342
394
  Icon={Download}
343
395
  label={i18n("common.download")}
344
- on:click={() => {
396
+ onclick={() => {
345
397
  const image =
346
398
  "image" in selected_media
347
399
  ? selected_media?.image
@@ -358,15 +410,22 @@
358
410
  {/if}
359
411
 
360
412
  {#if show_fullscreen_button}
361
- <FullscreenButton {fullscreen} on:fullscreen />
413
+ <FullscreenButton
414
+ {fullscreen}
415
+ on:fullscreen={(e) => onfullscreen(e)}
416
+ />
362
417
  {/if}
363
418
 
364
419
  {#if show_share_button}
365
420
  <div class="icon-button">
366
421
  <ShareButton
367
422
  {i18n}
368
- on:share
369
- on:error
423
+ on:share={(detail) => {
424
+ onshare(detail);
425
+ }}
426
+ on:error={(detail) => {
427
+ onerror(detail);
428
+ }}
370
429
  value={resolved_value}
371
430
  formatter={format_gallery_for_sharing}
372
431
  />
@@ -376,9 +435,9 @@
376
435
  <IconButton
377
436
  Icon={Clear}
378
437
  label="Close"
379
- on:click={() => {
438
+ onclick={() => {
380
439
  selected_index = null;
381
- dispatch("preview_close");
440
+ onpreview_close();
382
441
  }}
383
442
  />
384
443
  {/if}
@@ -480,23 +539,26 @@
480
539
  {#if interactive && selected_index === null}
481
540
  <ModifyUpload
482
541
  {i18n}
483
- on:clear={() => {
542
+ onclear={() => {
484
543
  value = [];
485
- dispatch("clear");
544
+ onclear();
486
545
  }}
487
546
  >
488
547
  {#if upload && stream_handler}
489
- <IconButton Icon={UploadIcon} label={i18n("common.upload")}>
548
+ <IconButton
549
+ Icon={UploadIcon}
550
+ label={i18n("upload_text.click_to_upload")}
551
+ >
490
552
  <UploadComponent
491
553
  bind:upload_promise
492
554
  icon_upload={true}
493
- on:load={(e) => dispatch("upload", e.detail)}
555
+ onload={(e) => onupload(e)}
494
556
  filetype={file_types}
495
557
  file_count="multiple"
496
558
  {max_file_size}
497
559
  {root}
498
560
  bind:uploading
499
- on:error={(e) => dispatch("error", e.detail)}
561
+ onerror={(e) => onerror(e)}
500
562
  {stream_handler}
501
563
  {upload}
502
564
  />
@@ -516,7 +578,7 @@
516
578
  class:selected={selected_index === i}
517
579
  on:click={() => {
518
580
  if (selected_index === null && allow_preview) {
519
- dispatch("preview_open");
581
+ onpreview_open();
520
582
  }
521
583
  selected_index = i;
522
584
  }}