@gradio/gallery 0.15.35-dev.1 → 0.15.35
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 +48 -0
- package/Gallery.test.ts +5 -2
- package/Index.svelte +78 -93
- package/dist/Example.svelte +6 -3
- package/dist/Example.svelte.d.ts +20 -18
- package/dist/Index.svelte +122 -105
- package/dist/Index.svelte.d.ts +3 -60
- package/dist/shared/Gallery.svelte +338 -241
- package/dist/shared/Gallery.svelte.d.ts +57 -53
- package/dist/types.d.ts +30 -1
- package/package.json +13 -13
- package/shared/Gallery.svelte +27 -13
- package/types.ts +30 -1
|
@@ -1,231 +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
|
-
export let
|
|
49
|
-
let
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
let
|
|
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
|
-
}
|
|
228
|
-
|
|
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;
|
|
229
314
|
</script>
|
|
230
315
|
|
|
231
316
|
<svelte:window bind:innerHeight={window_height} />
|
|
@@ -238,7 +323,7 @@ let uploading = false;
|
|
|
238
323
|
{:else}
|
|
239
324
|
<div class="gallery-container" bind:this={image_container}>
|
|
240
325
|
{#if selected_media && allow_preview}
|
|
241
|
-
<
|
|
326
|
+
<span
|
|
242
327
|
on:keydown={on_keydown}
|
|
243
328
|
class="preview"
|
|
244
329
|
class:minimal={mode === "minimal"}
|
|
@@ -304,12 +389,14 @@ let uploading = false;
|
|
|
304
389
|
>
|
|
305
390
|
{#if "image" in selected_media}
|
|
306
391
|
<Image
|
|
307
|
-
|
|
392
|
+
restProps={{
|
|
393
|
+
alt: selected_media.caption || "",
|
|
394
|
+
title: selected_media.caption || null,
|
|
395
|
+
class: selected_media.caption && "with-caption",
|
|
396
|
+
loading: "lazy"
|
|
397
|
+
}}
|
|
308
398
|
src={selected_media.image.url}
|
|
309
|
-
|
|
310
|
-
title={selected_media.caption || null}
|
|
311
|
-
class={selected_media.caption && "with-caption"}
|
|
312
|
-
loading="lazy"
|
|
399
|
+
data_testid="detailed-image"
|
|
313
400
|
/>
|
|
314
401
|
{:else}
|
|
315
402
|
<Video
|
|
@@ -351,10 +438,13 @@ let uploading = false;
|
|
|
351
438
|
{#if "image" in media}
|
|
352
439
|
<Image
|
|
353
440
|
src={media.image.url}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
441
|
+
restProps={{
|
|
442
|
+
title: media.caption || null,
|
|
443
|
+
alt: "",
|
|
444
|
+
class: "with-caption",
|
|
445
|
+
loading: "lazy"
|
|
446
|
+
}}
|
|
447
|
+
data_testid={`thumbnail ${i + 1}`}
|
|
358
448
|
/>
|
|
359
449
|
{:else}
|
|
360
450
|
<Play />
|
|
@@ -371,7 +461,7 @@ let uploading = false;
|
|
|
371
461
|
</button>
|
|
372
462
|
{/each}
|
|
373
463
|
</div>
|
|
374
|
-
</
|
|
464
|
+
</span>
|
|
375
465
|
{/if}
|
|
376
466
|
|
|
377
467
|
<div
|
|
@@ -382,10 +472,17 @@ let uploading = false;
|
|
|
382
472
|
style:height={height !== "auto" ? height + "px" : null}
|
|
383
473
|
>
|
|
384
474
|
{#if interactive && selected_index === null}
|
|
385
|
-
<ModifyUpload
|
|
475
|
+
<ModifyUpload
|
|
476
|
+
{i18n}
|
|
477
|
+
on:clear={() => {
|
|
478
|
+
value = [];
|
|
479
|
+
dispatch("clear");
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
386
482
|
{#if upload && stream_handler}
|
|
387
483
|
<IconButton Icon={UploadIcon} label={i18n("common.upload")}>
|
|
388
484
|
<UploadComponent
|
|
485
|
+
bind:upload_promise
|
|
389
486
|
icon_upload={true}
|
|
390
487
|
on:load={(e) => dispatch("upload", e.detail)}
|
|
391
488
|
filetype={file_types}
|
|
@@ -464,7 +561,7 @@ let uploading = false;
|
|
|
464
561
|
</div>
|
|
465
562
|
{/if}
|
|
466
563
|
|
|
467
|
-
<style>
|
|
564
|
+
<style lang="postcss">
|
|
468
565
|
.image-container {
|
|
469
566
|
height: 100%;
|
|
470
567
|
position: relative;
|