@gradio/gallery 0.11.4 → 0.12.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.12.1
4
+
5
+ ### Features
6
+
7
+ - [#9118](https://github.com/gradio-app/gradio/pull/9118) [`e1c404d`](https://github.com/gradio-app/gradio/commit/e1c404da1143fb52b659d03e028bdba1badf443d) - setup npm-previews of all packages. Thanks @pngwn!
8
+
9
+ ### Dependency updates
10
+
11
+ - @gradio/utils@0.6.0
12
+ - @gradio/upload@0.12.3
13
+ - @gradio/atoms@0.8.0
14
+ - @gradio/client@1.5.1
15
+ - @gradio/statustracker@0.7.5
16
+ - @gradio/icons@0.7.1
17
+ - @gradio/image@0.15.0
18
+ - @gradio/file@0.9.3
19
+
20
+ ## 0.12.0
21
+
22
+ ### Features
23
+
24
+ - [#8964](https://github.com/gradio-app/gradio/pull/8964) [`bf6bbd9`](https://github.com/gradio-app/gradio/commit/bf6bbd971acddbf78f03bea431ed7d1e0003ccf9) - Add min/max-imize button to gr.Image and gr.Gallery. Thanks @hannahblair!
25
+
26
+ ### Dependency updates
27
+
28
+ - @gradio/atoms@0.7.9
29
+ - @gradio/statustracker@0.7.4
30
+ - @gradio/client@1.5.0
31
+ - @gradio/icons@0.7.0
32
+ - @gradio/upload@0.12.2
33
+ - @gradio/image@0.14.0
34
+ - @gradio/file@0.9.2
35
+
3
36
  ## 0.11.4
4
37
 
5
38
  ### Dependency updates
@@ -2,6 +2,8 @@
2
2
  import { Template, Story } from "@storybook/addon-svelte-csf";
3
3
  import Gallery from "./Index.svelte";
4
4
  import { allModes } from "../storybook/modes";
5
+ import { within } from "@testing-library/dom";
6
+ import { userEvent } from "@storybook/test";
5
7
 
6
8
  export const meta = {
7
9
  title: "Components/Gallery",
@@ -134,6 +136,16 @@
134
136
  <Story
135
137
  name="Gallery with label"
136
138
  args={{ label: "My Cheetah Gallery", show_label: true }}
139
+ play={async ({ canvasElement }) => {
140
+ const canvas = within(canvasElement);
141
+
142
+ const image = canvas.getByLabelText("Thumbnail 1 of 7");
143
+ await userEvent.click(image);
144
+ const expand_btn = canvas.getByRole("button", {
145
+ name: "View in full screen"
146
+ });
147
+ await userEvent.click(expand_btn);
148
+ }}
137
149
  />
138
150
  <Story
139
151
  name="Gallery without label"
package/Gallery.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { test, describe, assert, afterEach, vi } from "vitest";
2
2
  import { cleanup, render } from "@gradio/tootils";
3
- import { setupi18n } from "../app/src/i18n";
3
+ import { setupi18n } from "../core/src/i18n";
4
4
 
5
5
  import Gallery from "./Index.svelte";
6
6
  import type { LoadingStatus } from "@gradio/statustracker";
package/Index.svelte CHANGED
@@ -43,6 +43,7 @@
43
43
  prop_change: Record<string, any>;
44
44
  clear_status: LoadingStatus;
45
45
  }>;
46
+ export let show_fullscreen_button = true;
46
47
 
47
48
  const dispatch = createEventDispatcher();
48
49
 
@@ -113,6 +114,7 @@
113
114
  {show_download_button}
114
115
  i18n={gradio.i18n}
115
116
  _fetch={gradio.client.fetch}
117
+ {show_fullscreen_button}
116
118
  />
117
119
  {/if}
118
120
  </Block>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/gallery",
3
- "version": "0.11.4",
3
+ "version": "0.12.1",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -8,17 +8,17 @@
8
8
  "private": false,
9
9
  "dependencies": {
10
10
  "dequal": "^2.0.2",
11
- "@gradio/atoms": "^0.7.8",
12
- "@gradio/client": "^1.4.0",
13
- "@gradio/icons": "^0.6.1",
14
- "@gradio/image": "^0.13.1",
15
- "@gradio/utils": "^0.5.2",
16
- "@gradio/upload": "^0.12.1",
17
- "@gradio/statustracker": "^0.7.3",
18
- "@gradio/file": "^0.9.1"
11
+ "@gradio/atoms": "^0.8.0",
12
+ "@gradio/client": "^1.5.1",
13
+ "@gradio/icons": "^0.7.1",
14
+ "@gradio/image": "^0.15.0",
15
+ "@gradio/upload": "^0.12.3",
16
+ "@gradio/utils": "^0.6.0",
17
+ "@gradio/file": "^0.9.3",
18
+ "@gradio/statustracker": "^0.7.5"
19
19
  },
20
20
  "devDependencies": {
21
- "@gradio/preview": "^0.10.2"
21
+ "@gradio/preview": "^0.11.0"
22
22
  },
23
23
  "main": "./Index.svelte",
24
24
  "main_changeset": true,
@@ -26,5 +26,10 @@
26
26
  ".": "./Index.svelte",
27
27
  "./package.json": "./package.json",
28
28
  "./base": "./shared/Gallery.svelte"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/gradio-app/gradio.git",
33
+ "directory": "js/gallery"
29
34
  }
30
35
  }
