@gradio/gallery 0.15.35-dev.0 → 0.15.35-dev.3
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 +35 -0
- package/Gallery.stories.svelte +25 -0
- package/Index.svelte +78 -91
- package/dist/Example.svelte +6 -3
- package/dist/Example.svelte.d.ts +20 -18
- package/dist/Index.svelte +122 -103
- package/dist/Index.svelte.d.ts +3 -59
- package/dist/shared/Gallery.svelte +338 -240
- package/dist/shared/Gallery.svelte.d.ts +57 -52
- package/dist/types.d.ts +30 -1
- package/package.json +13 -13
- package/shared/Gallery.svelte +29 -14
- package/types.ts +30 -1
|
@@ -1,230 +1,316 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export let
|
|
34
|
-
export let
|
|
35
|
-
export let
|
|
36
|
-
export let
|
|
37
|
-
export let
|
|
38
|
-
export let
|
|
39
|
-
export let
|
|
40
|
-
export let
|
|
41
|
-
export let
|
|
42
|
-
|
|
43
|
-
export let
|
|
44
|
-
export let
|
|
45
|
-
export let
|
|
46
|
-
export let
|
|
47
|
-
export let
|
|
48
|
-
let
|
|
49
|
-
let
|
|
50
|
-
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
$:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
$:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
BlockLabel,
|
|
4
|
+
Empty,
|
|
5
|
+
ShareButton,
|
|
6
|
+
IconButton,
|
|
7
|
+
IconButtonWrapper,
|
|
8
|
+
FullscreenButton
|
|
9
|
+
} from "@gradio/atoms";
|
|
10
|
+
import { ModifyUpload, Upload as UploadComponent } from "@gradio/upload";
|
|
11
|
+
import type { SelectData } from "@gradio/utils";
|
|
12
|
+
import { Image } from "@gradio/image/shared";
|
|
13
|
+
import { Video } from "@gradio/video/shared";
|
|
14
|
+
import { dequal } from "dequal";
|
|
15
|
+
import { createEventDispatcher, onMount } from "svelte";
|
|
16
|
+
import { tick } from "svelte";
|
|
17
|
+
import type { GalleryImage, GalleryVideo } from "../types";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
Download,
|
|
21
|
+
Image as ImageIcon,
|
|
22
|
+
Clear,
|
|
23
|
+
Play,
|
|
24
|
+
Upload as UploadIcon
|
|
25
|
+
} from "@gradio/icons";
|
|
26
|
+
import { FileData } from "@gradio/client";
|
|
27
|
+
import type { Client } from "@gradio/client";
|
|
28
|
+
import { format_gallery_for_sharing } from "./utils";
|
|
29
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
30
|
+
|
|
31
|
+
type GalleryData = GalleryImage | GalleryVideo;
|
|
32
|
+
|
|
33
|
+
export let show_label = true;
|
|
34
|
+
export let label: string;
|
|
35
|
+
export let value: GalleryData[] | null = null;
|
|
36
|
+
export let columns: number | number[] | undefined = [2];
|
|
37
|
+
export let rows: number | number[] | undefined = undefined;
|
|
38
|
+
export let height: number | "auto" = "auto";
|
|
39
|
+
export let preview: boolean;
|
|
40
|
+
export let allow_preview = true;
|
|
41
|
+
export let object_fit: "contain" | "cover" | "fill" | "none" | "scale-down" =
|
|
42
|
+
"cover";
|
|
43
|
+
export let show_share_button = false;
|
|
44
|
+
export let show_download_button = false;
|
|
45
|
+
export let i18n: I18nFormatter;
|
|
46
|
+
export let selected_index: number | null = null;
|
|
47
|
+
export let interactive: boolean;
|
|
48
|
+
export let _fetch: typeof fetch;
|
|
49
|
+
export let mode: "normal" | "minimal" = "normal";
|
|
50
|
+
export let show_fullscreen_button = true;
|
|
51
|
+
export let display_icon_button_wrapper_top_corner = false;
|
|
52
|
+
export let fullscreen = false;
|
|
53
|
+
export let root = "";
|
|
54
|
+
export let file_types: string[] | null = ["image", "video"];
|
|
55
|
+
export let max_file_size: number | null = null;
|
|
56
|
+
export let upload: Client["upload"] | undefined = undefined;
|
|
57
|
+
export let stream_handler: Client["stream"] | undefined = undefined;
|
|
58
|
+
export let fit_columns = true;
|
|
59
|
+
export let upload_promise: Promise<any> | null = null;
|
|
60
|
+
|
|
61
|
+
let is_full_screen = false;
|
|
62
|
+
let image_container: HTMLElement;
|
|
63
|
+
|
|
64
|
+
const dispatch = createEventDispatcher<{
|
|
65
|
+
change: undefined;
|
|
66
|
+
select: SelectData;
|
|
67
|
+
preview_open: undefined;
|
|
68
|
+
preview_close: undefined;
|
|
69
|
+
fullscreen: boolean;
|
|
70
|
+
delete: { file: FileData; index: number };
|
|
71
|
+
upload: FileData | FileData[];
|
|
72
|
+
error: string;
|
|
73
|
+
clear: undefined;
|
|
74
|
+
}>();
|
|
75
|
+
|
|
76
|
+
// tracks whether the value of the gallery was reset
|
|
77
|
+
let was_reset = true;
|
|
78
|
+
|
|
79
|
+
$: was_reset = value == null || value.length === 0 ? true : was_reset;
|
|
80
|
+
|
|
81
|
+
let resolved_value: GalleryData[] | null = null;
|
|
82
|
+
|
|
83
|
+
$: resolved_value =
|
|
84
|
+
value == null
|
|
85
|
+
? null
|
|
86
|
+
: (value.map((data) => {
|
|
87
|
+
if ("video" in data) {
|
|
88
|
+
return {
|
|
89
|
+
video: data.video as FileData,
|
|
90
|
+
caption: data.caption
|
|
91
|
+
};
|
|
92
|
+
} else if ("image" in data) {
|
|
93
|
+
return { image: data.image as FileData, caption: data.caption };
|
|
94
|
+
}
|
|
95
|
+
return {};
|
|
96
|
+
}) as GalleryData[]);
|
|
97
|
+
|
|
98
|
+
let effective_columns: number | number[] | undefined = columns;
|
|
99
|
+
|
|
100
|
+
$: {
|
|
101
|
+
if (resolved_value && columns && fit_columns) {
|
|
102
|
+
const item_count = resolved_value.length;
|
|
103
|
+
if (Array.isArray(columns)) {
|
|
104
|
+
effective_columns = columns.map((col) => Math.min(col, item_count));
|
|
105
|
+
} else {
|
|
106
|
+
effective_columns = Math.min(columns, item_count);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
effective_columns = columns;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let prev_value: GalleryData[] | null = value;
|
|
114
|
+
if (selected_index == null && preview && value?.length) {
|
|
115
|
+
selected_index = 0;
|
|
116
|
+
}
|
|
117
|
+
let old_selected_index: number | null = selected_index;
|
|
118
|
+
|
|
119
|
+
$: if (!dequal(prev_value, value)) {
|
|
120
|
+
// When value is falsy (clear button or first load),
|
|
121
|
+
// preview determines the selected image
|
|
122
|
+
if (was_reset) {
|
|
123
|
+
selected_index = preview && value?.length ? 0 : null;
|
|
124
|
+
was_reset = false;
|
|
125
|
+
// Otherwise we keep the selected_index the same if the
|
|
126
|
+
// gallery has at least as many elements as it did before
|
|
127
|
+
} else {
|
|
128
|
+
if (selected_index !== null && value !== null) {
|
|
129
|
+
selected_index = Math.max(
|
|
130
|
+
0,
|
|
131
|
+
Math.min(selected_index, value.length - 1)
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
selected_index = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
dispatch("change");
|
|
138
|
+
prev_value = value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$: previous =
|
|
142
|
+
((selected_index ?? 0) + (resolved_value?.length ?? 0) - 1) %
|
|
143
|
+
(resolved_value?.length ?? 0);
|
|
144
|
+
$: next = ((selected_index ?? 0) + 1) % (resolved_value?.length ?? 0);
|
|
145
|
+
|
|
146
|
+
function handle_preview_click(event: MouseEvent): void {
|
|
147
|
+
const element = event.target as HTMLElement;
|
|
148
|
+
const x = event.offsetX;
|
|
149
|
+
const width = element.offsetWidth;
|
|
150
|
+
const centerX = width / 2;
|
|
151
|
+
|
|
152
|
+
if (x < centerX) {
|
|
153
|
+
selected_index = previous;
|
|
154
|
+
} else {
|
|
155
|
+
selected_index = next;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function on_keydown(e: KeyboardEvent): void {
|
|
160
|
+
switch (e.code) {
|
|
161
|
+
case "Escape":
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
selected_index = null;
|
|
164
|
+
break;
|
|
165
|
+
case "ArrowLeft":
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
selected_index = previous;
|
|
168
|
+
break;
|
|
169
|
+
case "ArrowRight":
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
selected_index = next;
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
$: {
|
|
179
|
+
if (selected_index !== old_selected_index) {
|
|
180
|
+
old_selected_index = selected_index;
|
|
181
|
+
if (selected_index !== null) {
|
|
182
|
+
if (resolved_value != null) {
|
|
183
|
+
selected_index = Math.max(
|
|
184
|
+
0,
|
|
185
|
+
Math.min(selected_index, resolved_value.length - 1)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
dispatch("select", {
|
|
189
|
+
index: selected_index,
|
|
190
|
+
value: resolved_value?.[selected_index]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
$: if (allow_preview) {
|
|
197
|
+
scroll_to_img(selected_index);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let el: HTMLButtonElement[] = [];
|
|
201
|
+
let container_element: HTMLDivElement;
|
|
202
|
+
|
|
203
|
+
async function scroll_to_img(index: number | null): Promise<void> {
|
|
204
|
+
if (typeof index !== "number") return;
|
|
205
|
+
await tick();
|
|
206
|
+
|
|
207
|
+
if (el[index] === undefined) return;
|
|
208
|
+
|
|
209
|
+
el[index]?.focus();
|
|
210
|
+
|
|
211
|
+
const { left: container_left, width: container_width } =
|
|
212
|
+
container_element.getBoundingClientRect();
|
|
213
|
+
const { left, width } = el[index].getBoundingClientRect();
|
|
214
|
+
|
|
215
|
+
const relative_left = left - container_left;
|
|
216
|
+
|
|
217
|
+
const pos =
|
|
218
|
+
relative_left +
|
|
219
|
+
width / 2 -
|
|
220
|
+
container_width / 2 +
|
|
221
|
+
container_element.scrollLeft;
|
|
222
|
+
|
|
223
|
+
if (container_element && typeof container_element.scrollTo === "function") {
|
|
224
|
+
container_element.scrollTo({
|
|
225
|
+
left: pos < 0 ? 0 : pos,
|
|
226
|
+
behavior: "smooth"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let window_height = 0;
|
|
232
|
+
|
|
233
|
+
// Unlike `gr.Image()`, images specified via remote URLs are not cached in the server
|
|
234
|
+
// and their remote URLs are directly passed to the client as `value[].image.url`.
|
|
235
|
+
// 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),
|
|
236
|
+
// so we need to download the image via JS as below.
|
|
237
|
+
async function download(file_url: string, name: string): Promise<void> {
|
|
238
|
+
let response;
|
|
239
|
+
try {
|
|
240
|
+
response = await _fetch(file_url);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error instanceof TypeError) {
|
|
243
|
+
// If CORS is not allowed (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#checking_that_the_fetch_was_successful),
|
|
244
|
+
// open the link in a new tab instead, mimicing the behavior of the `download` attribute for remote URLs,
|
|
245
|
+
// which is not ideal, but a reasonable fallback.
|
|
246
|
+
window.open(file_url, "_blank", "noreferrer");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
const blob = await response.blob();
|
|
253
|
+
const url = URL.createObjectURL(blob);
|
|
254
|
+
const link = document.createElement("a");
|
|
255
|
+
link.href = url;
|
|
256
|
+
link.download = name;
|
|
257
|
+
link.click();
|
|
258
|
+
URL.revokeObjectURL(url);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
$: selected_media =
|
|
262
|
+
selected_index != null && resolved_value != null
|
|
263
|
+
? resolved_value[selected_index]
|
|
264
|
+
: null;
|
|
265
|
+
|
|
266
|
+
let thumbnails_overflow = false;
|
|
267
|
+
|
|
268
|
+
function check_thumbnails_overflow(): void {
|
|
269
|
+
if (container_element) {
|
|
270
|
+
thumbnails_overflow =
|
|
271
|
+
container_element.scrollWidth > container_element.clientWidth;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
onMount(() => {
|
|
276
|
+
check_thumbnails_overflow();
|
|
277
|
+
document.addEventListener("fullscreenchange", () => {
|
|
278
|
+
is_full_screen = !!document.fullscreenElement;
|
|
279
|
+
});
|
|
280
|
+
window.addEventListener("resize", check_thumbnails_overflow);
|
|
281
|
+
return () =>
|
|
282
|
+
window.removeEventListener("resize", check_thumbnails_overflow);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
$: (resolved_value, check_thumbnails_overflow());
|
|
286
|
+
$: if (container_element) {
|
|
287
|
+
check_thumbnails_overflow();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function handle_item_delete(index: number): void {
|
|
291
|
+
if (!value || !resolved_value) return;
|
|
292
|
+
|
|
293
|
+
const deleted_item = resolved_value[index];
|
|
294
|
+
let deleted_file_data;
|
|
295
|
+
|
|
296
|
+
if ("image" in deleted_item) {
|
|
297
|
+
deleted_file_data = {
|
|
298
|
+
file: deleted_item.image,
|
|
299
|
+
index: index
|
|
300
|
+
};
|
|
301
|
+
} else if ("video" in deleted_item) {
|
|
302
|
+
deleted_file_data = {
|
|
303
|
+
file: deleted_item.video,
|
|
304
|
+
index: index
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (deleted_file_data) {
|
|
309
|
+
dispatch("delete", deleted_file_data);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let uploading = false;
|
|
228
314
|
</script>
|
|
229
315
|
|
|
230
316
|
<svelte:window bind:innerHeight={window_height} />
|
|
@@ -237,7 +323,7 @@ let uploading = false;
|
|
|
237
323
|
{:else}
|
|
238
324
|
<div class="gallery-container" bind:this={image_container}>
|
|
239
325
|
{#if selected_media && allow_preview}
|
|
240
|
-
<
|
|
326
|
+
<span
|
|
241
327
|
on:keydown={on_keydown}
|
|
242
328
|
class="preview"
|
|
243
329
|
class:minimal={mode === "minimal"}
|
|
@@ -303,12 +389,14 @@ let uploading = false;
|
|
|
303
389
|
>
|
|
304
390
|
{#if "image" in selected_media}
|
|
305
391
|
<Image
|
|
306
|
-
|
|
392
|
+
restProps={{
|
|
393
|
+
alt: selected_media.caption || "",
|
|
394
|
+
title: selected_media.caption || null,
|
|
395
|
+
class: selected_media.caption && "with-caption",
|
|
396
|
+
loading: "lazy"
|
|
397
|
+
}}
|
|
307
398
|
src={selected_media.image.url}
|
|
308
|
-
|
|
309
|
-
title={selected_media.caption || null}
|
|
310
|
-
class={selected_media.caption && "with-caption"}
|
|
311
|
-
loading="lazy"
|
|
399
|
+
data_testid="detailed-image"
|
|
312
400
|
/>
|
|
313
401
|
{:else}
|
|
314
402
|
<Video
|
|
@@ -350,10 +438,13 @@ let uploading = false;
|
|
|
350
438
|
{#if "image" in media}
|
|
351
439
|
<Image
|
|
352
440
|
src={media.image.url}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
441
|
+
restProps={{
|
|
442
|
+
title: media.caption || null,
|
|
443
|
+
alt: "",
|
|
444
|
+
class: "with-caption",
|
|
445
|
+
loading: "lazy"
|
|
446
|
+
}}
|
|
447
|
+
data_testid={`thumbnail ${i + 1}`}
|
|
357
448
|
/>
|
|
358
449
|
{:else}
|
|
359
450
|
<Play />
|
|
@@ -370,7 +461,7 @@ let uploading = false;
|
|
|
370
461
|
</button>
|
|
371
462
|
{/each}
|
|
372
463
|
</div>
|
|
373
|
-
</
|
|
464
|
+
</span>
|
|
374
465
|
{/if}
|
|
375
466
|
|
|
376
467
|
<div
|
|
@@ -381,10 +472,17 @@ let uploading = false;
|
|
|
381
472
|
style:height={height !== "auto" ? height + "px" : null}
|
|
382
473
|
>
|
|
383
474
|
{#if interactive && selected_index === null}
|
|
384
|
-
<ModifyUpload
|
|
475
|
+
<ModifyUpload
|
|
476
|
+
{i18n}
|
|
477
|
+
on:clear={() => {
|
|
478
|
+
value = [];
|
|
479
|
+
dispatch("clear");
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
385
482
|
{#if upload && stream_handler}
|
|
386
483
|
<IconButton Icon={UploadIcon} label={i18n("common.upload")}>
|
|
387
484
|
<UploadComponent
|
|
485
|
+
bind:upload_promise
|
|
388
486
|
icon_upload={true}
|
|
389
487
|
on:load={(e) => dispatch("upload", e.detail)}
|
|
390
488
|
filetype={file_types}
|
|
@@ -463,7 +561,7 @@ let uploading = false;
|
|
|
463
561
|
</div>
|
|
464
562
|
{/if}
|
|
465
563
|
|
|
466
|
-
<style>
|
|
564
|
+
<style lang="postcss">
|
|
467
565
|
.image-container {
|
|
468
566
|
height: 100%;
|
|
469
567
|
position: relative;
|