@gradio/gallery 0.4.10 → 0.4.11

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,11 @@
1
1
  # @gradio/gallery
2
2
 
3
+ ## 0.4.11
4
+
5
+ ### Fixes
6
+
7
+ - [#6487](https://github.com/gradio-app/gradio/pull/6487) [`9a5811d`](https://github.com/gradio-app/gradio/commit/9a5811df9218b622af59ba243a937a9c36ba00f9) - Fix the download button of the `gr.Gallery()` component to work. Thanks [@whitphx](https://github.com/whitphx)!
8
+
3
9
  ## 0.4.10
4
10
 
5
11
  ### Patch Changes
@@ -240,4 +246,4 @@ Thanks [@pngwn](https://github.com/pngwn)!
240
246
 
241
247
  ### Features
242
248
 
243
- - [#4995](https://github.com/gradio-app/gradio/pull/4995) [`3f8c210b`](https://github.com/gradio-app/gradio/commit/3f8c210b01ef1ceaaf8ee73be4bf246b5b745bbf) - Implement left and right click in `Gallery` component and show implicit images in `Gallery` grid. Thanks [@hannahblair](https://github.com/hannahblair)!
249
+ - [#4995](https://github.com/gradio-app/gradio/pull/4995) [`3f8c210b`](https://github.com/gradio-app/gradio/commit/3f8c210b01ef1ceaaf8ee73be4bf246b5b745bbf) - Implement left and right click in `Gallery` component and show implicit images in `Gallery` grid. Thanks [@hannahblair](https://github.com/hannahblair)!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/gallery",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -8,13 +8,13 @@
8
8
  "private": false,
9
9
  "dependencies": {
10
10
  "dequal": "^2.0.2",
11
- "@gradio/client": "^0.8.2",
12
- "@gradio/atoms": "^0.3.1",
13
- "@gradio/image": "^0.4.2",
14
- "@gradio/icons": "^0.3.1",
15
- "@gradio/upload": "^0.5.2",
16
- "@gradio/utils": "^0.2.0",
17
- "@gradio/statustracker": "^0.4.1"
11
+ "@gradio/atoms": "^0.4.0",
12
+ "@gradio/client": "^0.9.0",
13
+ "@gradio/icons": "^0.3.2",
14
+ "@gradio/image": "^0.5.0",
15
+ "@gradio/statustracker": "^0.4.2",
16
+ "@gradio/upload": "^0.5.3",
17
+ "@gradio/utils": "^0.2.0"
18
18
  },
19
19
  "main": "./Index.svelte",
20
20
  "main_changeset": true,
@@ -12,11 +12,14 @@
12
12
  import { IconButton } from "@gradio/atoms";
13
13
  import type { I18nFormatter } from "@gradio/utils";
14
14
 
15
+ type GalleryImage = { image: FileData; caption: string | null };
16
+ type GalleryData = GalleryImage[];
17
+
15
18
  export let show_label = true;
16
19
  export let label: string;
17
20
  export let root = "";
18
21
  export let proxy_url: null | string = null;
19
- export let value: { image: FileData; caption: string | null }[] | null = null;
22
+ export let value: GalleryData | null = null;
20
23
  export let columns: number | number[] | undefined = [2];
21
24
  export let rows: number | number[] | undefined = undefined;
22
25
  export let height: number | "auto" = "auto";
@@ -37,25 +40,24 @@
37
40
  // tracks whether the value of the gallery was reset
38
41
  let was_reset = true;
39
42
 
40
- $: was_reset = value == null || value.length == 0 ? true : was_reset;
43
+ $: was_reset = value == null || value.length === 0 ? true : was_reset;
41
44
 
42
- let _value: { image: FileData; caption: string | null }[] | null = null;
43
- $: _value =
44
- value === null
45
+ let resolved_value: GalleryData | null = null;
46
+ $: resolved_value =
47
+ value == null
45
48
  ? null
46
49
  : value.map((data) => ({
47
50
  image: normalise_file(data.image, root, proxy_url) as FileData,
48
51
  caption: data.caption
49
52
  }));
50
53
 
51
- let prevValue: { image: FileData; caption: string | null }[] | null | null =
52
- value;
53
- if (selected_index === null && preview && value?.length) {
54
+ let prev_value: GalleryData | null = value;
55
+ if (selected_index == null && preview && value?.length) {
54
56
  selected_index = 0;
55
57
  }
56
58
  let old_selected_index: number | null = selected_index;
57
59
 
58
- $: if (!dequal(prevValue, value)) {
60
+ $: if (!dequal(prev_value, value)) {
59
61
  // When value is falsy (clear button or first load),
60
62
  // preview determines the selected image
61
63
  if (was_reset) {
@@ -65,19 +67,18 @@
65
67
  // gallery has at least as many elements as it did before
66
68
  } else {
67
69
  selected_index =
68
- selected_index !== null &&
69
- value !== null &&
70
- selected_index < value.length
70
+ selected_index != null && value != null && selected_index < value.length
71
71
  ? selected_index
72
72
  : null;
73
73
  }
74
74
  dispatch("change");
75
- prevValue = value;
75
+ prev_value = value;
76
76
  }
77
77
 
78
78
  $: previous =
79
- ((selected_index ?? 0) + (_value?.length ?? 0) - 1) % (_value?.length ?? 0);
80
- $: next = ((selected_index ?? 0) + 1) % (_value?.length ?? 0);
79
+ ((selected_index ?? 0) + (resolved_value?.length ?? 0) - 1) %
80
+ (resolved_value?.length ?? 0);
81
+ $: next = ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0);
81
82
 
82
83
  function handle_preview_click(event: MouseEvent): void {
83
84
  const element = event.target as HTMLElement;
@@ -111,28 +112,13 @@
111
112
  }
112
113
  }
113
114
 
114
- function isFileData(obj: any): obj is FileData {
115
- return typeof obj === "object" && obj !== null && "data" in obj;
116
- }
117
-
118
- function getHrefValue(selected: any): string {
119
- if (isFileData(selected)) {
120
- return selected.path;
121
- } else if (typeof selected === "string") {
122
- return selected;
123
- } else if (Array.isArray(selected)) {
124
- return getHrefValue(selected[0]);
125
- }
126
- return "";
127
- }
128
-
129
115
  $: {
130
116
  if (selected_index !== old_selected_index) {
131
117
  old_selected_index = selected_index;
132
118
  if (selected_index !== null) {
133
119
  dispatch("select", {
134
120
  index: selected_index,
135
- value: _value?.[selected_index]
121
+ value: resolved_value?.[selected_index]
136
122
  });
137
123
  }
138
124
  }
@@ -175,6 +161,39 @@
175
161
 
176
162
  let client_height = 0;
177
163
  let window_height = 0;
164
+
165
+ // Unlike `gr.Image()`, images specified via remote URLs are not cached in the server
166
+ // and their remote URLs are directly passed to the client as `value[].image.url`.
167
+ // The `download` attribute of the <a> tag doesn't work for remote URLs (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download),
168
+ // so we need to download the image via JS as below.
169
+ async function download(file_url: string, name: string): Promise<void> {
170
+ let response;
171
+ try {
172
+ response = await fetch(file_url);
173
+ } catch (error) {
174
+ if (error instanceof TypeError) {
175
+ // If CORS is not allowed (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#checking_that_the_fetch_was_successful),
176
+ // open the link in a new tab instead, mimicing the behavior of the `download` attribute for remote URLs,
177
+ // which is not ideal, but a reasonable fallback.
178
+ window.open(file_url, "_blank", "noreferrer");
179
+ return;
180
+ }
181
+
182
+ throw error;
183
+ }
184
+ const blob = await response.blob();
185
+ const url = URL.createObjectURL(blob);
186
+ const link = document.createElement("a");
187
+ link.href = url;
188
+ link.download = name;
189
+ link.click();
190
+ URL.revokeObjectURL(url);
191
+ }
192
+
193
+ $: selected_image =
194
+ selected_index != null && resolved_value != null
195
+ ? resolved_value[selected_index]
196
+ : null;
178
197
  </script>
179
198
 
180
199
  <svelte:window bind:innerHeight={window_height} />
@@ -182,20 +201,29 @@
182
201
  {#if show_label}
183
202
  <BlockLabel {show_label} Icon={Image} label={label || "Gallery"} />
184
203
  {/if}
185
- {#if value === null || _value === null || _value.length === 0}
204
+ {#if value == null || resolved_value == null || resolved_value.length === 0}
186
205
  <Empty unpadded_box={true} size="large"><Image /></Empty>
187
206
  {:else}
188
- {#if selected_index !== null && allow_preview}
207
+ {#if selected_image && allow_preview}
189
208
  <button on:keydown={on_keydown} class="preview">
190
209
  <div class="icon-buttons">
191
210
  {#if show_download_button}
192
- <a
193
- href={getHrefValue(value[selected_index])}
194
- target={window.__is_colab__ ? "_blank" : null}
195
- download="image"
196
- >
197
- <IconButton Icon={Download} label={i18n("common.download")} />
198
- </a>
211
+ <div class="download-button-container">
212
+ <IconButton
213
+ Icon={Download}
214
+ label={i18n("common.download")}
215
+ on:click={() => {
216
+ const image = selected_image?.image;
217
+ if (image == null) {
218
+ return;
219
+ }
220
+ const { url, orig_name } = image;
221
+ if (url) {
222
+ download(url, orig_name ?? "image");
223
+ }
224
+ }}
225
+ />
226
+ </div>
199
227
  {/if}
200
228
 
201
229
  <ModifyUpload
@@ -207,23 +235,21 @@
207
235
  <button
208
236
  class="image-button"
209
237
  on:click={(event) => handle_preview_click(event)}
210
- style="height: calc(100% - {_value[selected_index].caption
211
- ? '80px'
212
- : '60px'})"
238
+ style="height: calc(100% - {selected_image.caption ? '80px' : '60px'})"
213
239
  aria-label="detailed view of selected image"
214
240
  >
215
241
  <img
216
242
  data-testid="detailed-image"
217
- src={_value[selected_index].image.url}
218
- alt={_value[selected_index].caption || ""}
219
- title={_value[selected_index].caption || null}
220
- class:with-caption={!!_value[selected_index].caption}
243
+ src={selected_image.image.url}
244
+ alt={selected_image.caption || ""}
245
+ title={selected_image.caption || null}
246
+ class:with-caption={!!selected_image.caption}
221
247
  loading="lazy"
222
248
  />
223
249
  </button>
224
- {#if _value[selected_index]?.caption}
250
+ {#if selected_image?.caption}
225
251
  <caption class="caption">
226
- {_value[selected_index].caption}
252
+ {selected_image.caption}
227
253
  </caption>
228
254
  {/if}
229
255
  <div
@@ -231,13 +257,13 @@
231
257
  class="thumbnails scroll-hide"
232
258
  data-testid="container_el"
233
259
  >
234
- {#each _value as image, i}
260
+ {#each resolved_value as image, i}
235
261
  <button
236
262
  bind:this={el[i]}
237
263
  on:click={() => (selected_index = i)}
238
264
  class="thumbnail-item thumbnail-small"
239
265
  class:selected={selected_index === i}
240
- aria-label={"Thumbnail " + (i + 1) + " of " + _value.length}
266
+ aria-label={"Thumbnail " + (i + 1) + " of " + resolved_value.length}
241
267
  >
242
268
  <img
243
269
  src={image.image.url}
@@ -268,17 +294,17 @@
268
294
  {i18n}
269
295
  on:share
270
296
  on:error
271
- value={_value}
297
+ value={resolved_value}
272
298
  formatter={format_gallery_for_sharing}
273
299
  />
274
300
  </div>
275
301
  {/if}
276
- {#each _value as entry, i}
302
+ {#each resolved_value as entry, i}
277
303
  <button
278
304
  class="thumbnail-item thumbnail-lg"
279
305
  class:selected={selected_index === i}
280
306
  on:click={() => (selected_index = i)}
281
- aria-label={"Thumbnail " + (i + 1) + " of " + _value.length}
307
+ aria-label={"Thumbnail " + (i + 1) + " of " + resolved_value.length}
282
308
  >
283
309
  <img
284
310
  alt={entry.caption || ""}
@@ -465,7 +491,7 @@
465
491
  right: 0;
466
492
  }
467
493
 
468
- .icon-buttons a {
494
+ .icon-buttons .download-button-container {
469
495
  margin: var(--size-1) 0;
470
496
  }
471
497
  </style>