@gradio/image 0.24.0-dev.2 → 0.25.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,46 @@
1
1
  # @gradio/image
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Features
6
+
7
+ - [#12539](https://github.com/gradio-app/gradio/pull/12539) [`f1d83fa`](https://github.com/gradio-app/gradio/commit/f1d83fac3d6e4bad60cf896a026fa2d572f26073) - Add ability to add custom buttons to components. Thanks @abidlabs!
8
+
9
+ ### Fixes
10
+
11
+ - [#12575](https://github.com/gradio-app/gradio/pull/12575) [`7498fac`](https://github.com/gradio-app/gradio/commit/7498fac6a1c575ff04920ad5178853843a3b270e) - Fix image buttons default value. Thanks @freddyaboulton!
12
+
13
+ ### Dependency updates
14
+
15
+ - @gradio/atoms@0.20.0
16
+ - @gradio/utils@0.11.0
17
+ - @gradio/client@2.0.1
18
+ - @gradio/statustracker@0.12.1
19
+ - @gradio/upload@0.17.3
20
+
21
+ ## 0.24.0
22
+
23
+ ### Dependency updates
24
+
25
+ - @gradio/utils@0.10.4
26
+
27
+ ## 0.24.0
28
+
29
+ ### Features
30
+
31
+ - [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Clear Error statuses
32
+ - [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Improve audio player UI in gr.Chatbot
33
+ - [#12438](https://github.com/gradio-app/gradio/pull/12438) [`25ffc03`](https://github.com/gradio-app/gradio/commit/25ffc0398f8feb43d817c02b2ab970c16de6d797) - Svelte5 migration and bugfix
34
+
35
+ ### Dependencies
36
+
37
+ - @gradio/atoms@0.19.0
38
+ - @gradio/client@2.0.0
39
+ - @gradio/icons@0.15.0
40
+ - @gradio/statustracker@0.12.0
41
+ - @gradio/upload@0.17.2
42
+ - @gradio/utils@0.10.3
43
+
3
44
  ## 0.24.0-dev.2
4
45
 
5
46
  ### Features
package/Image.test.ts CHANGED
@@ -29,10 +29,12 @@ describe("Image", () => {
29
29
  window.HTMLMediaElement.prototype.play = vi.fn();
30
30
  window.HTMLMediaElement.prototype.pause = vi.fn();
31
31
  });
32
- beforeEach(setupi18n);
32
+ beforeEach(async () => {
33
+ await setupi18n();
34
+ });
33
35
  afterEach(() => cleanup());
34
36
 
35
- test("image change event trigger fires when value is changed and only fires once", async () => {
37
+ test.skip("image change event trigger fires when value is changed and only fires once", async () => {
36
38
  const { component, listen } = await render(Image, {
37
39
  show_label: true,
38
40
  loading_status,
@@ -50,7 +52,8 @@ describe("Image", () => {
50
52
  // brush_color: "#000000",
51
53
  // brush_radius: 5,
52
54
  // mask_opacity: 0.5,
53
- interactive: true
55
+ interactive: true,
56
+ buttons: ["download", "share", "fullscreen"]
54
57
  });
55
58
 
56
59
  const mock = listen("change");
package/Index.svelte CHANGED
@@ -102,6 +102,8 @@
102
102
  autoscroll={gradio.shared.autoscroll}
103
103
  i18n={gradio.i18n}
104
104
  {...gradio.shared.loading_status}
105
+ on_clear_status={() =>
106
+ gradio.dispatch("clear_status", gradio.shared.loading_status)}
105
107
  />
106
108
  <StaticImage
107
109
  on:select={({ detail }) => gradio.dispatch("select", detail)}
@@ -117,6 +119,9 @@
117
119
  selectable={gradio.props._selectable}
118
120
  i18n={gradio.i18n}
119
121
  buttons={gradio.props.buttons}
122
+ on_custom_button_click={(id) => {
123
+ gradio.dispatch("custom_button_click", { id });
124
+ }}
120
125
  />
121
126
  </Block>
122
127
  {:else}
@@ -144,7 +149,7 @@
144
149
  autoscroll={gradio.shared.autoscroll}
145
150
  i18n={gradio.i18n}
146
151
  {...gradio.shared.loading_status}
147
- on:clear_status={() =>
152
+ on_clear_status={() =>
148
153
  gradio.dispatch("clear_status", gradio.shared.loading_status)}
149
154
  />
150
155
  {/if}
@@ -160,7 +165,9 @@
160
165
  {fullscreen}
161
166
  show_fullscreen_button={gradio.props.buttons === null
162
167
  ? true
163
- : gradio.props.buttons.includes("fullscreen")}
168
+ : gradio.props.buttons.some(
169
+ (btn) => typeof btn === "string" && btn === "fullscreen"
170
+ )}
164
171
  on:edit={() => gradio.dispatch("edit")}
165
172
  on:clear={() => {
166
173
  fullscreen = false;
package/dist/Index.svelte CHANGED
@@ -102,6 +102,8 @@
102
102
  autoscroll={gradio.shared.autoscroll}
103
103
  i18n={gradio.i18n}
104
104
  {...gradio.shared.loading_status}
105
+ on_clear_status={() =>
106
+ gradio.dispatch("clear_status", gradio.shared.loading_status)}
105
107
  />
106
108
  <StaticImage
107
109
  on:select={({ detail }) => gradio.dispatch("select", detail)}
@@ -117,6 +119,9 @@
117
119
  selectable={gradio.props._selectable}
118
120
  i18n={gradio.i18n}
119
121
  buttons={gradio.props.buttons}
122
+ on_custom_button_click={(id) => {
123
+ gradio.dispatch("custom_button_click", { id });
124
+ }}
120
125
  />
121
126
  </Block>
122
127
  {:else}
@@ -144,7 +149,7 @@
144
149
  autoscroll={gradio.shared.autoscroll}
145
150
  i18n={gradio.i18n}
146
151
  {...gradio.shared.loading_status}
147
- on:clear_status={() =>
152
+ on_clear_status={() =>
148
153
  gradio.dispatch("clear_status", gradio.shared.loading_status)}
149
154
  />
150
155
  {/if}
@@ -160,7 +165,9 @@
160
165
  {fullscreen}
161
166
  show_fullscreen_button={gradio.props.buttons === null
162
167
  ? true
163
- : gradio.props.buttons.includes("fullscreen")}
168
+ : gradio.props.buttons.some(
169
+ (btn) => typeof btn === "string" && btn === "fullscreen"
170
+ )}
164
171
  on:edit={() => gradio.dispatch("edit")}
165
172
  on:clear={() => {
166
173
  fullscreen = false;
@@ -11,6 +11,7 @@
11
11
  FullscreenButton,
12
12
  DownloadLink
13
13
  } from "@gradio/atoms";
14
+ import type { CustomButton as CustomButtonType } from "@gradio/utils";
14
15
  import { Download, Image as ImageIcon } from "@gradio/icons";
15
16
  import { get_coordinates_of_clicked_image } from "./utils";
16
17
  import Image from "./Image.svelte";
@@ -21,7 +22,8 @@
21
22
  export let value: null | FileData;
22
23
  export let label: string | undefined = undefined;
23
24
  export let show_label: boolean;
24
- export let buttons: string[] | null = null;
25
+ export let buttons: (string | CustomButtonType)[] = [];
26
+ export let on_custom_button_click: ((id: number) => void) | null = null;
25
27
  export let selectable = false;
26
28
  export let i18n: I18nFormatter;
27
29
  export let display_icon_button_wrapper_top_corner = false;
@@ -56,17 +58,18 @@
56
58
  <IconButtonWrapper
57
59
  display_top_corner={display_icon_button_wrapper_top_corner}
58
60
  show_background={show_button_background}
61
+ {buttons}
62
+ {on_custom_button_click}
59
63
  >
60
- {#if buttons === null ? true : buttons.includes("fullscreen")}
64
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "fullscreen")}
61
65
  <FullscreenButton {fullscreen} on:fullscreen />
62
66
  {/if}
63
-
64
- {#if buttons === null ? true : buttons.includes("download")}
67
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "download")}
65
68
  <DownloadLink href={value.url} download={value.orig_name || "image"}>
66
69
  <IconButton Icon={Download} label={i18n("common.download")} />
67
70
  </DownloadLink>
68
71
  {/if}
69
- {#if buttons === null ? true : buttons.includes("share")}
72
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
70
73
  <ShareButton
71
74
  {i18n}
72
75
  on:share
@@ -1,4 +1,5 @@
1
1
  import type { SelectData } from "@gradio/utils";
2
+ import type { CustomButton as CustomButtonType } from "@gradio/utils";
2
3
  import type { I18nFormatter } from "@gradio/utils";
3
4
  import type { FileData } from "@gradio/client";
4
5
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
@@ -18,7 +19,8 @@ declare const ImagePreview: $$__sveltets_2_IsomorphicComponent<{
18
19
  value: null | FileData;
19
20
  label?: string | undefined;
20
21
  show_label: boolean;
21
- buttons?: string[] | null;
22
+ buttons?: (string | CustomButtonType)[];
23
+ on_custom_button_click?: ((id: number) => void) | null;
22
24
  selectable?: boolean;
23
25
  i18n: I18nFormatter;
24
26
  display_icon_button_wrapper_top_corner?: boolean;
@@ -82,7 +82,6 @@
82
82
  img_blob: Blob | any,
83
83
  event: "change" | "stream" | "upload"
84
84
  ): Promise<void> {
85
- console.log("handle_save", { event, img_blob });
86
85
  if (event === "stream") {
87
86
  dispatch("stream", {
88
87
  value: { url: img_blob } as Base64File,
@@ -104,7 +103,6 @@
104
103
  ];
105
104
  pending = true;
106
105
  const f = await upload_input.load_files([f_], upload_id);
107
- console.log("uploaded file", f);
108
106
  if (event === "change" || event === "upload") {
109
107
  value = f?.[0] || null;
110
108
  await tick();
@@ -118,7 +118,6 @@
118
118
  video_source.videoWidth &&
119
119
  video_source.videoHeight
120
120
  ) {
121
- console.log("Taking picture from webcam");
122
121
  var context = canvas.getContext("2d")!;
123
122
  canvas.width = video_source.videoWidth;
124
123
  canvas.height = video_source.videoHeight;
@@ -1,5 +1,6 @@
1
1
  import type { LoadingStatus } from "@gradio/statustracker";
2
2
  import type { FileData } from "@gradio/client";
3
+ import type { CustomButton } from "@gradio/utils";
3
4
  export interface Base64File {
4
5
  url: string;
5
6
  alt_text: string;
@@ -15,7 +16,7 @@ export interface ImageProps {
15
16
  width: number;
16
17
  webcam_options: WebcamOptions;
17
18
  value: FileData | null;
18
- buttons: string[];
19
+ buttons: (string | CustomButton)[];
19
20
  pending: boolean;
20
21
  streaming: boolean;
21
22
  stream_every: number;
@@ -35,4 +36,7 @@ export interface ImageEvents {
35
36
  error: any;
36
37
  close_stream: void;
37
38
  edit: void;
39
+ custom_button_click: {
40
+ id: number;
41
+ };
38
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/image",
3
- "version": "0.24.0-dev.2",
3
+ "version": "0.25.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -10,15 +10,15 @@
10
10
  "cropperjs": "^2.0.1",
11
11
  "lazy-brush": "^2.0.2",
12
12
  "resize-observer-polyfill": "^1.5.1",
13
- "@gradio/atoms": "^0.19.0-dev.1",
14
- "@gradio/client": "^2.0.0-dev.2",
15
- "@gradio/statustracker": "^0.12.0-dev.1",
16
- "@gradio/upload": "^0.17.2-dev.2",
17
- "@gradio/utils": "^0.10.3-dev.0",
18
- "@gradio/icons": "^0.15.0-dev.0"
13
+ "@gradio/atoms": "^0.20.0",
14
+ "@gradio/upload": "^0.17.3",
15
+ "@gradio/icons": "^0.15.0",
16
+ "@gradio/statustracker": "^0.12.1",
17
+ "@gradio/utils": "^0.11.0",
18
+ "@gradio/client": "^2.0.1"
19
19
  },
20
20
  "devDependencies": {
21
- "@gradio/preview": "^0.15.0-dev.0"
21
+ "@gradio/preview": "^0.15.1"
22
22
  },
23
23
  "main_changeset": true,
24
24
  "main": "./Index.svelte",
@@ -11,6 +11,7 @@
11
11
  FullscreenButton,
12
12
  DownloadLink
13
13
  } from "@gradio/atoms";
14
+ import type { CustomButton as CustomButtonType } from "@gradio/utils";
14
15
  import { Download, Image as ImageIcon } from "@gradio/icons";
15
16
  import { get_coordinates_of_clicked_image } from "./utils";
16
17
  import Image from "./Image.svelte";
@@ -21,7 +22,8 @@
21
22
  export let value: null | FileData;
22
23
  export let label: string | undefined = undefined;
23
24
  export let show_label: boolean;
24
- export let buttons: string[] | null = null;
25
+ export let buttons: (string | CustomButtonType)[] = [];
26
+ export let on_custom_button_click: ((id: number) => void) | null = null;
25
27
  export let selectable = false;
26
28
  export let i18n: I18nFormatter;
27
29
  export let display_icon_button_wrapper_top_corner = false;
@@ -56,17 +58,18 @@
56
58
  <IconButtonWrapper
57
59
  display_top_corner={display_icon_button_wrapper_top_corner}
58
60
  show_background={show_button_background}
61
+ {buttons}
62
+ {on_custom_button_click}
59
63
  >
60
- {#if buttons === null ? true : buttons.includes("fullscreen")}
64
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "fullscreen")}
61
65
  <FullscreenButton {fullscreen} on:fullscreen />
62
66
  {/if}
63
-
64
- {#if buttons === null ? true : buttons.includes("download")}
67
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "download")}
65
68
  <DownloadLink href={value.url} download={value.orig_name || "image"}>
66
69
  <IconButton Icon={Download} label={i18n("common.download")} />
67
70
  </DownloadLink>
68
71
  {/if}
69
- {#if buttons === null ? true : buttons.includes("share")}
72
+ {#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
70
73
  <ShareButton
71
74
  {i18n}
72
75
  on:share
@@ -82,7 +82,6 @@
82
82
  img_blob: Blob | any,
83
83
  event: "change" | "stream" | "upload"
84
84
  ): Promise<void> {
85
- console.log("handle_save", { event, img_blob });
86
85
  if (event === "stream") {
87
86
  dispatch("stream", {
88
87
  value: { url: img_blob } as Base64File,
@@ -104,7 +103,6 @@
104
103
  ];
105
104
  pending = true;
106
105
  const f = await upload_input.load_files([f_], upload_id);
107
- console.log("uploaded file", f);
108
106
  if (event === "change" || event === "upload") {
109
107
  value = f?.[0] || null;
110
108
  await tick();
@@ -118,7 +118,6 @@
118
118
  video_source.videoWidth &&
119
119
  video_source.videoHeight
120
120
  ) {
121
- console.log("Taking picture from webcam");
122
121
  var context = canvas.getContext("2d")!;
123
122
  canvas.width = video_source.videoWidth;
124
123
  canvas.height = video_source.videoHeight;
@@ -5,7 +5,6 @@ import {
5
5
  set_available_devices,
6
6
  set_local_stream
7
7
  } from "./stream_utils";
8
- import * as stream_utils from "./stream_utils";
9
8
 
10
9
  let test_device: MediaDeviceInfo = {
11
10
  deviceId: "test-device",
@@ -31,7 +30,41 @@ const mock_getUserMedia = vi.fn(async () => {
31
30
  });
32
31
  });
33
32
 
34
- window.MediaStream = vi.fn().mockImplementation(() => ({}));
33
+ class MockMediaStream extends EventTarget {
34
+ id: string;
35
+ active: boolean;
36
+
37
+ constructor() {
38
+ super();
39
+ this.id = "mock-stream-id";
40
+ this.active = true;
41
+ }
42
+
43
+ getTracks(): MediaStreamTrack[] {
44
+ return [];
45
+ }
46
+
47
+ getAudioTracks(): MediaStreamTrack[] {
48
+ return [];
49
+ }
50
+
51
+ getVideoTracks(): MediaStreamTrack[] {
52
+ return [];
53
+ }
54
+
55
+ addTrack(): void {}
56
+ removeTrack(): void {}
57
+ getTrackById(): MediaStreamTrack | null {
58
+ return null;
59
+ }
60
+
61
+ clone(): MediaStream {
62
+ return this as unknown as MediaStream;
63
+ }
64
+ }
65
+
66
+ // @ts-ignore - Override global MediaStream for testing
67
+ window.MediaStream = MockMediaStream as any;
35
68
 
36
69
  Object.defineProperty(global.navigator, "mediaDevices", {
37
70
  value: {
@@ -47,10 +80,10 @@ describe("stream_utils", () => {
47
80
  });
48
81
 
49
82
  test("set_local_stream should set the local stream to the video source", () => {
50
- const mock_stream = {}; // mocked MediaStream obj as it's not available in a node env
83
+ const mock_stream = new MockMediaStream() as unknown as MediaStream;
51
84
 
52
85
  const mock_video_source = {
53
- srcObject: null,
86
+ srcObject: null as MediaStream | null,
54
87
  muted: false,
55
88
  play: vi.fn()
56
89
  };
@@ -64,28 +97,27 @@ describe("stream_utils", () => {
64
97
  });
65
98
 
66
99
  test("get_video_stream requests user media with the correct constraints and sets the local stream", async () => {
67
- const mock_video_source = document.createElement("video");
68
- const mock_stream = new MediaStream();
100
+ const mock_video_source = {
101
+ srcObject: null as MediaStream | null,
102
+ muted: false,
103
+ play: vi.fn().mockResolvedValue(undefined)
104
+ } as unknown as HTMLVideoElement;
105
+ const mock_stream = new MockMediaStream() as unknown as MediaStream;
69
106
 
70
107
  global.navigator.mediaDevices.getUserMedia = vi
71
108
  .fn()
72
109
  .mockResolvedValue(mock_stream);
73
110
 
74
- await get_video_stream(true, mock_video_source);
111
+ await get_video_stream(true, mock_video_source, null);
75
112
 
76
113
  expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({
77
114
  video: { width: { ideal: 1920 }, height: { ideal: 1440 } },
78
115
  audio: true
79
116
  });
80
117
 
81
- const spy_set_local_stream = vi.spyOn(stream_utils, "set_local_stream");
82
- stream_utils.set_local_stream(mock_stream, mock_video_source);
83
-
84
- expect(spy_set_local_stream).toHaveBeenCalledWith(
85
- mock_stream,
86
- mock_video_source
87
- );
88
- spy_set_local_stream.mockRestore();
118
+ expect(mock_video_source.srcObject).toBe(mock_stream);
119
+ expect(mock_video_source.muted).toBe(true);
120
+ expect(mock_video_source.play).toHaveBeenCalled();
89
121
  });
90
122
 
91
123
  test("set_available_devices should return only video input devices", () => {
package/shared/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { LoadingStatus } from "@gradio/statustracker";
2
2
  import type { FileData } from "@gradio/client";
3
+ import type { CustomButton } from "@gradio/utils";
3
4
 
4
5
  export interface Base64File {
5
6
  url: string;
@@ -18,7 +19,7 @@ export interface ImageProps {
18
19
  width: number;
19
20
  webcam_options: WebcamOptions;
20
21
  value: FileData | null;
21
- buttons: string[];
22
+ buttons: (string | CustomButton)[];
22
23
  pending: boolean;
23
24
  streaming: boolean;
24
25
  stream_every: number;
@@ -39,4 +40,5 @@ export interface ImageEvents {
39
40
  error: any;
40
41
  close_stream: void;
41
42
  edit: void;
43
+ custom_button_click: { id: number };
42
44
  }