@gradio/image 0.13.1 → 0.15.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,41 @@
1
1
  # @gradio/image
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Features
6
+
7
+ - [#9031](https://github.com/gradio-app/gradio/pull/9031) [`04b7d32`](https://github.com/gradio-app/gradio/commit/04b7d327ec1227a693fc2dfea51b1e2729851bde) - Allow drag and replace image in `gr.Image` and Multimodal textbox. Thanks @hannahblair!
8
+ - [#8930](https://github.com/gradio-app/gradio/pull/8930) [`41d5ab9`](https://github.com/gradio-app/gradio/commit/41d5ab987ba9728753be4509490c79041655809b) - Add `placeholder` param to Image and ImageEditor to replace upload image text. Thanks @hannahblair!
9
+ - [#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!
10
+
11
+ ### Fixes
12
+
13
+ - [#9116](https://github.com/gradio-app/gradio/pull/9116) [`ba6322e`](https://github.com/gradio-app/gradio/commit/ba6322ec2bb975f15389fe0700816bf671c6819d) - Fix image height content fit. Thanks @hannahblair!
14
+
15
+ ### Dependency updates
16
+
17
+ - @gradio/utils@0.6.0
18
+ - @gradio/upload@0.12.3
19
+ - @gradio/atoms@0.8.0
20
+ - @gradio/client@1.5.1
21
+ - @gradio/statustracker@0.7.5
22
+ - @gradio/wasm@0.13.0
23
+ - @gradio/icons@0.7.1
24
+
25
+ ## 0.14.0
26
+
27
+ ### Features
28
+
29
+ - [#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!
30
+
31
+ ### Dependency updates
32
+
33
+ - @gradio/atoms@0.7.9
34
+ - @gradio/statustracker@0.7.4
35
+ - @gradio/client@1.5.0
36
+ - @gradio/icons@0.7.0
37
+ - @gradio/upload@0.12.2
38
+
3
39
  ## 0.13.1
4
40
 
5
41
  ### Dependency updates
@@ -3,6 +3,8 @@
3
3
  import StaticImage from "./Index.svelte";
4
4
  import { userEvent, within } from "@storybook/test";
5
5
  import { allModes } from "../storybook/modes";
6
+ import image_file_100x100 from "../storybook/test_files/image_100x100.webp";
7
+ import image_file_100x1000 from "../storybook/test_files/image_100x100.webp";
6
8
 
7
9
  export const meta = {
8
10
  title: "Components/Image",
@@ -16,6 +18,8 @@
16
18
  }
17
19
  }
18
20
  };
21
+
22
+ let md = `# a heading! /n a new line! `;
19
23
  </script>
20
24
 
21
25
  <Template let:args>
@@ -28,7 +32,7 @@
28
32
  </Template>
29
33
 
30
34
  <Story
31
- name="static with label and download button"
35
+ name="static with label, info and download button"
32
36
  args={{
33
37
  value: {
34
38
  path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
@@ -36,8 +40,17 @@
36
40
  orig_name: "cheetah.jpg"
37
41
  },
38
42
  show_label: true,
43
+ placeholder: "This is a cheetah",
39
44
  show_download_button: true
40
45
  }}
46
+ play={async ({ canvasElement }) => {
47
+ const canvas = within(canvasElement);
48
+
49
+ const expand_btn = canvas.getByRole("button", {
50
+ name: "View in full screen"
51
+ });
52
+ await userEvent.click(expand_btn);
53
+ }}
41
54
  />
42
55
 
43
56
  <Story
@@ -53,6 +66,41 @@
53
66
  }}
54
67
  />
55
68
 
69
+ <Story
70
+ name="static with a vertically long image"
71
+ args={{
72
+ value: {
73
+ path: image_file_100x1000,
74
+ url: image_file_100x1000,
75
+ orig_name: "image.webp"
76
+ }
77
+ }}
78
+ />
79
+
80
+ <Story
81
+ name="static with a vertically long image and a fixed height"
82
+ args={{
83
+ value: {
84
+ path: image_file_100x1000,
85
+ url: image_file_100x1000,
86
+ orig_name: "image.webp"
87
+ },
88
+ height: "500px"
89
+ }}
90
+ />
91
+
92
+ <Story
93
+ name="static with a small image and a fixed height"
94
+ args={{
95
+ value: {
96
+ path: image_file_100x100,
97
+ url: image_file_100x100,
98
+ orig_name: "image.webp"
99
+ },
100
+ height: "500px"
101
+ }}
102
+ />
103
+
56
104
  <Story
57
105
  name="interactive with upload, clipboard, and webcam"
58
106
  args={{
@@ -64,7 +112,8 @@
64
112
  },
65
113
  show_label: false,
66
114
  show_download_button: false,
67
- interactive: true
115
+ interactive: true,
116
+ placeholder: md
68
117
  }}