@@ -4,10 +4,16 @@
4
4
  import type { SelectData } from "@gradio/utils";
5
5
  import { Image } from "@gradio/image/shared";
6
6
  import { dequal } from "dequal";
7
- import { createEventDispatcher } from "svelte";
7
+ import { createEventDispatcher, onMount } from "svelte";
8
8
  import { tick } from "svelte";
9
9
 
10
- import { Download, Image as ImageIcon } from "@gradio/icons";
10
+ import {
11
+ Download,
12
+ Image as ImageIcon,
13
+ Maximize,
14
+ Minimize,
15
+ Clear
16
+ } from "@gradio/icons";
11
17
  import { FileData } from "@gradio/client";
12
18
  import { format_gallery_for_sharing } from "./utils";
13
19
  import { IconButton } from "@gradio/atoms";
@@ -33,6 +39,10 @@
33
39
  export let interactive: boolean;
34
40
  export let _fetch: typeof fetch;
35
41
  export let mode: "normal" | "minimal" = "normal";
42
+ export let show_fullscreen_button = true;
43
+
44
+ let is_full_screen = false;
45
+ let gallery_container: HTMLElement;
36
46
 
37
47
  const dispatch = createEventDispatcher<{
38
48
  change: undefined;
@@ -195,6 +205,20 @@
195
205
  selected_index != null && resolved_value != null
196
206
  ? resolved_value[selected_index]
197
207
  : null;
208
+
209
+ onMount(() => {
210
+ document.addEventListener("fullscreenchange", () => {
211
+ is_full_screen = !!document.fullscreenElement;
212
+ });
213
+ });
214
+
215
+ const toggle_full_screen = async (): Promise<void> => {
216
+ if (!is_full_screen) {
217
+ await gallery_container.requestFullscreen();
218
+ } else {
219
+ await document.exitFullscreen();
220
+ }
221
+ };
198
222
  </script>
199
223
 
200
224
  <svelte:window bind:innerHeight={window_height} />
@@ -205,15 +229,15 @@
205
229
  {#if value == null || resolved_value == null || resolved_value.length === 0}
206
230
  <Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
207
231
  {:else}
208
- {#if selected_image && allow_preview}
209
- <button
210
- on:keydown={on_keydown}
211
- class="preview"
212
- class:minimal={mode === "minimal"}
213
- >
214
- <div class="icon-buttons">
215
- {#if show_download_button}
216
- <div class="download-button-container">
232
+ <div class="gallery-container" bind:this={gallery_container}>
233
+ {#if selected_image && allow_preview}
234
+ <button
235
+ on:keydown={on_keydown}
236
+ class="preview"
237
+ class:minimal={mode === "minimal"}
238
+ >
239
+ <div class="icon-buttons">
240
+ {#if show_download_button}
217
241
  <IconButton
218
242
  Icon={Download}
219
243
  label={i18n("common.download")}
@@ -228,117 +252,156 @@
228
252
  }
229
253
  }}
230
254
  />
231
- </div>
232
- {/if}
255
+ {/if}
233
256
 
234
- <ModifyUpload
235
- {i18n}
236
- absolute={false}
237
- on:clear={() => (selected_index = null)}
238
- />
239
- </div>
240
- <button
241
- class="image-button"
242
- on:click={(event) => handle_preview_click(event)}
243
- style="height: calc(100% - {selected_image.caption ? '80px' : '60px'})"
244
- aria-label="detailed view of selected image"
245
- >
246
- <Image
247
- data-testid="detailed-image"
248
- src={selected_image.image.url}
249
- alt={selected_image.caption || ""}
250
- title={selected_image.caption || null}
251
- class={selected_image.caption && "with-caption"}
252
- loading="lazy"
253
- />
257
+ {#if show_fullscreen_button && !is_full_screen}
258
+ <IconButton
259
+ Icon={is_full_screen ? Minimize : Maximize}
260
+ label={is_full_screen
261
+ ? "Exit full screen"
262
+ : "View in full screen"}
263
+ on:click={toggle_full_screen}
264
+ />
265
+ {/if}
266
+
267
+ {#if show_fullscreen_button && is_full_screen}
268
+ <IconButton
269
+ Icon={Minimize}
270
+ label="Exit full screen"
271
+ on:click={toggle_full_screen}
272
+ />
273
+ {/if}
274
+
275
+ {#if !is_full_screen}
276
+ <IconButton
277
+ Icon={Clear}
278
+ label="Close"
279
+ on:click={() => (selected_index = null)}
280
+ />
281
+ {/if}
282
+ </div>
283
+ <button
284
+ class="image-button"
285
+ on:click={(event) => handle_preview_click(event)}
286
+ style="height: calc(100% - {selected_image.caption
287
+ ? '80px'
288
+ : '60px'})"
289
+ aria-label="detailed view of selected image"
290
+ >
291
+ <Image
292
+ data-testid="detailed-image"
293
+ src={selected_image.image.url}
294
+ alt={selected_image.caption || ""}
295
+ title={selected_image.caption || null}
296
+ class={selected_image.caption && "with-caption"}
297
+ loading="lazy"
298
+ />
299
+ </button>
300
+ {#if selected_image?.caption}
301
+ <caption class="caption">
302
+ {selected_image.caption}
303
+ </caption>
304
+ {/if}
305
+ <div
306
+ bind:this={container_element}
307
+ class="thumbnails scroll-hide"
308
+ data-testid="container_el"
309
+ >
310
+ {#each resolved_value as image, i}
311
+ <button
312
+ bind:this={el[i]}
313
+ on:click={() => (selected_index = i)}
314
+ class="thumbnail-item thumbnail-small"
315
+ class:selected={selected_index === i && mode !== "minimal"}
316
+ aria-label={"Thumbnail " +
317
+ (i + 1) +
318
+ " of " +
319
+ resolved_value.length}
320
+ >
321
+ <Image
322
+ src={image.image.url}
323
+ title={image.caption || null}
324
+ data-testid={"thumbnail " + (i + 1)}
325
+ alt=""
326
+ loading="lazy"
327
+ />
328
+ </button>
329
+ {/each}
330
+ </div>
254
331
  </button>
255
- {#if selected_image?.caption}
256
- <caption class="caption">
257
- {selected_image.caption}
258
- </caption>
259
- {/if}
332
+ {/if}
333
+
334
+ <div
335
+ class="grid-wrap"
336
+ class:minimal={mode === "minimal"}
337
+ class:fixed-height={mode !== "minimal" && (!height || height == "auto")}
338
+ class:hidden={is_full_screen}
339
+ >
260
340
  <div
261
- bind:this={container_element}
262
- class="thumbnails scroll-hide"
263
- data-testid="container_el"
341
+ class="grid-container"
342
+ style="--grid-cols:{columns}; --grid-rows:{rows}; --object-fit: {object_fit}; height: {height};"
343
+ class:pt-6={show_label}
264
344
  >
265
- {#each resolved_value as image, i}
345
+ {#if interactive}
346
+ <div class="icon-button">
347
+ <ModifyUpload
348
+ {i18n}
349
+ absolute={false}
350
+ on:clear={() => (value = null)}
351
+ />
352
+ </div>
353
+ {/if}
354
+ {#if show_share_button}
355
+ <div class="icon-button">
356
+ <ShareButton
357
+ {i18n}
358
+ on:share
359
+ on:error
360
+ value={resolved_value}
361
+ formatter={format_gallery_for_sharing}
362
+ />
363
+ </div>
364
+ {/if}
365
+ {#each resolved_value as entry, i}
266
366
  <button
267
- bind:this={el[i]}
367
+ class="thumbnail-item thumbnail-lg"
368
+ class:selected={selected_index === i}
268
369
  on:click={() => (selected_index = i)}
269
- class="thumbnail-item thumbnail-small"
270
- class:selected={selected_index === i && mode !== "minimal"}
271
370
  aria-label={"Thumbnail " + (i + 1) + " of " + resolved_value.length}
272
371
  >
273
372
  <Image
274
- src={image.image.url}
275
- title={image.caption || null}
276
- data-testid={"thumbnail " + (i + 1)}
277
- alt=""
373
+ alt={entry.caption || ""}
374
+ src={typeof entry.image === "string"
375
+ ? entry.image
376
+ : entry.image.url}
278
377
  loading="lazy"
279
378
  />
379
+ {#if entry.caption}
380
+ <div class="caption-label">
381
+ {entry.caption}
382
+ </div>
383
+ {/if}
280
384
  </button>
281
385
  {/each}
282
386
  </div>
283
- </button>
284
- {/if}
285
-
286
- <div
287
- class="grid-wrap"
288
- class:minimal={mode === "minimal"}
289
- class:fixed-height={mode !== "minimal" && (!height || height == "auto")}
290
- >
291
- <div
292
- class="grid-container"
293
- style="--grid-cols:{columns}; --grid-rows:{rows}; --object-fit: {object_fit}; height: {height};"
294
- class:pt-6={show_label}
295
- >
296
- {#if interactive}
297
- <div class="icon-button">
298
- <ModifyUpload
299
- {i18n}
300
- absolute={false}
301
- on:clear={() => (value = null)}
302
- />
303
- </div>
304
- {/if}
305
- {#if show_share_button}
306
- <div class="icon-button">
307
- <ShareButton
308
- {i18n}
309
- on:share
310
- on:error
311
- value={resolved_value}
312
- formatter={format_gallery_for_sharing}
313
- />
314
- </div>
315
- {/if}
316
- {#each resolved_value as entry, i}
317
- <button
318
- class="thumbnail-item thumbnail-lg"
319
- class:selected={selected_index === i}
320
- on:click={() => (selected_index = i)}
321
- aria-label={"Thumbnail " + (i + 1) + " of " + resolved_value.length}
322
- >
323
- <Image
324
- alt={entry.caption || ""}
325
- src={typeof entry.image === "string"
326
- ? entry.image
327
- : entry.image.url}
328
- loading="lazy"
329
- />
330
- {#if entry.caption}
331
- <div class="caption-label">
332
- {entry.caption}
333
- </div>
334
- {/if}
335
- </button>
336
- {/each}
337
387
  </div>
338
388
  </div>
339
389
  {/if}
340
390
 
341
391
  <style lang="postcss">
392
+ .image-container {
393
+ height: 100%;
394
+ position: relative;
395
+ }
396
+ .image-container :global(img),
397
+ button {
398
+ width: var(--size-full);
399
+ height: var(--size-full);
400
+ object-fit: contain;
401
+ display: block;
402
+ border-radius: var(--radius-lg);
403
+ }
404
+
342
405
  .preview {
343
406
  display: flex;
344
407
  position: absolute;
@@ -400,6 +463,10 @@
400
463
  height: auto;
401
464
  }
402
465
 
466
+ .selectable {
467
+ cursor: crosshair;
468
+ }
469
+
403
470
  .caption {
404
471
  padding: var(--size-2) var(--size-3);
405
472
  overflow: hidden;
@@ -526,10 +593,9 @@
526
593
  display: flex;
527
594
  position: absolute;
528
595
  right: 0;
529
- }
530
-
531
- .icon-buttons .download-button-container {
532
- margin: var(--size-1) 0;
596
+ gap: var(--size-1);
597
+ z-index: 1;
598
+ margin: var(--size-1);
533
599
  }
534
600
 
535
601
  .grid-wrap.minimal {