@gradio/imageeditor 0.10.0 → 0.10.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/Index.svelte +5 -2
  3. package/dist/Example.svelte +41 -0
  4. package/dist/Example.svelte.d.ts +19 -0
  5. package/dist/Index.svelte +184 -0
  6. package/dist/Index.svelte.d.ts +192 -0
  7. package/dist/shared/Controls.svelte +73 -0
  8. package/dist/shared/Controls.svelte.d.ts +24 -0
  9. package/dist/shared/ImageEditor.svelte +329 -0
  10. package/dist/shared/ImageEditor.svelte.d.ts +79 -0
  11. package/dist/shared/InteractiveImageEditor.svelte +273 -0
  12. package/dist/shared/InteractiveImageEditor.svelte.d.ts +58 -0
  13. package/dist/shared/layers/Layers.svelte +216 -0
  14. package/dist/shared/layers/Layers.svelte.d.ts +18 -0
  15. package/dist/shared/layers/utils.d.ts +69 -0
  16. package/dist/shared/layers/utils.js +131 -0
  17. package/dist/shared/tools/Brush.svelte +208 -0
  18. package/dist/shared/tools/Brush.svelte.d.ts +29 -0
  19. package/dist/shared/tools/BrushOptions.svelte +162 -0
  20. package/dist/shared/tools/BrushOptions.svelte.d.ts +29 -0
  21. package/dist/shared/tools/BrushSize.svelte +60 -0
  22. package/dist/shared/tools/BrushSize.svelte.d.ts +20 -0
  23. package/dist/shared/tools/ColorField.svelte +159 -0
  24. package/dist/shared/tools/ColorField.svelte.d.ts +20 -0
  25. package/dist/shared/tools/ColorPicker.svelte +179 -0
  26. package/dist/shared/tools/ColorPicker.svelte.d.ts +16 -0
  27. package/dist/shared/tools/ColorSwatch.svelte +183 -0
  28. package/dist/shared/tools/ColorSwatch.svelte.d.ts +30 -0
  29. package/dist/shared/tools/Crop.svelte +155 -0
  30. package/dist/shared/tools/Crop.svelte.d.ts +16 -0
  31. package/dist/shared/tools/Cropper.svelte +271 -0
  32. package/dist/shared/tools/Cropper.svelte.d.ts +41 -0
  33. package/dist/shared/tools/Handle.svelte +260 -0
  34. package/dist/shared/tools/Handle.svelte.d.ts +29 -0
  35. package/dist/shared/tools/Sources.svelte +250 -0
  36. package/dist/shared/tools/Sources.svelte.d.ts +32 -0
  37. package/dist/shared/tools/Tools.svelte +101 -0
  38. package/dist/shared/tools/Tools.svelte.d.ts +48 -0
  39. package/dist/shared/tools/brush.d.ts +62 -0
  40. package/dist/shared/tools/brush.js +133 -0
  41. package/dist/shared/tools/crop.d.ts +15 -0
  42. package/dist/shared/tools/crop.js +154 -0
  43. package/dist/shared/tools/index.d.ts +5 -0
  44. package/dist/shared/tools/index.js +5 -0
  45. package/dist/shared/tools/sources.d.ts +40 -0
  46. package/dist/shared/tools/sources.js +69 -0
  47. package/dist/shared/tools/types.d.ts +1 -0
  48. package/dist/shared/tools/types.js +1 -0
  49. package/dist/shared/utils/commands.d.ts +96 -0
  50. package/dist/shared/utils/commands.js +65 -0
  51. package/dist/shared/utils/events.d.ts +7 -0
  52. package/dist/shared/utils/events.js +22 -0
  53. package/dist/shared/utils/parse_placeholder.d.ts +1 -0
  54. package/dist/shared/utils/parse_placeholder.js +19 -0
  55. package/dist/shared/utils/pixi.d.ts +76 -0
  56. package/dist/shared/utils/pixi.js +152 -0
  57. package/package.json +23 -12
  58. package/shared/ImageEditor.svelte +8 -4
  59. package/shared/InteractiveImageEditor.svelte +3 -1
  60. package/shared/tools/Brush.svelte +4 -6
  61. package/shared/tools/BrushOptions.svelte +1 -1
  62. package/shared/tools/Crop.svelte +2 -1
  63. package/shared/utils/pixi.ts +0 -1