69
118
  play={async ({ canvasElement }) => {
70
119
  const canvas = within(canvasElement);
package/Image.test.ts CHANGED
@@ -7,9 +7,8 @@ import {
7
7
  beforeAll,
8
8
  beforeEach
9
9
  } from "vitest";
10
- import { spy } from "tinyspy";
11
10
  import { cleanup, render } from "@gradio/tootils";
12
- import { setupi18n } from "../app/src/i18n";
11
+ import { setupi18n } from "../core/src/i18n";
13
12
 
14
13
  import Image from "./Index.svelte";
15
14
  import type { LoadingStatus } from "@gradio/statustracker";
package/Index.svelte CHANGED
@@ -51,6 +51,8 @@
51
51
  export let streaming: boolean;
52
52
  export let pending: boolean;
53
53
  export let mirror_webcam: boolean;
54
+ export let placeholder: string | undefined = undefined;
55
+ export let show_fullscreen_button: boolean;
54
56
 
55
57
  export let gradio: Gradio<{
56
58
  input: never;
@@ -81,6 +83,30 @@
81
83
 
82
84
  let dragging: boolean;
83
85
  let active_source: sources = null;
86
+ let upload_component: ImageUploader;
87
+ const handle_drag_event = (event: Event): void => {
88
+ const drag_event = event as DragEvent;
89
+ drag_event.preventDefault();
90
+ drag_event.stopPropagation();
91
+ if (drag_event.type === "dragenter" || drag_event.type === "dragover") {
92
+ dragging = true;
93
+ } else if (drag_event.type === "dragleave") {
94
+ dragging = false;
95
+ }
96
+ };
97
+
98
+ const handle_drop = (event: Event): void => {
99
+ if (interactive) {
100
+ const drop_event = event as DragEvent;
101
+ drop_event.preventDefault();
102
+ drop_event.stopPropagation();
103
+ dragging = false;
104
+
105
+ if (upload_component) {
106
+ upload_component.loadFilesFromDrop(drop_event);
107
+ }
108
+ }
109
+ };
84
110
  </script>
85
111
 
86
112
  {#if !interactive}
@@ -114,6 +140,7 @@
114
140
  selectable={_selectable}
115
141
  {show_share_button}
116
142
  i18n={gradio.i18n}
143
+ {show_fullscreen_button}
117
144
  />
118
145
  </Block>
119
146
  {:else}
@@ -130,6 +157,10 @@
130
157
  {container}
131
158
  {scale}
132
159
  {min_width}
160
+ on:dragenter={handle_drag_event}
161
+ on:dragleave={handle_drag_event}
162
+ on:dragover={handle_drag_event}
163
+ on:drop={handle_drop}
133
164
  >
134
165
  <StatusTracker
135
166
  autoscroll={gradio.autoscroll}
@@ -139,8 +170,10 @@
139
170
  />
140
171
 
141
172
  <ImageUploader
173
+ bind:this={upload_component}
142
174
  bind:active_source
143
175
  bind:value
176
+ bind:dragging
144
177
  selectable={_selectable}
145
178
  {root}
146
179
  {sources}
@@ -169,7 +202,7 @@
169
202
  stream_handler={gradio.client.stream}
170
203
  >
171
204
  {#if active_source === "upload" || !active_source}
172
- <UploadText i18n={gradio.i18n} type="image" />
205
+ <UploadText i18n={gradio.i18n} type="image" {placeholder} />
173
206
  {:else if active_source === "clipboard"}
174
207
  <UploadText i18n={gradio.i18n} type="clipboard" mode="short" />
175
208
  {:else}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/image",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -10,16 +10,16 @@
10
10
  "cropperjs": "^1.5.12",
11
11
  "lazy-brush": "^1.0.1",
12
12
  "resize-observer-polyfill": "^1.5.1",
13
- "@gradio/atoms": "^0.7.8",
14
- "@gradio/icons": "^0.6.1",
15
- "@gradio/statustracker": "^0.7.3",
16
- "@gradio/upload": "^0.12.1",
17
- "@gradio/client": "^1.4.0",
18
- "@gradio/wasm": "^0.12.0",
19
- "@gradio/utils": "^0.5.2"
13
+ "@gradio/atoms": "^0.8.0",
14
+ "@gradio/icons": "^0.7.1",
15
+ "@gradio/statustracker": "^0.7.5",
16
+ "@gradio/client": "^1.5.1",
17
+ "@gradio/utils": "^0.6.0",
18
+ "@gradio/wasm": "^0.13.0",
19
+ "@gradio/upload": "^0.12.3"
20
20
  },
21
21
  "devDependencies": {
22
- "@gradio/preview": "^0.10.2"
22
+ "@gradio/preview": "^0.11.0"
23
23
  },
24
24
  "main_changeset": true,
25
25
  "main": "./Index.svelte",
@@ -29,5 +29,10 @@
29
29
  "./example": "./Example.svelte",
30
30
  "./base": "./shared/ImagePreview.svelte",
31
31
  "./package.json": "./package.json"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/gradio-app/gradio.git",
36
+ "directory": "js/image"
32
37
  }
33
38
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher } from "svelte";
2
+ import { createEventDispatcher, onMount } from "svelte";
3
3
  import type { SelectData } from "@gradio/utils";
4
4
  import { uploadToHuggingFace } from "@gradio/utils";
5
5
  import { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
@@ -7,6 +7,7 @@
7
7
  import { get_coordinates_of_clicked_image } from "./utils";
8
8
  import Image from "./Image.svelte";
9
9
  import { DownloadLink } from "@gradio/wasm/svelte";
10
+ import { Maximize, Minimize } from "@gradio/icons";
10
11
 
11
12
  import { Image as ImageIcon } from "@gradio/icons";
12
13
  import { type FileData } from "@gradio/client";
@@ -19,6 +20,7 @@
19
20
  export let selectable = false;
20
21
  export let show_share_button = false;
21
22
  export let i18n: I18nFormatter;
23
+ export let show_fullscreen_button = true;
22
24
 
23
25
  const dispatch = createEventDispatcher<{
24
26
  change: string;
@@ -31,6 +33,24 @@
31
33
  dispatch("select", { index: coordinates, value: null });
32
34
  }
33
35
  };
36
+
37
+ let is_full_screen = false;
38
+ let image_container: HTMLElement;
39
+
40
+ onMount(() => {
41
+ document.addEventListener("fullscreenchange", () => {
42
+ is_full_screen = !!document.fullscreenElement;
43
+ });
44
+ });
45
+
46
+ const toggle_full_screen = async (): Promise<void> => {
47
+ if (!is_full_screen) {
48
+ await image_container.requestFullscreen();
49
+ } else {
50
+ await document.exitFullscreen();
51
+ is_full_screen = !is_full_screen;
52
+ }
53
+ };
34
54
  </script>
35
55
 
36
56
  <BlockLabel
@@ -41,44 +61,78 @@
41
61
  {#if value === null || !value.url}
42
62
  <Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
43
63
  {:else}
44
- <div class="icon-buttons">
45
- {#if show_download_button}
46
- <DownloadLink href={value.url} download={value.orig_name || "image"}>
47
- <IconButton Icon={Download} label={i18n("common.download")} />
48
- </DownloadLink>
49
- {/if}
50
- {#if show_share_button}
51
- <ShareButton
52
- {i18n}
53
- on:share
54
- on:error
55
- formatter={async (value) => {
56
- if (!value) return "";
57
- let url = await uploadToHuggingFace(value, "url");
58
- return `<img src="${url}" />`;
59
- }}
60
- {value}
61
- />
62
- {/if}
63
- </div>
64
- <button on:click={handle_click}>
65
- <div class:selectable class="image-container">
66
- <Image src={value.url} alt="" loading="lazy" on:load />
64
+ <div class="image-container" bind:this={image_container}>
65
+ <div class="icon-buttons">
66
+ {#if !is_full_screen && show_fullscreen_button}
67
+ <IconButton
68
+ Icon={Maximize}
69
+ label={is_full_screen ? "Exit full screen" : "View in full screen"}
70
+ on:click={toggle_full_screen}
71
+ />
72
+ {/if}
73
+
74
+ {#if is_full_screen && show_fullscreen_button}
75
+ <IconButton
76
+ Icon={Minimize}
77
+ label={is_full_screen ? "Exit full screen" : "View in full screen"}
78
+ on:click={toggle_full_screen}
79
+ />
80
+ {/if}
81
+
82
+ {#if show_download_button}
83
+ <DownloadLink href={value.url} download={value.orig_name || "image"}>
84
+ <IconButton Icon={Download} label={i18n("common.download")} />
85
+ </DownloadLink>
86
+ {/if}
87
+ {#if show_share_button}
88
+ <ShareButton
89
+ {i18n}
90
+ on:share
91
+ on:error
92
+ formatter={async (value) => {
93
+ if (!value) return "";
94
+ let url = await uploadToHuggingFace(value, "url");
95
+ return `<img src="${url}" />`;
96
+ }}
97
+ {value}
98
+ />
99
+ {/if}
67
100
  </div>
68
- </button>
101
+ <button on:click={handle_click}>
102
+ <div class:selectable class="image-frame">
103
+ <Image src={value.url} alt="" loading="lazy" on:load />
104
+ </div>
105
+ </button>
106
+ </div>
69
107
  {/if}
70
108
 
71
109
  <style>
72
110
  .image-container {
73
111
  height: 100%;
112
+ position: relative;
74
113
  }
75
- .image-container :global(img),
76
- button {
114
+
115
+ .image-container button {
77
116
  width: var(--size-full);
78
117
  height: var(--size-full);
79
- object-fit: contain;
80
- display: block;
81
118
  border-radius: var(--radius-lg);
119
+
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ }
124
+
125
+ .image-frame {
126
+ width: auto;
127
+ height: 100%;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ }
132
+ .image-frame :global(img) {
133
+ width: var(--size-full);
134
+ height: var(--size-full);
135
+ object-fit: scale-down;
82
136
  }
83
137
 
84
138
  .selectable {
@@ -91,5 +145,24 @@
91
145
  top: 6px;
92
146
  right: 6px;
93
147
  gap: var(--size-1);
148
+ z-index: 1;
149
+ }
150
+
151
+ :global(.fullscreen-controls svg) {
152
+ position: relative;
153
+ top: 0px;
154
+ }
155
+
156
+ :global(.image-container:fullscreen) {
157
+ background-color: black;
158
+ display: flex;
159
+ justify-content: center;
160
+ align-items: center;
161
+ }
162
+
163
+ :global(.image-container:fullscreen img) {
164
+ max-width: 90vw;
165
+ max-height: 90vh;
166
+ object-fit: scale-down;
94
167
  }
95
168
  </style>
@@ -70,7 +70,7 @@
70
70
  select: SelectData;
71
71
  }>();
72
72
 
73
- let dragging = false;
73
+ export let dragging = false;
74
74
 
75
75
  $: dispatch("drag", dragging);
76
76
 
@@ -109,7 +109,11 @@
109
109
  }}
110
110
  />
111
111
  {/if}
112
- <div class="upload-container" class:reduced-height={sources.length > 1}>
112
+ <div
113
+ class="upload-container"
114
+ class:reduced-height={sources.length > 1}
115
+ style:width={value ? "auto" : "100%"}
116
+ >
113
117
  <Upload
114
118
  hidden={value !== null || active_source === "webcam"}
115
119
  bind:this={upload_input}
@@ -120,7 +124,7 @@
120
124
  on:error
121
125
  {root}
122
126
  {max_file_size}
123
- disable_click={!sources.includes("upload")}
127
+ disable_click={!sources.includes("upload") || value !== null}
124
128
  {upload}
125
129
  {stream_handler}
126
130
  >
@@ -165,7 +169,7 @@
165
169
  .image-frame :global(img) {
166
170
  width: var(--size-full);
167
171
  height: var(--size-full);
168
- object-fit: contain;
172
+ object-fit: scale-down;
169
173
  }
170
174
 
171
175
  .image-frame {
@@ -175,10 +179,13 @@
175
179
  }
176
180
 
177
181
  .upload-container {
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+
178
186
  height: 100%;
179
187
  flex-shrink: 1;
180
188
  max-height: 100%;
181
- width: 100%;
182
189
  }
183
190
 
184
191
  .reduced-height {