@gradio/imageeditor 0.20.1 → 0.21.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 +46 -0
- package/ImageEditor.test.ts +715 -0
- package/ImageEditorTestWrapper.svelte +81 -0
- package/Index.svelte +0 -2
- package/InteractiveImageEditor.svelte +64 -15
- package/dist/ImageEditorTestWrapper.svelte +81 -0
- package/dist/ImageEditorTestWrapper.svelte.d.ts +11 -0
- package/dist/Index.svelte +0 -2
- package/dist/InteractiveImageEditor.svelte +64 -15
- package/dist/shared/Controls.svelte +12 -9
- package/dist/shared/Controls.svelte.d.ts +2 -0
- package/dist/shared/ImageEditor.svelte +25 -9
- package/dist/shared/ImageEditor.svelte.d.ts +2 -1
- package/dist/shared/Layers.svelte +18 -5
- package/dist/shared/Layers.svelte.d.ts +2 -0
- package/dist/shared/Resize.svelte +1 -1
- package/dist/shared/SecondaryToolbar.svelte +4 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +2 -0
- package/dist/shared/Toolbar.svelte +15 -12
- package/dist/shared/Toolbar.svelte.d.ts +2 -0
- package/dist/shared/brush/brush-textures.js +8 -24
- package/dist/shared/resize/resize.js +10 -1
- package/package.json +8 -8
- package/shared/Controls.svelte +12 -9
- package/shared/ImageEditor.svelte +25 -9
- package/shared/Layers.svelte +18 -5
- package/shared/Resize.svelte +1 -1
- package/shared/SecondaryToolbar.svelte +4 -0
- package/shared/Toolbar.svelte +15 -12
- package/shared/brush/brush-textures.test.ts +77 -0
- package/shared/brush/brush-textures.ts +8 -27
- package/shared/resize/resize.ts +9 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
3
|
+
import { writable } from "svelte/store";
|
|
4
|
+
import Controls from "./shared/Controls.svelte";
|
|
5
|
+
import SecondaryToolbar from "./shared/SecondaryToolbar.svelte";
|
|
6
|
+
import Toolbar from "./shared/Toolbar.svelte";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
props
|
|
10
|
+
}: {
|
|
11
|
+
props: {
|
|
12
|
+
kind: "controls" | "toolbar" | "layers";
|
|
13
|
+
i18n?: I18nFormatter;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
};
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
const identity: I18nFormatter = (s: string) => s;
|
|
19
|
+
|
|
20
|
+
const default_brush = {
|
|
21
|
+
default_size: "auto" as const,
|
|
22
|
+
colors: ["#000000"],
|
|
23
|
+
default_color: "#000000",
|
|
24
|
+
color_mode: "defaults" as const
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const layers = writable(
|
|
28
|
+
props.layer_store ?? {
|
|
29
|
+
active_layer: "layer-1",
|
|
30
|
+
layers: [
|
|
31
|
+
{ name: "Layer 1", id: "layer-1", user_created: false, visible: true }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
{#if props.kind === "controls"}
|
|
38
|
+
<Controls
|
|
39
|
+
i18n={props.i18n ?? identity}
|
|
40
|
+
tool={props.tool ?? "image"}
|
|
41
|
+
can_undo={props.can_undo ?? false}
|
|
42
|
+
can_redo={props.can_redo ?? false}
|
|
43
|
+
can_save={props.can_save ?? false}
|
|
44
|
+
enable_download={props.enable_download ?? true}
|
|
45
|
+
changeable={props.changeable ?? true}
|
|
46
|
+
current_zoom={props.current_zoom ?? 1}
|
|
47
|
+
onundo={props.onundo}
|
|
48
|
+
onredo={props.onredo}
|
|
49
|
+
onsave={props.onsave}
|
|
50
|
+
onremove_image={props.onremove_image}
|
|
51
|
+
ondownload={props.ondownload}
|
|
52
|
+
onzoom_in={props.onzoom_in}
|
|
53
|
+
onzoom_out={props.onzoom_out}
|
|
54
|
+
onpan={props.onpan}
|
|
55
|
+
/>
|
|
56
|
+
{:else if props.kind === "toolbar"}
|
|
57
|
+
<Toolbar
|
|
58
|
+
i18n={props.i18n ?? identity}
|
|
59
|
+
brush_options={props.brush_options === undefined
|
|
60
|
+
? default_brush
|
|
61
|
+
: props.brush_options}
|
|
62
|
+
eraser_options={props.eraser_options === undefined
|
|
63
|
+
? { default_size: "auto" }
|
|
64
|
+
: props.eraser_options}
|
|
65
|
+
sources={props.sources ?? ["upload", "clipboard", "webcam"]}
|
|
66
|
+
transforms={props.transforms ?? []}
|
|
67
|
+
background={props.background ?? false}
|
|
68
|
+
tool={props.tool ?? "image"}
|
|
69
|
+
subtool={props.subtool ?? null}
|
|
70
|
+
ontool_change={props.ontool_change}
|
|
71
|
+
onsubtool_change={props.onsubtool_change}
|
|
72
|
+
/>
|
|
73
|
+
{:else}
|
|
74
|
+
<SecondaryToolbar
|
|
75
|
+
i18n={props.i18n ?? identity}
|
|
76
|
+
{layers}
|
|
77
|
+
enable_layers={props.enable_layers ?? true}
|
|
78
|
+
enable_additional_layers={props.enable_additional_layers ?? true}
|
|
79
|
+
onnew_layer={props.onnew_layer}
|
|
80
|
+
/>
|
|
81
|
+
{/if}
|
package/Index.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts"
|
|
1
|
+
<script module lang="ts">
|
|
2
2
|
export interface EditorData {
|
|
3
3
|
background: FileData | null;
|
|
4
4
|
layers: FileData[] | null;
|
|
@@ -115,11 +115,53 @@
|
|
|
115
115
|
return !!o;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
async function upload_original(
|
|
119
|
+
file_data: FileData | null,
|
|
120
|
+
fallback_name: string
|
|
121
|
+
): Promise<FileData | null> {
|
|
122
|
+
if (!file_data) return null;
|
|
123
|
+
|
|
124
|
+
let file = file_data.blob;
|
|
125
|
+
if (!file) {
|
|
126
|
+
if (!file_data.url) throw new Error("Image file has no URL");
|
|
127
|
+
const response = await fetch(file_data.url);
|
|
128
|
+
if (!response.ok) throw new Error("Image file could not be fetched");
|
|
129
|
+
const blob = await response.blob();
|
|
130
|
+
file = new File([blob], file_data.orig_name || fallback_name, {
|
|
131
|
+
type: file_data.mime_type || blob.type
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const uploaded = await upload(await prepare_files([file]), root);
|
|
136
|
+
return Array.isArray(uploaded) ? uploaded[0] : uploaded;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function upload_original_data(): Promise<ImageBlobs> {
|
|
140
|
+
const [uploaded_background, uploaded_composite, ...uploaded_layers] =
|
|
141
|
+
await Promise.all([
|
|
142
|
+
upload_original(background, "background.png"),
|
|
143
|
+
upload_original(composite, "composite.png"),
|
|
144
|
+
...layers.map((layer, i) => upload_original(layer, `layer_${i}.png`))
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
background: uploaded_background,
|
|
149
|
+
layers: uploaded_layers.filter(is_file_data),
|
|
150
|
+
composite: uploaded_composite
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
$effect(() => {
|
|
119
155
|
if (background_image) onupload?.();
|
|
120
156
|
});
|
|
121
157
|
|
|
122
158
|
export async function get_data(): Promise<ImageBlobs> {
|
|
159
|
+
if (!has_user_edits) {
|
|
160
|
+
try {
|
|
161
|
+
return await upload_original_data();
|
|
162
|
+
} catch {}
|
|
163
|
+
}
|
|
164
|
+
|
|
123
165
|
let blobs;
|
|
124
166
|
try {
|
|
125
167
|
blobs = await editor.get_blobs();
|
|
@@ -127,38 +169,43 @@
|
|
|
127
169
|
return { background: null, layers: [], composite: null };
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
const
|
|
172
|
+
const background_upload = blobs.background
|
|
131
173
|
? upload(
|
|
132
174
|
await prepare_files([new File([blobs.background], "background.png")]),
|
|
133
175
|
root
|
|
134
176
|
)
|
|
135
177
|
: Promise.resolve(null);
|
|
136
178
|
|
|
137
|
-
const
|
|
179
|
+
const layer_uploads = blobs.layers
|
|
138
180
|
.filter(is_not_null)
|
|
139
181
|
.map(async (blob, i) =>
|
|
140
182
|
upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root)
|
|
141
183
|
);
|
|
142
184
|
|
|
143
|
-
const
|
|
185
|
+
const composite_upload = blobs.composite
|
|
144
186
|
? upload(
|
|
145
187
|
await prepare_files([new File([blobs.composite], "composite.png")]),
|
|
146
188
|
root
|
|
147
189
|
)
|
|
148
190
|
: Promise.resolve(null);
|
|
149
191
|
|
|
150
|
-
const [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
192
|
+
const [uploaded_background, uploaded_composite, ...uploaded_layers] =
|
|
193
|
+
await Promise.all([
|
|
194
|
+
background_upload,
|
|
195
|
+
composite_upload,
|
|
196
|
+
...layer_uploads
|
|
197
|
+
]);
|
|
155
198
|
|
|
156
199
|
return {
|
|
157
|
-
background: Array.isArray(
|
|
158
|
-
|
|
200
|
+
background: Array.isArray(uploaded_background)
|
|
201
|
+
? uploaded_background[0]
|
|
202
|
+
: uploaded_background,
|
|
203
|
+
layers: uploaded_layers
|
|
159
204
|
.flatMap((layer) => (Array.isArray(layer) ? layer : [layer]))
|
|
160
205
|
.filter(is_file_data),
|
|
161
|
-
composite: Array.isArray(
|
|
206
|
+
composite: Array.isArray(uploaded_composite)
|
|
207
|
+
? uploaded_composite[0]
|
|
208
|
+
: uploaded_composite
|
|
162
209
|
};
|
|
163
210
|
}
|
|
164
211
|
|
|
@@ -176,6 +223,7 @@
|
|
|
176
223
|
|
|
177
224
|
let background_image = $state(false);
|
|
178
225
|
let can_undo = $state(false);
|
|
226
|
+
let has_user_edits = $state(false);
|
|
179
227
|
|
|
180
228
|
type BinaryImages = [string, string, File, number | null][];
|
|
181
229
|
|
|
@@ -282,6 +330,7 @@
|
|
|
282
330
|
{show_download_button}
|
|
283
331
|
{theme_mode}
|
|
284
332
|
bind:can_undo
|
|
333
|
+
bind:has_user_edits
|
|
285
334
|
bind:full_history
|
|
286
335
|
>
|
|
287
336
|
{#if current_tool === "image" && !can_undo}
|
|
@@ -295,15 +344,15 @@
|
|
|
295
344
|
<p>{paragraph}</p>
|
|
296
345
|
{/if}
|
|
297
346
|
{:else}
|
|
298
|
-
<div>
|
|
347
|
+
<div>{i18n("image_editor.upload_image")}</div>
|
|
299
348
|
{/if}
|
|
300
349
|
{/if}
|
|
301
350
|
|
|
302
351
|
{#if sources && sources.length && brush && !placeholder}
|
|
303
|
-
<div class="or">or</div>
|
|
352
|
+
<div class="or">{i18n("common.or")}</div>
|
|
304
353
|
{/if}
|
|
305
354
|
{#if brush && !placeholder}
|
|
306
|
-
<div>
|
|
355
|
+
<div>{i18n("image_editor.select_draw_tool")}</div>
|
|
307
356
|
{/if}
|
|
308
357
|
</div>
|
|
309
358
|
{/if}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
3
|
+
import { writable } from "svelte/store";
|
|
4
|
+
import Controls from "./shared/Controls.svelte";
|
|
5
|
+
import SecondaryToolbar from "./shared/SecondaryToolbar.svelte";
|
|
6
|
+
import Toolbar from "./shared/Toolbar.svelte";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
props
|
|
10
|
+
}: {
|
|
11
|
+
props: {
|
|
12
|
+
kind: "controls" | "toolbar" | "layers";
|
|
13
|
+
i18n?: I18nFormatter;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
};
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
const identity: I18nFormatter = (s: string) => s;
|
|
19
|
+
|
|
20
|
+
const default_brush = {
|
|
21
|
+
default_size: "auto" as const,
|
|
22
|
+
colors: ["#000000"],
|
|
23
|
+
default_color: "#000000",
|
|
24
|
+
color_mode: "defaults" as const
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const layers = writable(
|
|
28
|
+
props.layer_store ?? {
|
|
29
|
+
active_layer: "layer-1",
|
|
30
|
+
layers: [
|
|
31
|
+
{ name: "Layer 1", id: "layer-1", user_created: false, visible: true }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
{#if props.kind === "controls"}
|
|
38
|
+
<Controls
|
|
39
|
+
i18n={props.i18n ?? identity}
|
|
40
|
+
tool={props.tool ?? "image"}
|
|
41
|
+
can_undo={props.can_undo ?? false}
|
|
42
|
+
can_redo={props.can_redo ?? false}
|
|
43
|
+
can_save={props.can_save ?? false}
|
|
44
|
+
enable_download={props.enable_download ?? true}
|
|
45
|
+
changeable={props.changeable ?? true}
|
|
46
|
+
current_zoom={props.current_zoom ?? 1}
|
|
47
|
+
onundo={props.onundo}
|
|
48
|
+
onredo={props.onredo}
|
|
49
|
+
onsave={props.onsave}
|
|
50
|
+
onremove_image={props.onremove_image}
|
|
51
|
+
ondownload={props.ondownload}
|
|
52
|
+
onzoom_in={props.onzoom_in}
|
|
53
|
+
onzoom_out={props.onzoom_out}
|
|
54
|
+
onpan={props.onpan}
|
|
55
|
+
/>
|
|
56
|
+
{:else if props.kind === "toolbar"}
|
|
57
|
+
<Toolbar
|
|
58
|
+
i18n={props.i18n ?? identity}
|
|
59
|
+
brush_options={props.brush_options === undefined
|
|
60
|
+
? default_brush
|
|
61
|
+
: props.brush_options}
|
|
62
|
+
eraser_options={props.eraser_options === undefined
|
|
63
|
+
? { default_size: "auto" }
|
|
64
|
+
: props.eraser_options}
|
|
65
|
+
sources={props.sources ?? ["upload", "clipboard", "webcam"]}
|
|
66
|
+
transforms={props.transforms ?? []}
|
|
67
|
+
background={props.background ?? false}
|
|
68
|
+
tool={props.tool ?? "image"}
|
|
69
|
+
subtool={props.subtool ?? null}
|
|
70
|
+
ontool_change={props.ontool_change}
|
|
71
|
+
onsubtool_change={props.onsubtool_change}
|
|
72
|
+
/>
|
|
73
|
+
{:else}
|
|
74
|
+
<SecondaryToolbar
|
|
75
|
+
i18n={props.i18n ?? identity}
|
|
76
|
+
{layers}
|
|
77
|
+
enable_layers={props.enable_layers ?? true}
|
|
78
|
+
enable_additional_layers={props.enable_additional_layers ?? true}
|
|
79
|
+
onnew_layer={props.onnew_layer}
|
|
80
|
+
/>
|
|
81
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
props: {
|
|
4
|
+
kind: "controls" | "toolbar" | "layers";
|
|
5
|
+
i18n?: I18nFormatter;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
declare const ImageEditorTestWrapper: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type ImageEditorTestWrapper = ReturnType<typeof ImageEditorTestWrapper>;
|
|
11
|
+
export default ImageEditorTestWrapper;
|
package/dist/Index.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts"
|
|
1
|
+
<script module lang="ts">
|
|
2
2
|
export interface EditorData {
|
|
3
3
|
background: FileData | null;
|
|
4
4
|
layers: FileData[] | null;
|
|
@@ -115,11 +115,53 @@
|
|
|
115
115
|
return !!o;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
async function upload_original(
|
|
119
|
+
file_data: FileData | null,
|
|
120
|
+
fallback_name: string
|
|
121
|
+
): Promise<FileData | null> {
|
|
122
|
+
if (!file_data) return null;
|
|
123
|
+
|
|
124
|
+
let file = file_data.blob;
|
|
125
|
+
if (!file) {
|
|
126
|
+
if (!file_data.url) throw new Error("Image file has no URL");
|
|
127
|
+
const response = await fetch(file_data.url);
|
|
128
|
+
if (!response.ok) throw new Error("Image file could not be fetched");
|
|
129
|
+
const blob = await response.blob();
|
|
130
|
+
file = new File([blob], file_data.orig_name || fallback_name, {
|
|
131
|
+
type: file_data.mime_type || blob.type
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const uploaded = await upload(await prepare_files([file]), root);
|
|
136
|
+
return Array.isArray(uploaded) ? uploaded[0] : uploaded;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function upload_original_data(): Promise<ImageBlobs> {
|
|
140
|
+
const [uploaded_background, uploaded_composite, ...uploaded_layers] =
|
|
141
|
+
await Promise.all([
|
|
142
|
+
upload_original(background, "background.png"),
|
|
143
|
+
upload_original(composite, "composite.png"),
|
|
144
|
+
...layers.map((layer, i) => upload_original(layer, `layer_${i}.png`))
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
background: uploaded_background,
|
|
149
|
+
layers: uploaded_layers.filter(is_file_data),
|
|
150
|
+
composite: uploaded_composite
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
$effect(() => {
|
|
119
155
|
if (background_image) onupload?.();
|
|
120
156
|
});
|
|
121
157
|
|
|
122
158
|
export async function get_data(): Promise<ImageBlobs> {
|
|
159
|
+
if (!has_user_edits) {
|
|
160
|
+
try {
|
|
161
|
+
return await upload_original_data();
|
|
162
|
+
} catch {}
|
|
163
|
+
}
|
|
164
|
+
|
|
123
165
|
let blobs;
|
|
124
166
|
try {
|
|
125
167
|
blobs = await editor.get_blobs();
|
|
@@ -127,38 +169,43 @@
|
|
|
127
169
|
return { background: null, layers: [], composite: null };
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
const
|
|
172
|
+
const background_upload = blobs.background
|
|
131
173
|
? upload(
|
|
132
174
|
await prepare_files([new File([blobs.background], "background.png")]),
|
|
133
175
|
root
|
|
134
176
|
)
|
|
135
177
|
: Promise.resolve(null);
|
|
136
178
|
|
|
137
|
-
const
|
|
179
|
+
const layer_uploads = blobs.layers
|
|
138
180
|
.filter(is_not_null)
|
|
139
181
|
.map(async (blob, i) =>
|
|
140
182
|
upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root)
|
|
141
183
|
);
|
|
142
184
|
|
|
143
|
-
const
|
|
185
|
+
const composite_upload = blobs.composite
|
|
144
186
|
? upload(
|
|
145
187
|
await prepare_files([new File([blobs.composite], "composite.png")]),
|
|
146
188
|
root
|
|
147
189
|
)
|
|
148
190
|
: Promise.resolve(null);
|
|
149
191
|
|
|
150
|
-
const [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
192
|
+
const [uploaded_background, uploaded_composite, ...uploaded_layers] =
|
|
193
|
+
await Promise.all([
|
|
194
|
+
background_upload,
|
|
195
|
+
composite_upload,
|
|
196
|
+
...layer_uploads
|
|
197
|
+
]);
|
|
155
198
|
|
|
156
199
|
return {
|
|
157
|
-
background: Array.isArray(
|
|
158
|
-
|
|
200
|
+
background: Array.isArray(uploaded_background)
|
|
201
|
+
? uploaded_background[0]
|
|
202
|
+
: uploaded_background,
|
|
203
|
+
layers: uploaded_layers
|
|
159
204
|
.flatMap((layer) => (Array.isArray(layer) ? layer : [layer]))
|
|
160
205
|
.filter(is_file_data),
|
|
161
|
-
composite: Array.isArray(
|
|
206
|
+
composite: Array.isArray(uploaded_composite)
|
|
207
|
+
? uploaded_composite[0]
|
|
208
|
+
: uploaded_composite
|
|
162
209
|
};
|
|
163
210
|
}
|
|
164
211
|
|
|
@@ -176,6 +223,7 @@
|
|
|
176
223
|
|
|
177
224
|
let background_image = $state(false);
|
|
178
225
|
let can_undo = $state(false);
|
|
226
|
+
let has_user_edits = $state(false);
|
|
179
227
|
|
|
180
228
|
type BinaryImages = [string, string, File, number | null][];
|
|
181
229
|
|
|
@@ -282,6 +330,7 @@
|
|
|
282
330
|
{show_download_button}
|
|
283
331
|
{theme_mode}
|
|
284
332
|
bind:can_undo
|
|
333
|
+
bind:has_user_edits
|
|
285
334
|
bind:full_history
|
|
286
335
|
>
|
|
287
336
|
{#if current_tool === "image" && !can_undo}
|
|
@@ -295,15 +344,15 @@
|
|
|
295
344
|
<p>{paragraph}</p>
|
|
296
345
|
{/if}
|
|
297
346
|
{:else}
|
|
298
|
-
<div>
|
|
347
|
+
<div>{i18n("image_editor.upload_image")}</div>
|
|
299
348
|
{/if}
|
|
300
349
|
{/if}
|
|
301
350
|
|
|
302
351
|
{#if sources && sources.length && brush && !placeholder}
|
|
303
|
-
<div class="or">or</div>
|
|
352
|
+
<div class="or">{i18n("common.or")}</div>
|
|
304
353
|
{/if}
|
|
305
354
|
{#if brush && !placeholder}
|
|
306
|
-
<div>
|
|
355
|
+
<div>{i18n("image_editor.select_draw_tool")}</div>
|
|
307
356
|
{/if}
|
|
308
357
|
</div>
|
|
309
358
|
{/if}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
|
3
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
3
4
|
import {
|
|
4
5
|
Check,
|
|
5
6
|
Trash,
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
Redo
|
|
12
13
|
} from "@gradio/icons";
|
|
13
14
|
let {
|
|
15
|
+
i18n,
|
|
14
16
|
can_save = false,
|
|
15
17
|
changeable = false,
|
|
16
18
|
current_zoom = 1,
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
onpan,
|
|
30
32
|
ondownload
|
|
31
33
|
}: {
|
|
34
|
+
i18n: I18nFormatter;
|
|
32
35
|
can_save?: boolean;
|
|
33
36
|
changeable?: boolean;
|
|
34
37
|
current_zoom?: number;
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
{#if enable_download}
|
|
74
77
|
<IconButton
|
|
75
78
|
Icon={Download}
|
|
76
|
-
label="
|
|
79
|
+
label={i18n("common.download")}
|
|
77
80
|
onclick={(event) => {
|
|
78
81
|
ondownload?.();
|
|
79
82
|
event.stopPropagation();
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
|
|
84
87
|
<IconButton
|
|
85
88
|
Icon={Pan}
|
|
86
|
-
label="
|
|
89
|
+
label={i18n("image_editor.pan")}
|
|
87
90
|
onclick={(e) => {
|
|
88
91
|
e.stopPropagation();
|
|
89
92
|
onpan?.();
|
|
@@ -97,7 +100,7 @@
|
|
|
97
100
|
|
|
98
101
|
<IconButton
|
|
99
102
|
Icon={ZoomOut}
|
|
100
|
-
label="
|
|
103
|
+
label={i18n("image_editor.zoom_out")}
|
|
101
104
|
onclick={(event) => {
|
|
102
105
|
onzoom_out?.();
|
|
103
106
|
event.stopPropagation();
|
|
@@ -105,7 +108,7 @@
|
|
|
105
108
|
/>
|
|
106
109
|
<IconButton
|
|
107
110
|
Icon={ZoomIn}
|
|
108
|
-
label="
|
|
111
|
+
label={i18n("image_editor.zoom_in")}
|
|
109
112
|
onclick={(event) => {
|
|
110
113
|
onzoom_in?.();
|
|
111
114
|
event.stopPropagation();
|
|
@@ -129,7 +132,7 @@
|
|
|
129
132
|
handle_zoom_change("fit");
|
|
130
133
|
}}
|
|
131
134
|
>
|
|
132
|
-
|
|
135
|
+
{i18n("image_editor.fit_to_screen")}
|
|
133
136
|
</button>
|
|
134
137
|
</li>
|
|
135
138
|
{#each [0.25, 0.5, 1, 2, 4] as zoom}
|
|
@@ -152,7 +155,7 @@
|
|
|
152
155
|
|
|
153
156
|
<IconButton
|
|
154
157
|
Icon={Undo}
|
|
155
|
-
label="
|
|
158
|
+
label={i18n("common.undo")}
|
|
156
159
|
onclick={(event) => {
|
|
157
160
|
onundo?.();
|
|
158
161
|
event.stopPropagation();
|
|
@@ -162,7 +165,7 @@
|
|
|
162
165
|
|
|
163
166
|
<IconButton
|
|
164
167
|
Icon={Redo}
|
|
165
|
-
label="
|
|
168
|
+
label={i18n("image_editor.redo")}
|
|
166
169
|
onclick={(event) => {
|
|
167
170
|
onredo?.();
|
|
168
171
|
event.stopPropagation();
|
|
@@ -174,7 +177,7 @@
|
|
|
174
177
|
<IconButton
|
|
175
178
|
disabled={!can_save}
|
|
176
179
|
Icon={Check}
|
|
177
|
-
label="
|
|
180
|
+
label={i18n("image_editor.save_changes")}
|
|
178
181
|
onclick={(event) => {
|
|
179
182
|
onsave?.();
|
|
180
183
|
event.stopPropagation();
|
|
@@ -185,7 +188,7 @@
|
|
|
185
188
|
|
|
186
189
|
<IconButton
|
|
187
190
|
Icon={Trash}
|
|
188
|
-
label="
|
|
191
|
+
label={i18n("image_editor.clear_canvas")}
|
|
189
192
|
onclick={(event) => {
|
|
190
193
|
onremove_image?.();
|
|
191
194
|
event.stopPropagation();
|