@@ -0,0 +1,329 @@
1
+ <script context="module">import {} from "./utils/pixi";
2
+ import {} from "./utils/commands";
3
+ export const EDITOR_KEY = Symbol("editor");
4
+ import {} from "./tools";
5
+ </script>
6
+
7
+ <script>import { onMount, setContext, createEventDispatcher, tick } from "svelte";
8
+ import { writable } from "svelte/store";
9
+ import { spring } from "svelte/motion";
10
+ import { Rectangle } from "pixi.js";
11
+ import { command_manager } from "./utils/commands";
12
+ import {} from "./layers/utils";
13
+ import { create_pixi_app } from "./utils/pixi";
14
+ import Controls from "./Controls.svelte";
15
+ export let antialias = true;
16
+ export let crop_size;
17
+ export let changeable = false;
18
+ export let history;
19
+ export let bg = false;
20
+ export let sources;
21
+ const dispatch = createEventDispatcher();
22
+ export let crop_constraint = false;
23
+ export let canvas_size;
24
+ export let parent_height;
25
+ $:
26
+ orig_canvas_size = canvas_size;
27
+ const BASE_DIMENSIONS = canvas_size || [800, 600];
28
+ let dimensions = writable(BASE_DIMENSIONS);
29
+ export let height = 0;
30
+ let editor_box = writable({
31
+ parent_width: 0,
32
+ parent_height,
33
+ parent_top: 0,
34
+ parent_left: 0,
35
+ parent_right: 0,
36
+ parent_bottom: 0,
37
+ child_width: 0,
38
+ child_height: parent_height,
39
+ child_top: 0,
40
+ child_left: 0,
41
+ child_right: 0,
42
+ child_bottom: 0
43
+ });
44
+ $:
45
+ height = $editor_box.child_height;
46
+ const crop = writable([0, 0, 1, 1]);
47
+ const position_spring = spring(
48
+ { x: 0, y: 0 },
49
+ {
50
+ stiffness: 0.1,
51
+ damping: 0.5
52
+ }
53
+ );
54
+ const pixi = writable(null);
55
+ const CommandManager = command_manager();
56
+ const { can_redo, can_undo, current_history } = CommandManager;
57
+ $:
58
+ $current_history.previous, dispatch("change");
59
+ $: {
60
+ history = !!$current_history.previous || $active_tool !== "bg";
61
+ }
62
+ const is_browser = typeof window !== "undefined";
63
+ const active_tool = writable("bg");
64
+ const reset_context = writable({});
65
+ const init_context = writable({});
66
+ const contexts = writable([]);
67
+ const toolbar_box = writable(
68
+ is_browser ? new DOMRect() : {}
69
+ );
70
+ const sort_order = ["bg", "layers", "crop", "draw", "erase"];
71
+ const editor_context = setContext(EDITOR_KEY, {
72
+ pixi,
73
+ current_layer: writable(null),
74
+ dimensions,
75
+ editor_box,
76
+ toolbar_box,
77
+ active_tool,
78
+ crop,
79
+ position_spring,
80
+ command_manager: CommandManager,
81
+ current_history,
82
+ register_context: (type, {
83
+ reset_fn,
84
+ init_fn
85
+ }) => {
86
+ contexts.update((c) => [...c, type]);
87
+ init_context.update((c) => ({ ...c, [type]: init_fn }));
88
+ reset_context.update((c) => ({ ...c, [type]: reset_fn }));
89
+ },
90
+ reset: (clear_image, dimensions2) => {
91
+ bg = false;
92
+ const _sorted_contexts = $contexts.sort((a, b) => {
93
+ return sort_order.indexOf(a) - sort_order.indexOf(b);
94
+ });
95
+ for (const k of _sorted_contexts) {
96
+ if (k in $reset_context && typeof $reset_context[k] === "function") {
97
+ $reset_context[k]?.();
98
+ }
99
+ }
100
+ for (const k of _sorted_contexts) {
101
+ if (k in $init_context && typeof $init_context[k] === "function") {
102
+ if (k === "bg" && !clear_image) {
103
+ continue;
104
+ } else {
105
+ $init_context[k]?.(dimensions2);
106
+ }
107
+ }
108
+ CommandManager.reset();
109
+ $pixi?.resize?.(...dimensions2);
110
+ }
111
+ }
112
+ });
113
+ let pixi_target;
114
+ let canvas_wrap;
115
+ function get_dimensions(parent, child) {
116
+ if (!parent || !child)
117
+ return;
118
+ const {
119
+ width: parent_width,
120
+ height: parent_height2,
121
+ top: parent_top,
122
+ left: parent_left,
123
+ right: parent_right,
124
+ bottom: parent_bottom
125
+ } = canvas_wrap.getBoundingClientRect();
126
+ const {
127
+ width: child_width,
128
+ height: child_height,
129
+ top: child_top,
130
+ left: child_left,
131
+ right: child_right,
132
+ bottom: child_bottom
133
+ } = child.getBoundingClientRect();
134
+ editor_box.set({
135
+ child_width,
136
+ child_height,
137
+ child_left,
138
+ child_right,
139
+ child_top,
140
+ child_bottom,
141
+ parent_width,
142
+ parent_height: parent_height2,
143
+ parent_left,
144
+ parent_right,
145
+ parent_top,
146
+ parent_bottom
147
+ });
148
+ }
149
+ $:
150
+ if (crop_constraint && bg && !history) {
151
+ set_crop();
152
+ }
153
+ function set_crop() {
154
+ requestAnimationFrame(() => {
155
+ tick().then((v) => $active_tool = "crop");
156
+ });
157
+ }
158
+ function reposition_canvas() {
159
+ if (!$editor_box)
160
+ return;
161
+ const [l, t, w, h] = $crop;
162
+ const cx = l * $editor_box.child_width;
163
+ const cy = t * $editor_box.child_height;
164
+ const cw = w * $editor_box.child_width;
165
+ const ch = h * $editor_box.child_height;
166
+ const x = 0.5 * $editor_box.child_width - cx - cw / 2;
167
+ const y = 0.5 * $editor_box.child_height - cy - ch / 2;
168
+ position_spring.set({ x, y });
169
+ }
170
+ export async function get_blobs() {
171
+ if (!$pixi || !$pixi.get_layers)
172
+ return { background: null, layers: [], composite: null };
173
+ const [l, t, w, h] = $crop;
174
+ return $pixi?.get_blobs(
175
+ $pixi.get_layers(),
176
+ new Rectangle(
177
+ Math.round(l * $dimensions[0]),
178
+ Math.round(t * $dimensions[1]),
179
+ Math.round(w * $dimensions[0]),
180
+ Math.round(h * $dimensions[1])
181
+ ),
182
+ $dimensions
183
+ );
184
+ }
185
+ $:
186
+ $crop && reposition_canvas();
187
+ $:
188
+ $position_spring && get_dimensions(canvas_wrap, pixi_target);
189
+ export function handle_remove() {
190
+ editor_context.reset(
191
+ true,
192
+ orig_canvas_size ? orig_canvas_size : $dimensions
193
+ );
194
+ if (!sources.length) {
195
+ set_tool("draw");
196
+ } else {
197
+ set_tool("bg");
198
+ }
199
+ dispatch("clear");
200
+ }
201
+ onMount(() => {
202
+ const _size = (canvas_size ? canvas_size : crop_size) || [800, 600];
203
+ const app = create_pixi_app({
204
+ target: pixi_target,
205
+ dimensions: _size,
206
+ antialias
207
+ });
208
+ function resize(width, height2) {
209
+ app.resize(width, height2);
210
+ dimensions.set([width, height2]);
211
+ }
212
+ pixi.set({ ...app, resize });
213
+ const resizer = new ResizeObserver((entries) => {
214
+ for (const entry of entries) {
215
+ get_dimensions(canvas_wrap, pixi_target);
216
+ }
217
+ });
218
+ resizer.observe(canvas_wrap);
219
+ resizer.observe(pixi_target);
220
+ for (const k of $contexts) {
221
+ if (k in $init_context && typeof $init_context[k] === "function") {
222
+ $init_context[k]?.($dimensions);
223
+ }
224
+ }
225
+ resize(...$dimensions);
226
+ return () => {
227
+ $pixi?.destroy();
228
+ resizer.disconnect();
229
+ for (const k of $contexts) {
230
+ if (k in $reset_context) {
231
+ $reset_context[k]?.();
232
+ }
233
+ }
234
+ };
235
+ });
236
+ let saved_history = $current_history;
237
+ function handle_save() {
238
+ saved_history = $current_history;
239
+ dispatch("save");
240
+ }
241
+ export function set_tool(tool) {
242
+ $active_tool = tool;
243
+ }
244
+ </script>
245
+
246
+ <svelte:window on:scroll={() => get_dimensions(canvas_wrap, pixi_target)} />
247
+
248
+ <div data-testid="image" class="image-container">
249
+ <Controls
250
+ can_undo={$can_undo}
251
+ can_redo={$can_redo}
252
+ can_save={saved_history !== $current_history}
253
+ {changeable}
254
+ on:undo={CommandManager.undo}
255
+ on:redo={CommandManager.redo}
256
+ on:remove_image={handle_remove}
257
+ on:save={handle_save}
258
+ />
259
+ <div class="wrap" bind:this={canvas_wrap}>
260
+ <div
261
+ bind:this={pixi_target}
262
+ class="stage-wrap"
263
+ class:bg={!bg}
264
+ style:transform="translate({$position_spring.x}px, {$position_spring.y}px)"
265
+ ></div>
266
+ </div>
267
+ <div class="tools-wrap">
268
+ <slot />
269
+ </div>
270
+ <div
271
+ class="canvas"
272
+ class:no-border={!bg && $active_tool === "bg" && !history}
273
+ style:width="{$crop[2] * $editor_box.child_width + 1}px"
274
+ style:height="{$crop[3] * $editor_box.child_height + 1}px"
275
+ style:top="{$crop[1] * $editor_box.child_height +
276
+ ($editor_box.child_top - $editor_box.parent_top) -
277
+ 0.5}px"
278
+ style:left="{$crop[0] * $editor_box.child_width +
279
+ ($editor_box.child_left - $editor_box.parent_left) -
280
+ 0.5}px"
281
+ ></div>
282
+ </div>
283
+
284
+ <style>
285
+ .wrap {
286
+ display: flex;
287
+ width: 100%;
288
+ height: 100%;
289
+ position: relative;
290
+ justify-content: center;
291
+ align-items: flex-start;
292
+ }
293
+ .canvas {
294
+ position: absolute;
295
+ border: var(--block-border-color) 1px solid;
296
+ pointer-events: none;
297
+ border-radius: var(--radius-md);
298
+ }
299
+
300
+ .no-border {
301
+ border: none;
302
+ }
303
+
304
+ .stage-wrap {
305
+ margin: var(--size-8);
306
+ margin-bottom: var(--size-1);
307
+ border-radius: var(--radius-md);
308
+ overflow: hidden;
309
+ }
310
+
311
+ .tools-wrap {
312
+ display: flex;
313
+ justify-content: center;
314
+ align-items: center;
315
+ padding: 0 var(--spacing-xl) 0 0;
316
+ border: 1px solid var(--block-border-color);
317
+ border-radius: var(--radius-sm);
318
+ margin: var(--spacing-xxl) 0 var(--spacing-xxl) 0;
319
+ }
320
+
321
+ .image-container {
322
+ display: flex;
323
+ height: 100%;
324
+ flex-direction: column;
325
+ justify-content: center;
326
+ align-items: center;
327
+ max-height: 100%;
328
+ }
329
+ </style>
@@ -0,0 +1,79 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Writable } from "svelte/store";
3
+ import type { Spring } from "svelte/motion";
4
+ import { type PixiApp } from "./utils/pixi";
5
+ import { type CommandManager } from "./utils/commands";
6
+ export declare const EDITOR_KEY: unique symbol;
7
+ export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
8
+ import { type tool } from "./tools";
9
+ export interface EditorContext {
10
+ pixi: Writable<PixiApp | null>;
11
+ current_layer: Writable<LayerScene | null>;
12
+ dimensions: Writable<[number, number]>;
13
+ editor_box: Writable<{
14
+ parent_width: number;
15
+ parent_height: number;
16
+ parent_left: number;
17
+ parent_top: number;
18
+ parent_right: number;
19
+ parent_bottom: number;
20
+ child_width: number;
21
+ child_height: number;
22
+ child_left: number;
23
+ child_top: number;
24
+ child_right: number;
25
+ child_bottom: number;
26
+ }>;
27
+ active_tool: Writable<tool>;
28
+ toolbar_box: Writable<DOMRect | Record<string, never>>;
29
+ crop: Writable<[number, number, number, number]>;
30
+ position_spring: Spring<{
31
+ x: number;
32
+ y: number;
33
+ }>;
34
+ command_manager: CommandManager;
35
+ current_history: CommandManager["current_history"];
36
+ register_context: (type: context_type, { reset_fn, init_fn }: {
37
+ reset_fn?: () => void;
38
+ init_fn?: (dimensions?: [number, number]) => void;
39
+ }) => void;
40
+ reset: (clear_image: boolean, dimensions: [number, number]) => void;
41
+ }
42
+ import { type LayerScene } from "./layers/utils";
43
+ import { type ImageBlobs } from "./utils/pixi";
44
+ declare const __propDef: {
45
+ props: {
46
+ antialias?: boolean | undefined;
47
+ crop_size: [number, number] | undefined;
48
+ changeable?: boolean | undefined;
49
+ history: boolean;
50
+ bg?: boolean | undefined;
51
+ sources: ("clipboard" | "webcam" | "upload")[];
52
+ crop_constraint?: boolean | undefined;
53
+ canvas_size: [number, number] | undefined;
54
+ parent_height: number;
55
+ height?: number | undefined;
56
+ get_blobs?: (() => Promise<ImageBlobs>) | undefined;
57
+ handle_remove?: (() => void) | undefined;
58
+ set_tool?: ((tool: tool) => void) | undefined;
59
+ };
60
+ events: {
61
+ clear?: CustomEvent<undefined> | undefined;
62
+ save: CustomEvent<void>;
63
+ change: CustomEvent<void>;
64
+ } & {
65
+ [evt: string]: CustomEvent<any>;
66
+ };
67
+ slots: {
68
+ default: {};
69
+ };
70
+ };
71
+ export type ImageEditorProps = typeof __propDef.props;
72
+ export type ImageEditorEvents = typeof __propDef.events;
73
+ export type ImageEditorSlots = typeof __propDef.slots;
74
+ export default class ImageEditor extends SvelteComponent<ImageEditorProps, ImageEditorEvents, ImageEditorSlots> {
75
+ get get_blobs(): () => Promise<ImageBlobs>;
76
+ get handle_remove(): () => void;
77
+ get set_tool(): (tool: tool) => void;
78
+ }
79
+ export {};
@@ -0,0 +1,273 @@
1
+ <script context="module"></script>
2
+
3
+ <script>import { createEventDispatcher } from "svelte";
4
+ import {} from "@gradio/utils";
5
+ import { prepare_files } from "@gradio/client";
6
+ import ImageEditor from "./ImageEditor.svelte";
7
+ import Layers from "./layers/Layers.svelte";
8
+ import {} from "./tools/Brush.svelte";
9
+ import {} from "./tools/Brush.svelte";
10
+ import { Tools, Crop, Brush, Sources } from "./tools";
11
+ import { BlockLabel } from "@gradio/atoms";
12
+ import { Image as ImageIcon } from "@gradio/icons";
13
+ import { inject } from "./utils/parse_placeholder";
14
+ export let brush;
15
+ export let eraser;
16
+ export let sources;
17
+ export let crop_size = null;
18
+ export let i18n;
19
+ export let root;
20
+ export let label = void 0;
21
+ export let show_label;
22
+ export let changeable = false;
23
+ export let value = {
24
+ background: null,
25
+ layers: [],
26
+ composite: null
27
+ };
28
+ export let transforms = ["crop"];
29
+ export let layers;
30
+ export let accept_blobs;
31
+ export let status = "complete";
32
+ export let canvas_size;
33
+ export let realtime;
34
+ export let upload;
35
+ export let stream_handler;
36
+ export let dragging;
37
+ export let placeholder = void 0;
38
+ export let height = 400;
39
+ const dispatch = createEventDispatcher();
40
+ let editor;
41
+ function is_not_null(o) {
42
+ return !!o;
43
+ }
44
+ function is_file_data(o) {
45
+ return !!o;
46
+ }
47
+ $:
48
+ if (bg)
49
+ dispatch("upload");
50
+ export async function get_data() {
51
+ const blobs = await editor.get_blobs();
52
+ const bg2 = blobs.background ? upload(
53
+ await prepare_files([new File([blobs.background], "background.png")]),
54
+ root
55
+ ) : Promise.resolve(null);
56
+ const layers2 = blobs.layers.filter(is_not_null).map(
57
+ async (blob, i) => upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root)
58
+ );
59
+ const composite = blobs.composite ? upload(
60
+ await prepare_files([new File([blobs.composite], "composite.png")]),
61
+ root
62
+ ) : Promise.resolve(null);
63
+ const [background, composite_, ...layers_] = await Promise.all([
64
+ bg2,
65
+ composite,
66
+ ...layers2
67
+ ]);
68
+ return {
69
+ background: Array.isArray(background) ? background[0] : background,
70
+ layers: layers_.flatMap((layer) => Array.isArray(layer) ? layer : [layer]).filter(is_file_data),
71
+ composite: Array.isArray(composite_) ? composite_[0] : composite_
72
+ };
73
+ }
74
+ function handle_value(value2) {
75
+ if (!editor)
76
+ return;
77
+ if (value2 == null) {
78
+ editor.handle_remove();
79
+ }
80
+ }
81
+ $:
82
+ handle_value(value);
83
+ $:
84
+ crop_constraint = crop_size;
85
+ let bg = false;
86
+ let history = false;
87
+ export let image_id = null;
88
+ $:
89
+ editor && editor.set_tool && (sources && sources.length ? editor.set_tool("bg") : editor.set_tool("draw"));
90
+ function nextframe() {
91
+ return new Promise((resolve) => setTimeout(() => resolve(), 30));
92
+ }
93
+ let uploading = false;
94
+ let pending = false;
95
+ async function handle_change(e) {
96
+ if (!realtime)
97
+ return;
98
+ if (uploading) {
99
+ pending = true;
100
+ return;
101
+ }
102
+ uploading = true;
103
+ await nextframe();
104
+ const blobs = await editor.get_blobs();
105
+ const images = [];
106
+ let id = Math.random().toString(36).substring(2);
107
+ if (blobs.background)
108
+ images.push([
109
+ id,
110
+ "background",
111
+ new File([blobs.background], "background.png"),
112
+ null
113
+ ]);
114
+ if (blobs.composite)
115
+ images.push([
116
+ id,
117
+ "composite",
118
+ new File([blobs.composite], "composite.png"),
119
+ null
120
+ ]);
121
+ blobs.layers.forEach((layer, i) => {
122
+ if (layer)
123
+ images.push([
124
+ id,
125
+ `layer`,
126
+ new File([layer], `layer_${i}.png`),
127
+ i
128
+ ]);
129
+ });
130
+ await Promise.all(
131
+ images.map(async ([image_id2, type, data, index]) => {
132
+ return accept_blobs({
133
+ binary: true,
134
+ data: { file: data, id: image_id2, type, index }
135
+ });
136
+ })
137
+ );
138
+ image_id = id;
139
+ dispatch("change");
140
+ await nextframe();
141
+ uploading = false;
142
+ if (pending) {
143
+ pending = false;
144
+ uploading = false;
145
+ handle_change(e);
146
+ }
147
+ }
148
+ let active_mode = null;
149
+ let editor_height = height - 100;
150
+ $:
151
+ [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
152
+ </script>
153
+
154
+ <BlockLabel
155
+ {show_label}
156
+ Icon={ImageIcon}
157
+ label={label || i18n("image.image")}
158
+ />
159
+ <ImageEditor
160
+ {canvas_size}
161
+ crop_size={Array.isArray(crop_size) ? crop_size : undefined}
162
+ bind:this={editor}
163
+ bind:height={editor_height}
164
+ parent_height={height}
165
+ {changeable}
166
+ on:save
167
+ on:change={handle_change}
168
+ on:clear={() => dispatch("clear")}
169
+ bind:history
170
+ bind:bg
171
+ {sources}
172
+ crop_constraint={!!crop_constraint}
173
+ >
174
+ <Tools {i18n}>
175
+ <Layers layer_files={value?.layers || null} enable_layers={layers} />
176
+
177
+ <Sources
178
+ bind:dragging
179
+ {i18n}
180
+ {root}
181
+ {sources}
182
+ {upload}
183
+ {stream_handler}
184
+ bind:bg
185
+ bind:active_mode
186
+ background_file={value?.background || value?.composite || null}
187
+ ></Sources>
188
+
189
+ {#if transforms.includes("crop")}
190
+ <Crop {crop_constraint} />
191
+ {/if}
192
+ {#if brush}
193
+ <Brush
194
+ color_mode={brush.color_mode}
195
+ default_color={brush.default_color}
196
+ default_size={brush.default_size}
197
+ colors={brush.colors}
198
+ mode="draw"
199
+ />
200
+ {/if}
201
+
202
+ {#if brush && eraser}
203
+ <Brush default_size={eraser.default_size} mode="erase" />
204
+ {/if}
205
+ </Tools>
206
+
207
+ {#if !bg && !history && active_mode !== "webcam" && status !== "error"}
208
+ <div class="empty wrap" style:height={`${editor_height}px`}>
209
+ {#if sources && sources.length}
210
+ {#if heading || paragraph}
211
+ {#if heading}
212
+ <h2>{heading}</h2>
213
+ {/if}
214
+ {#if paragraph}
215
+ <p>{paragraph}</p>
216
+ {/if}
217
+ {:else}
218
+ <div>Upload an image</div>
219
+ {/if}
220
+ {/if}
221
+
222
+ {#if sources && sources.length && brush && !placeholder}
223
+ <div class="or">or</div>
224
+ {/if}
225
+ {#if brush && !placeholder}
226
+ <div>select the draw tool to start</div>
227
+ {/if}
228
+ </div>
229
+ {/if}
230
+ </ImageEditor>
231
+
232
+ <style>
233
+ h2 {
234
+ font-size: var(--text-xl);
235
+ }
236
+
237
+ p,
238
+ h2 {
239
+ white-space: pre-line;
240
+ }
241
+
242
+ .empty {
243
+ display: flex;
244
+ flex-direction: column;
245
+ justify-content: center;
246
+ align-items: center;
247
+ position: absolute;
248
+ height: 100%;
249
+ width: 100%;
250
+ left: 0;
251
+ right: 0;
252
+ margin: auto;
253
+ z-index: var(--layer-top);
254
+ text-align: center;
255
+ color: var(--body-text-color);
256
+ top: var(--size-8);
257
+ }
258
+
259
+ .wrap {
260
+ display: flex;
261
+ flex-direction: column;
262
+ justify-content: center;
263
+ align-items: center;
264
+ color: var(--block-label-text-color);
265
+ line-height: var(--line-md);
266
+ font-size: var(--text-md);
267
+ pointer-events: none;
268
+ }
269
+
270
+ .or {
271
+ color: var(--body-text-color-subdued);
272
+ }
273
+ </style>