@gradio/imageeditor 0.12.14 → 0.14.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 +36 -0
- package/Example.svelte +1 -1
- package/IMAGE_EDITOR_OVERVIEW.md +224 -0
- package/ImageEditor.stories.svelte +13 -5
- package/Index.svelte +46 -45
- package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +65 -103
- package/README.md +2 -0
- package/dist/Example.svelte.d.ts +1 -1
- package/dist/Index.svelte +35 -33
- package/dist/Index.svelte.d.ts +25 -16
- package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +54 -88
- package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +17 -15
- package/dist/shared/Anchor.svelte +311 -0
- package/dist/shared/Anchor.svelte.d.ts +18 -0
- package/dist/shared/Controls.svelte +169 -18
- package/dist/shared/Controls.svelte.d.ts +9 -2
- package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
- package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
- package/dist/shared/ImageEditor.svelte +572 -289
- package/dist/shared/ImageEditor.svelte.d.ts +54 -56
- package/dist/shared/Layers.svelte +260 -0
- package/dist/shared/Layers.svelte.d.ts +37 -0
- package/dist/shared/Resize.svelte +218 -0
- package/dist/shared/Resize.svelte.d.ts +29 -0
- package/dist/shared/SecondaryToolbar.svelte +55 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +37 -0
- package/dist/shared/Toolbar.svelte +337 -0
- package/dist/shared/Toolbar.svelte.d.ts +42 -0
- package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
- package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
- package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
- package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
- package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
- package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
- package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
- package/dist/shared/brush/brush-cursor.d.ts +101 -0
- package/dist/shared/brush/brush-cursor.js +327 -0
- package/dist/shared/brush/brush-textures.d.ts +65 -0
- package/dist/shared/brush/brush-textures.js +385 -0
- package/dist/shared/brush/brush-utils.d.ts +32 -0
- package/dist/shared/brush/brush-utils.js +59 -0
- package/dist/shared/brush/brush.d.ts +152 -0
- package/dist/shared/brush/brush.js +397 -0
- package/dist/shared/brush/types.d.ts +37 -0
- package/dist/shared/brush/types.js +1 -0
- package/dist/shared/core/editor.d.ts +261 -0
- package/dist/shared/core/editor.js +1264 -0
- package/dist/shared/crop/crop.d.ts +201 -0
- package/dist/shared/crop/crop.js +727 -0
- package/dist/shared/image/image.d.ts +89 -0
- package/dist/shared/image/image.js +183 -0
- package/dist/shared/resize/resize.d.ts +270 -0
- package/dist/shared/resize/resize.js +1413 -0
- package/dist/shared/types.d.ts +16 -0
- package/dist/shared/utils/pixi.d.ts +16 -57
- package/dist/shared/utils/pixi.js +15 -113
- package/dist/shared/zoom/zoom.d.ts +136 -0
- package/dist/shared/zoom/zoom.js +548 -0
- package/package.json +8 -7
- package/shared/Anchor.svelte +384 -0
- package/shared/Controls.svelte +178 -37
- package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
- package/shared/ImageEditor.svelte +712 -350
- package/shared/Layers.svelte +282 -0
- package/shared/Resize.svelte +239 -0
- package/shared/SecondaryToolbar.svelte +67 -0
- package/shared/Toolbar.svelte +386 -0
- package/shared/brush/BRUSH_TOOL.md +134 -0
- package/shared/brush/BrushOptions.svelte +200 -0
- package/shared/{tools → brush}/BrushSize.svelte +2 -30
- package/shared/{tools → brush}/ColorField.svelte +3 -1
- package/shared/{tools → brush}/ColorPicker.svelte +86 -9
- package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
- package/shared/brush/brush-cursor.ts +408 -0
- package/shared/brush/brush-textures.ts +485 -0
- package/shared/brush/brush-utils.ts +76 -0
- package/shared/brush/brush.ts +510 -0
- package/shared/brush/types.ts +41 -0
- package/shared/core/EDITOR.md +182 -0
- package/shared/core/editor.ts +1788 -0
- package/shared/crop/CROP.md +139 -0
- package/shared/crop/crop.ts +1027 -0
- package/shared/image/IMAGE.md +135 -0
- package/shared/image/image.ts +298 -0
- package/shared/resize/resize.ts +1894 -0
- package/shared/types.ts +19 -0
- package/shared/utils/pixi.ts +19 -246
- package/shared/zoom/zoom.ts +729 -0
- package/dist/shared/layers/Layers.svelte +0 -231
- package/dist/shared/layers/Layers.svelte.d.ts +0 -18
- package/dist/shared/layers/utils.d.ts +0 -73
- package/dist/shared/layers/utils.js +0 -156
- package/dist/shared/tools/Brush.svelte +0 -215
- package/dist/shared/tools/Brush.svelte.d.ts +0 -29
- package/dist/shared/tools/Crop.svelte +0 -155
- package/dist/shared/tools/Crop.svelte.d.ts +0 -16
- package/dist/shared/tools/Cropper.svelte +0 -271
- package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
- package/dist/shared/tools/Handle.svelte +0 -260
- package/dist/shared/tools/Handle.svelte.d.ts +0 -29
- package/dist/shared/tools/Sources.svelte +0 -257
- package/dist/shared/tools/Sources.svelte.d.ts +0 -34
- package/dist/shared/tools/Tools.svelte +0 -102
- package/dist/shared/tools/Tools.svelte.d.ts +0 -48
- package/dist/shared/tools/brush.d.ts +0 -62
- package/dist/shared/tools/brush.js +0 -133
- package/dist/shared/tools/crop.d.ts +0 -15
- package/dist/shared/tools/crop.js +0 -154
- package/dist/shared/tools/index.d.ts +0 -5
- package/dist/shared/tools/index.js +0 -5
- package/dist/shared/tools/sources.d.ts +0 -46
- package/dist/shared/tools/sources.js +0 -121
- package/dist/shared/tools/types.d.ts +0 -1
- package/shared/layers/Layers.svelte +0 -250
- package/shared/layers/utils.ts +0 -294
- package/shared/tools/Brush.svelte +0 -280
- package/shared/tools/BrushOptions.svelte +0 -180
- package/shared/tools/Crop.svelte +0 -189
- package/shared/tools/Cropper.svelte +0 -346
- package/shared/tools/Handle.svelte +0 -289
- package/shared/tools/Sources.svelte +0 -283
- package/shared/tools/Tools.svelte +0 -162
- package/shared/tools/brush.ts +0 -247
- package/shared/tools/crop.ts +0 -213
- package/shared/tools/index.ts +0 -5
- package/shared/tools/sources.ts +0 -195
- package/shared/tools/types.ts +0 -1
- /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
- /package/dist/shared/{tools/types.js → types.js} +0 -0
|
@@ -1,447 +1,809 @@
|
|
|
1
1
|
<script lang="ts" context="module">
|
|
2
|
-
import type {
|
|
3
|
-
import type { Spring } from "svelte/motion";
|
|
4
|
-
import { type PixiApp } from "./utils/pixi";
|
|
5
|
-
import { type CommandManager, type CommandNode } from "./utils/commands";
|
|
2
|
+
import type { Tool, Subtool } from "./Toolbar.svelte";
|
|
6
3
|
|
|
7
4
|
export const EDITOR_KEY = Symbol("editor");
|
|
8
5
|
export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
|
|
9
|
-
type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>;
|
|
10
|
-
import { type tool } from "./tools";
|
|
11
|
-
|
|
12
|
-
export interface EditorContext {
|
|
13
|
-
pixi: Writable<PixiApp | null>;
|
|
14
|
-
current_layer: Writable<LayerScene | null>;
|
|
15
|
-
dimensions: Writable<[number, number]>;
|
|
16
|
-
editor_box: Writable<{
|
|
17
|
-
parent_width: number;
|
|
18
|
-
parent_height: number;
|
|
19
|
-
parent_left: number;
|
|
20
|
-
parent_top: number;
|
|
21
|
-
parent_right: number;
|
|
22
|
-
parent_bottom: number;
|
|
23
|
-
child_width: number;
|
|
24
|
-
child_height: number;
|
|
25
|
-
child_left: number;
|
|
26
|
-
child_top: number;
|
|
27
|
-
child_right: number;
|
|
28
|
-
child_bottom: number;
|
|
29
|
-
}>;
|
|
30
|
-
active_tool: Writable<tool>;
|
|
31
|
-
toolbar_box: Writable<DOMRect | Record<string, never>>;
|
|
32
|
-
crop: Writable<[number, number, number, number]>;
|
|
33
|
-
position_spring: Spring<{
|
|
34
|
-
x: number;
|
|
35
|
-
y: number;
|
|
36
|
-
}>;
|
|
37
|
-
command_manager: CommandManager;
|
|
38
|
-
current_history: CommandManager["current_history"];
|
|
39
|
-
register_context: (
|
|
40
|
-
type: context_type,
|
|
41
|
-
{
|
|
42
|
-
reset_fn,
|
|
43
|
-
init_fn
|
|
44
|
-
}: {
|
|
45
|
-
reset_fn?: () => void;
|
|
46
|
-
init_fn?: (dimensions?: [number, number]) => void;
|
|
47
|
-
}
|
|
48
|
-
) => void;
|
|
49
|
-
reset: (clear_image: boolean, dimensions: [number, number]) => void;
|
|
50
|
-
}
|
|
51
6
|
</script>
|
|
52
7
|
|
|
53
8
|
<script lang="ts">
|
|
54
|
-
import { onMount,
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
import { type
|
|
62
|
-
import {
|
|
9
|
+
import { onMount, createEventDispatcher, tick } from "svelte";
|
|
10
|
+
import Toolbar, { type Tool as ToolbarTool } from "./Toolbar.svelte";
|
|
11
|
+
import { CropTool } from "./crop/crop";
|
|
12
|
+
import { ResizeTool } from "./resize/resize";
|
|
13
|
+
import { Webcam } from "@gradio/image";
|
|
14
|
+
import type { I18nFormatter } from "@gradio/utils";
|
|
15
|
+
import type { Client, FileData } from "@gradio/client";
|
|
16
|
+
import tinycolor, { type ColorInput } from "tinycolor2";
|
|
17
|
+
import { ZoomTool } from "./zoom/zoom";
|
|
18
|
+
import { type CommandManager, type CommandNode } from "./utils/commands";
|
|
19
|
+
import { ImageEditor } from "./core/editor";
|
|
20
|
+
import { type Brush, type Eraser } from "./brush/types";
|
|
21
|
+
import { BrushTool } from "./brush/brush";
|
|
22
|
+
import { create_drag } from "@gradio/upload";
|
|
23
|
+
import SecondaryToolbar from "./SecondaryToolbar.svelte";
|
|
24
|
+
import { Check } from "@gradio/icons";
|
|
25
|
+
import type { LayerOptions, Source, Transform, WebcamOptions } from "./types";
|
|
26
|
+
|
|
27
|
+
import { type ImageBlobs } from "./types";
|
|
63
28
|
import Controls from "./Controls.svelte";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export let history: boolean;
|
|
68
|
-
export let bg = false;
|
|
69
|
-
export let sources: ("clipboard" | "webcam" | "upload")[];
|
|
29
|
+
import IconButton from "./IconButton.svelte";
|
|
30
|
+
|
|
31
|
+
const { drag, open_file_upload } = create_drag();
|
|
70
32
|
const dispatch = createEventDispatcher<{
|
|
71
33
|
clear?: never;
|
|
72
34
|
save: void;
|
|
73
35
|
change: void;
|
|
74
36
|
history: CommandManager["current_history"];
|
|
37
|
+
upload: void;
|
|
38
|
+
input: void;
|
|
39
|
+
download_error: string;
|
|
75
40
|
}>();
|
|
76
|
-
export let crop_constraint = false;
|
|
77
|
-
export let canvas_size: [number, number] | undefined;
|
|
78
|
-
export let parent_height: number;
|
|
79
|
-
export let full_history: CommandNode | null = null;
|
|
80
|
-
|
|
81
|
-
$: orig_canvas_size = canvas_size;
|
|
82
|
-
|
|
83
|
-
const BASE_DIMENSIONS: [number, number] = canvas_size || [800, 600];
|
|
84
|
-
|
|
85
|
-
let dimensions = writable(BASE_DIMENSIONS);
|
|
86
|
-
export let height = 0;
|
|
87
|
-
|
|
88
|
-
let editor_box: EditorContext["editor_box"] = writable({
|
|
89
|
-
parent_width: 0,
|
|
90
|
-
parent_height: parent_height,
|
|
91
|
-
parent_top: 0,
|
|
92
|
-
parent_left: 0,
|
|
93
|
-
parent_right: 0,
|
|
94
|
-
parent_bottom: 0,
|
|
95
|
-
child_width: 0,
|
|
96
|
-
child_height: parent_height,
|
|
97
|
-
child_top: 0,
|
|
98
|
-
child_left: 0,
|
|
99
|
-
child_right: 0,
|
|
100
|
-
child_bottom: 0
|
|
101
|
-
});
|
|
102
41
|
|
|
103
|
-
export
|
|
42
|
+
export const antialias = true;
|
|
43
|
+
export const full_history: CommandNode | null = null;
|
|
104
44
|
|
|
105
|
-
|
|
106
|
-
|
|
45
|
+
export let changeable = false;
|
|
46
|
+
export let sources: Source[] = ["upload", "webcam", "clipboard"];
|
|
47
|
+
export let transforms: Transform[] = ["crop", "resize"];
|
|
48
|
+
export let canvas_size: [number, number];
|
|
49
|
+
export let is_dragging = false;
|
|
50
|
+
export let background_image = false;
|
|
51
|
+
export let brush_options: Brush;
|
|
52
|
+
export let eraser_options: Eraser;
|
|
53
|
+
export let fixed_canvas = false;
|
|
54
|
+
export let root: string;
|
|
55
|
+
export let i18n: I18nFormatter;
|
|
56
|
+
export let upload: Client["upload"];
|
|
57
|
+
export let composite: FileData | null;
|
|
58
|
+
export let layers: FileData[];
|
|
59
|
+
export let background: FileData | null;
|
|
60
|
+
export let border_region = 0;
|
|
61
|
+
export let layer_options: LayerOptions;
|
|
62
|
+
export let current_tool: ToolbarTool;
|
|
63
|
+
export let webcam_options: WebcamOptions;
|
|
64
|
+
export let show_download_button = false;
|
|
65
|
+
export let theme_mode: "dark" | "light";
|
|
107
66
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
let pixi_target: HTMLDivElement;
|
|
68
|
+
let pixi_target_crop: HTMLDivElement;
|
|
69
|
+
|
|
70
|
+
$: if (layer_options) {
|
|
71
|
+
if (check_if_should_init()) {
|
|
72
|
+
editor.set_layer_options(layer_options);
|
|
73
|
+
refresh_tools();
|
|
114
74
|
}
|
|
115
|
-
|
|
116
|
-
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function refresh_tools(): void {
|
|
78
|
+
editor.set_tool(current_tool);
|
|
79
|
+
editor.set_subtool(current_subtool);
|
|
80
|
+
}
|
|
117
81
|
|
|
118
|
-
|
|
82
|
+
function check_if_should_init(): boolean {
|
|
83
|
+
return layer_options && editor && ready;
|
|
84
|
+
}
|
|
119
85
|
|
|
120
|
-
|
|
86
|
+
export let has_drawn = false;
|
|
121
87
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Gets the image blobs from the editor
|
|
90
|
+
* @returns {Promise<ImageBlobs>} Object containing background, layers, and composite image blobs
|
|
91
|
+
*/
|
|
92
|
+
export async function get_blobs(): Promise<ImageBlobs> {
|
|
93
|
+
if (!editor) return { background: null, layers: [], composite: null };
|
|
94
|
+
if (!background_image && !has_drawn && !layers.length)
|
|
95
|
+
return { background: null, layers: [], composite: null };
|
|
96
|
+
const blobs = await editor.get_blobs();
|
|
97
|
+
return blobs;
|
|
127
98
|
}
|
|
128
99
|
|
|
129
|
-
|
|
130
|
-
$: dispatch("history", get_start_history($current_history));
|
|
100
|
+
let editor: ImageEditor;
|
|
131
101
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
writable({});
|
|
140
|
-
const init_context: Writable<
|
|
141
|
-
PartialRecord<context_type, (dimensions?: typeof $dimensions) => void>
|
|
142
|
-
> = writable({});
|
|
143
|
-
const contexts: Writable<context_type[]> = writable([]);
|
|
144
|
-
const toolbar_box: Writable<DOMRect | Record<string, never>> = writable(
|
|
145
|
-
is_browser ? new DOMRect() : {}
|
|
146
|
-
);
|
|
102
|
+
/**
|
|
103
|
+
* Adds an image to the editor
|
|
104
|
+
* @param {Blob | File} image - The image to add
|
|
105
|
+
*/
|
|
106
|
+
export function add_image(image: Blob | File): void {
|
|
107
|
+
editor.add_image({ image });
|
|
108
|
+
}
|
|
147
109
|
|
|
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
|
-
|
|
110
|
+
let pending_bg: Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Adds an image to the editor from a URL
|
|
113
|
+
* @param {string | FileData} source - The URL of the image or a FileData object
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
export async function add_image_from_url(
|
|
117
|
+
source:
|
|
118
|
+
| string
|
|
119
|
+
| {
|
|
120
|
+
url: string;
|
|
121
|
+
meta: {
|
|
122
|
+
_type: string;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
| any
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
if (!editor || !source || !check_if_should_init()) return;
|
|
128
|
+
let url: string;
|
|
129
|
+
|
|
130
|
+
// Handle different source types
|
|
131
|
+
if (typeof source === "string") {
|
|
132
|
+
url = source;
|
|
133
|
+
} else if (source?.meta?._type === "gradio.FileData" && source?.url) {
|
|
134
|
+
url = source.url;
|
|
135
|
+
} else {
|
|
136
|
+
console.warn("Invalid source provided to add_image_from_url:", source);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
pending_bg = editor.add_image_from_url(url);
|
|
142
|
+
let pending_crop = crop.add_image_from_url(url);
|
|
143
|
+
await Promise.all([pending_bg, pending_crop]);
|
|
144
|
+
crop.set_tool("image");
|
|
145
|
+
crop.set_subtool("crop");
|
|
146
|
+
background_image = true;
|
|
147
|
+
dispatch("upload");
|
|
148
|
+
dispatch("input");
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error("Error adding image from URL:", error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Adds a new layer with an image loaded from a URL
|
|
156
|
+
* @param {string | FileData} source - The URL of the image or a FileData object
|
|
157
|
+
* @returns {Promise<string | null>} - The ID of the created layer, or null if failed
|
|
158
|
+
*/
|
|
159
|
+
export async function add_layers_from_url(
|
|
160
|
+
source: FileData[] | any
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
if (!editor || !source.length || !check_if_should_init()) return;
|
|
163
|
+
|
|
164
|
+
// Handle different source types
|
|
165
|
+
if (
|
|
166
|
+
Array.isArray(source) &&
|
|
167
|
+
source.every((item) => item?.meta?._type === "gradio.FileData")
|
|
168
|
+
) {
|
|
169
|
+
try {
|
|
170
|
+
await pending_bg;
|
|
171
|
+
|
|
172
|
+
await editor.add_layers_from_url(source.map((item) => item.url));
|
|
173
|
+
dispatch("change");
|
|
174
|
+
dispatch("input");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Error adding layer from URL:", error);
|
|
184
177
|
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
let brush: BrushTool;
|
|
182
|
+
let zoom: ZoomTool;
|
|
183
|
+
let zoom_level = 1;
|
|
184
|
+
let ready = false;
|
|
185
|
+
let mounted = false;
|
|
186
|
+
let min_zoom = true;
|
|
187
|
+
|
|
188
|
+
let last_dimensions = { width: 0, height: 0 };
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handles visibility changes and resets zoom if dimensions have changed
|
|
192
|
+
*/
|
|
193
|
+
async function handle_visibility_change(): Promise<void> {
|
|
194
|
+
if (!editor || !ready || !zoom) return;
|
|
195
|
+
await tick();
|
|
196
|
+
|
|
197
|
+
const is_visible = pixi_target.offsetParent !== null;
|
|
198
|
+
|
|
199
|
+
if (is_visible) {
|
|
200
|
+
const current_dimensions = pixi_target.getBoundingClientRect();
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
current_dimensions.width !== last_dimensions.width ||
|
|
204
|
+
current_dimensions.height !== last_dimensions.height
|
|
205
|
+
) {
|
|
206
|
+
// Use set_zoom with "fit" to reset to appropriate zoom level
|
|
207
|
+
zoom.set_zoom("fit");
|
|
208
|
+
|
|
209
|
+
// Update the last known dimensions
|
|
210
|
+
last_dimensions = {
|
|
211
|
+
width: current_dimensions.width,
|
|
212
|
+
height: current_dimensions.height
|
|
213
|
+
};
|
|
196
214
|
}
|
|
197
215
|
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
onMount(() => {
|
|
219
|
+
let intersection_observer: IntersectionObserver;
|
|
220
|
+
let resize_observer: ResizeObserver;
|
|
221
|
+
init_image_editor().then(() => {
|
|
222
|
+
mounted = true;
|
|
223
|
+
intersection_observer = new IntersectionObserver(() => {
|
|
224
|
+
handle_visibility_change();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
resize_observer = new ResizeObserver(() => {
|
|
228
|
+
handle_visibility_change();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
intersection_observer.observe(pixi_target);
|
|
232
|
+
resize_observer.observe(pixi_target);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Set up mutation observer to detect visibility changes
|
|
236
|
+
|
|
237
|
+
return () => {
|
|
238
|
+
if (intersection_observer) {
|
|
239
|
+
intersection_observer.disconnect();
|
|
240
|
+
}
|
|
241
|
+
if (resize_observer) {
|
|
242
|
+
resize_observer.disconnect();
|
|
243
|
+
}
|
|
244
|
+
if (editor) {
|
|
245
|
+
editor.destroy();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
198
248
|
});
|
|
199
249
|
|
|
200
|
-
let
|
|
250
|
+
let crop: ImageEditor;
|
|
251
|
+
let crop_zoom: ZoomTool;
|
|
252
|
+
async function init_image_editor(): Promise<void> {
|
|
253
|
+
brush = new BrushTool();
|
|
254
|
+
zoom = new ZoomTool();
|
|
255
|
+
editor = new ImageEditor({
|
|
256
|
+
target_element: pixi_target,
|
|
257
|
+
width: canvas_size[0],
|
|
258
|
+
height: canvas_size[1],
|
|
259
|
+
tools: ["image", zoom, new ResizeTool(), brush],
|
|
260
|
+
fixed_canvas,
|
|
261
|
+
border_region,
|
|
262
|
+
layer_options,
|
|
263
|
+
theme_mode
|
|
264
|
+
});
|
|
201
265
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
function get_dimensions(parent: HTMLDivElement, child: HTMLDivElement): void {
|
|
205
|
-
if (!parent || !child) return;
|
|
206
|
-
const {
|
|
207
|
-
width: parent_width,
|
|
208
|
-
height: parent_height,
|
|
209
|
-
top: parent_top,
|
|
210
|
-
left: parent_left,
|
|
211
|
-
right: parent_right,
|
|
212
|
-
bottom: parent_bottom
|
|
213
|
-
} = canvas_wrap.getBoundingClientRect();
|
|
214
|
-
const {
|
|
215
|
-
width: child_width,
|
|
216
|
-
height: child_height,
|
|
217
|
-
top: child_top,
|
|
218
|
-
left: child_left,
|
|
219
|
-
right: child_right,
|
|
220
|
-
bottom: child_bottom
|
|
221
|
-
} = child.getBoundingClientRect();
|
|
222
|
-
editor_box.set({
|
|
223
|
-
child_width,
|
|
224
|
-
child_height,
|
|
225
|
-
child_left,
|
|
226
|
-
child_right,
|
|
227
|
-
child_top,
|
|
228
|
-
child_bottom,
|
|
229
|
-
|
|
230
|
-
parent_width,
|
|
231
|
-
parent_height,
|
|
232
|
-
parent_left,
|
|
233
|
-
parent_right,
|
|
234
|
-
parent_top,
|
|
235
|
-
parent_bottom
|
|
266
|
+
brush.on("change", () => {
|
|
267
|
+
has_drawn = true;
|
|
236
268
|
});
|
|
237
|
-
}
|
|
238
269
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
270
|
+
crop_zoom = new ZoomTool();
|
|
271
|
+
|
|
272
|
+
crop = new ImageEditor({
|
|
273
|
+
target_element: pixi_target_crop,
|
|
274
|
+
width: canvas_size[0],
|
|
275
|
+
height: canvas_size[1],
|
|
276
|
+
tools: ["image", crop_zoom, new CropTool()],
|
|
277
|
+
dark: true,
|
|
278
|
+
fixed_canvas: false,
|
|
279
|
+
border_region: 0,
|
|
280
|
+
pad_bottom: 40
|
|
281
|
+
});
|
|
242
282
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
283
|
+
editor.scale.subscribe((_scale) => {
|
|
284
|
+
zoom_level = _scale;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
editor.min_zoom.subscribe((is_min_zoom) => {
|
|
288
|
+
min_zoom = is_min_zoom;
|
|
246
289
|
});
|
|
247
|
-
}
|
|
248
290
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
291
|
+
editor.dimensions.subscribe((dimensions) => {
|
|
292
|
+
// Store dimensions for later comparison
|
|
293
|
+
last_dimensions = { ...dimensions };
|
|
294
|
+
});
|
|
252
295
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
await Promise.all([editor.ready, crop.ready]).then(() => {
|
|
297
|
+
handle_tool_change({ tool: "image" });
|
|
298
|
+
ready = true;
|
|
299
|
+
if (sources.length > 0) {
|
|
300
|
+
handle_tool_change({ tool: "image" });
|
|
301
|
+
} else {
|
|
302
|
+
handle_tool_change({ tool: "draw" });
|
|
303
|
+
}
|
|
304
|
+
crop.set_subtool("crop");
|
|
305
|
+
});
|
|
257
306
|
|
|
258
|
-
|
|
259
|
-
|
|
307
|
+
editor.on("change", () => {
|
|
308
|
+
dispatch("change");
|
|
309
|
+
});
|
|
260
310
|
|
|
261
|
-
|
|
311
|
+
if (background || layers.length > 0) {
|
|
312
|
+
if (background) {
|
|
313
|
+
await add_image_from_url(background);
|
|
314
|
+
}
|
|
315
|
+
if (layers.length > 0) {
|
|
316
|
+
await add_layers_from_url(layers);
|
|
317
|
+
}
|
|
318
|
+
handle_tool_change({ tool: "draw" });
|
|
319
|
+
} else if (composite) {
|
|
320
|
+
await add_image_from_url(composite);
|
|
321
|
+
handle_tool_change({ tool: "draw" });
|
|
322
|
+
}
|
|
262
323
|
}
|
|
263
324
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
Math.round(1 * $dimensions[1])
|
|
276
|
-
),
|
|
277
|
-
$dimensions
|
|
278
|
-
);
|
|
325
|
+
$: if (
|
|
326
|
+
background == null &&
|
|
327
|
+
layers.length == 0 &&
|
|
328
|
+
composite == null &&
|
|
329
|
+
editor &&
|
|
330
|
+
ready
|
|
331
|
+
) {
|
|
332
|
+
editor.reset_canvas();
|
|
333
|
+
handle_tool_change({ tool: "image" });
|
|
334
|
+
background_image = false;
|
|
335
|
+
has_drawn = false;
|
|
279
336
|
}
|
|
280
337
|
|
|
281
|
-
$:
|
|
282
|
-
|
|
338
|
+
$: current_tool === "image" &&
|
|
339
|
+
current_subtool === "crop" &&
|
|
340
|
+
crop_zoom.set_zoom("fit");
|
|
341
|
+
|
|
342
|
+
// function resize_canvas(width: number, height: number): void {
|
|
343
|
+
// if (!editor) return;
|
|
344
|
+
// if (mounted && ready) {
|
|
345
|
+
// editor.resize(width, height);
|
|
346
|
+
// }
|
|
347
|
+
// }
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Handles file uploads
|
|
351
|
+
* @param {File[]} files - The uploaded files
|
|
352
|
+
*/
|
|
353
|
+
async function handle_files(
|
|
354
|
+
files: File[] | Blob[] | File | Blob | null
|
|
355
|
+
): Promise<void> {
|
|
356
|
+
if (files == null) return;
|
|
357
|
+
if (!sources.includes("upload")) return;
|
|
358
|
+
const _file = Array.isArray(files) ? files[0] : files;
|
|
359
|
+
await editor.add_image({ image: _file });
|
|
360
|
+
await crop.add_image({ image: _file });
|
|
361
|
+
crop.reset();
|
|
362
|
+
background_image = true;
|
|
363
|
+
handle_tool_change({ tool: "draw" });
|
|
364
|
+
dispatch("upload");
|
|
365
|
+
dispatch("input");
|
|
366
|
+
dispatch("change");
|
|
367
|
+
}
|
|
283
368
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Handles tool change events
|
|
371
|
+
* @param {{ tool: ToolbarTool }} param0 - Object containing the selected tool
|
|
372
|
+
*/
|
|
373
|
+
function handle_tool_change({ tool }: { tool: ToolbarTool }): void {
|
|
374
|
+
editor.set_tool(tool);
|
|
375
|
+
current_tool = tool;
|
|
376
|
+
|
|
377
|
+
if (tool === "image") {
|
|
378
|
+
crop.set_tool("image");
|
|
379
|
+
crop.set_subtool("crop");
|
|
293
380
|
}
|
|
294
|
-
dispatch("clear");
|
|
295
|
-
|
|
296
|
-
let _size = (canvas_size ? canvas_size : crop_size) || [800, 600];
|
|
297
|
-
editor_context.reset(true, _size);
|
|
298
381
|
}
|
|
299
382
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Handles subtool change events
|
|
385
|
+
* @param {{ tool: ToolbarTool, subtool: Subtool }} param0 - Object containing the selected tool and subtool
|
|
386
|
+
*/
|
|
387
|
+
function handle_subtool_change({
|
|
388
|
+
tool,
|
|
389
|
+
subtool
|
|
390
|
+
}: {
|
|
391
|
+
tool: ToolbarTool;
|
|
392
|
+
subtool: Subtool | null;
|
|
393
|
+
}): void {
|
|
394
|
+
editor.set_subtool(subtool);
|
|
395
|
+
current_subtool = subtool;
|
|
396
|
+
|
|
397
|
+
if (subtool === null) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
307
400
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
401
|
+
if (tool === "draw") {
|
|
402
|
+
if (subtool === "size") {
|
|
403
|
+
brush_size_visible = true;
|
|
404
|
+
} else if (subtool === "color") {
|
|
405
|
+
brush_color_visible = true;
|
|
406
|
+
}
|
|
311
407
|
}
|
|
312
408
|
|
|
313
|
-
|
|
409
|
+
if (tool === "erase" && subtool === "size") {
|
|
410
|
+
eraser_size_visible = true;
|
|
411
|
+
}
|
|
314
412
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
});
|
|
413
|
+
if (tool === "image" && subtool === "paste") {
|
|
414
|
+
process_clipboard();
|
|
415
|
+
}
|
|
320
416
|
|
|
321
|
-
|
|
322
|
-
|
|
417
|
+
if (tool === "image" && subtool === "upload") {
|
|
418
|
+
tick().then(() => {
|
|
419
|
+
disable_click = false;
|
|
420
|
+
open_file_upload();
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
let eraser_size_visible = false;
|
|
426
|
+
let selected_color: ColorInput | string;
|
|
427
|
+
let selected_size: number;
|
|
428
|
+
let selected_opacity = 1;
|
|
429
|
+
let selected_eraser_size: number;
|
|
430
|
+
|
|
431
|
+
$: {
|
|
432
|
+
if (brush_options) {
|
|
433
|
+
update_brush_options();
|
|
434
|
+
}
|
|
323
435
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
436
|
+
if (eraser_options) {
|
|
437
|
+
update_eraser_options();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function update_brush_options(): void {
|
|
442
|
+
const default_color =
|
|
443
|
+
brush_options.default_color === "auto"
|
|
444
|
+
? brush_options.colors[0]
|
|
445
|
+
: brush_options.default_color;
|
|
446
|
+
|
|
447
|
+
// color is already a tuple [color, opacity]
|
|
448
|
+
if (Array.isArray(default_color)) {
|
|
449
|
+
selected_color = default_color[0];
|
|
450
|
+
selected_opacity = default_color[1];
|
|
451
|
+
} else {
|
|
452
|
+
selected_color = default_color;
|
|
453
|
+
|
|
454
|
+
// color is a string, check if it has opacity info
|
|
455
|
+
const color = tinycolor(default_color);
|
|
456
|
+
if (color.getAlpha() < 1) {
|
|
457
|
+
selected_opacity = color.getAlpha();
|
|
458
|
+
} else {
|
|
459
|
+
selected_opacity = 1;
|
|
327
460
|
}
|
|
328
461
|
}
|
|
329
462
|
|
|
330
|
-
|
|
463
|
+
selected_size =
|
|
464
|
+
typeof brush_options.default_size === "number"
|
|
465
|
+
? brush_options.default_size
|
|
466
|
+
: 25;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function update_eraser_options(): void {
|
|
470
|
+
selected_eraser_size =
|
|
471
|
+
eraser_options.default_size === "auto" ? 25 : eraser_options.default_size;
|
|
472
|
+
}
|
|
331
473
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
474
|
+
let brush_size_visible = false;
|
|
475
|
+
|
|
476
|
+
let brush_color_visible = false;
|
|
477
|
+
|
|
478
|
+
$: brush?.set_brush_color(
|
|
479
|
+
(() => {
|
|
480
|
+
let color_value;
|
|
481
|
+
if (selected_color === "auto") {
|
|
482
|
+
const default_color =
|
|
483
|
+
brush_options.colors.find((color) =>
|
|
484
|
+
Array.isArray(color)
|
|
485
|
+
? color[0] === brush_options.default_color
|
|
486
|
+
: color === brush_options.default_color
|
|
487
|
+
) || brush_options.colors[0];
|
|
488
|
+
|
|
489
|
+
color_value = Array.isArray(default_color)
|
|
490
|
+
? default_color[0]
|
|
491
|
+
: default_color;
|
|
492
|
+
} else {
|
|
493
|
+
color_value = selected_color;
|
|
335
494
|
}
|
|
336
|
-
|
|
495
|
+
return color_value;
|
|
496
|
+
})()
|
|
497
|
+
);
|
|
337
498
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
499
|
+
// Type-safe brush size handling
|
|
500
|
+
$: brush?.set_brush_size(
|
|
501
|
+
typeof selected_size === "number" ? selected_size : 25
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
$: brush?.set_eraser_size(
|
|
505
|
+
typeof selected_eraser_size === "number" ? selected_eraser_size : 25
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
$: disable_click =
|
|
509
|
+
current_tool !== "image" ||
|
|
510
|
+
(current_tool === "image" && background_image) ||
|
|
511
|
+
(current_tool === "image" && current_subtool === "webcam") ||
|
|
512
|
+
!sources.includes("upload");
|
|
513
|
+
|
|
514
|
+
let current_subtool: Subtool | null = null;
|
|
515
|
+
let preview = false;
|
|
516
|
+
$: brush?.preview_brush(preview);
|
|
517
|
+
$: brush?.set_brush_opacity(selected_opacity);
|
|
518
|
+
|
|
519
|
+
function handle_zoom_change(zoom_level: number | "fit"): void {
|
|
520
|
+
zoom.set_zoom(zoom_level);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function zoom_in_out(direction: "in" | "out"): void {
|
|
524
|
+
zoom.set_zoom(
|
|
525
|
+
direction === "in"
|
|
526
|
+
? zoom_level + (zoom_level < 1 ? 0.1 : zoom_level * 0.1)
|
|
527
|
+
: zoom_level - (zoom_level < 1 ? 0.1 : zoom_level * 0.1)
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function process_clipboard(): Promise<void> {
|
|
532
|
+
const items = await navigator.clipboard.read();
|
|
533
|
+
|
|
534
|
+
for (let i = 0; i < items.length; i++) {
|
|
535
|
+
const type = items[i].types.find((t) => t.startsWith("image/"));
|
|
536
|
+
if (type) {
|
|
537
|
+
const blob = await items[i].getType(type);
|
|
538
|
+
|
|
539
|
+
handle_files(blob);
|
|
345
540
|
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
348
543
|
|
|
349
|
-
|
|
544
|
+
function handle_capture(e: CustomEvent): void {
|
|
545
|
+
if (e.detail !== null) {
|
|
546
|
+
handle_files(e.detail as Blob);
|
|
547
|
+
}
|
|
548
|
+
handle_subtool_change({ tool: current_tool, subtool: null });
|
|
549
|
+
}
|
|
350
550
|
|
|
351
551
|
function handle_save(): void {
|
|
352
|
-
saved_history = $current_history;
|
|
353
552
|
dispatch("save");
|
|
354
553
|
}
|
|
355
554
|
|
|
356
|
-
|
|
357
|
-
|
|
555
|
+
$: add_image_from_url(composite || background);
|
|
556
|
+
$: add_layers_from_url(layers);
|
|
557
|
+
|
|
558
|
+
async function handle_crop_confirm(): Promise<void> {
|
|
559
|
+
const { image } = await crop.get_crop_bounds();
|
|
560
|
+
if (!image) return;
|
|
561
|
+
|
|
562
|
+
await editor.add_image({
|
|
563
|
+
image,
|
|
564
|
+
resize: false
|
|
565
|
+
});
|
|
566
|
+
handle_subtool_change({ tool: "image", subtool: null });
|
|
567
|
+
dispatch("change");
|
|
568
|
+
dispatch("input");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function handle_download(): Promise<void> {
|
|
572
|
+
const blobs = await editor.get_blobs();
|
|
573
|
+
|
|
574
|
+
const blob = blobs.composite;
|
|
575
|
+
if (!blob) {
|
|
576
|
+
dispatch("download_error", "Unable to generate image to download.");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const url = URL.createObjectURL(blob);
|
|
580
|
+
const link = document.createElement("a");
|
|
581
|
+
link.href = url;
|
|
582
|
+
link.download = "image.png";
|
|
583
|
+
link.click();
|
|
584
|
+
URL.revokeObjectURL(url);
|
|
358
585
|
}
|
|
359
586
|
</script>
|
|
360
587
|
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
588
|
+
<div
|
|
589
|
+
data-testid="image"
|
|
590
|
+
class="image-container"
|
|
591
|
+
class:dark-bg={current_subtool === "crop"}
|
|
592
|
+
use:drag={{
|
|
593
|
+
on_drag_change: (dragging) => (is_dragging = dragging),
|
|
594
|
+
on_files: handle_files,
|
|
595
|
+
accepted_types: "image/*",
|
|
596
|
+
disable_click: disable_click
|
|
597
|
+
}}
|
|
598
|
+
aria-label={"Click to upload or drop files"}
|
|
599
|
+
aria-dropeffect="copy"
|
|
600
|
+
>
|
|
601
|
+
{#if ready}
|
|
602
|
+
{#if current_subtool !== "crop"}
|
|
603
|
+
<Controls
|
|
604
|
+
{changeable}
|
|
605
|
+
on:set_zoom={(e) => handle_zoom_change(e.detail)}
|
|
606
|
+
on:zoom_in={() => zoom_in_out("in")}
|
|
607
|
+
on:zoom_out={() => zoom_in_out("out")}
|
|
608
|
+
{min_zoom}
|
|
609
|
+
current_zoom={zoom_level}
|
|
610
|
+
on:remove_image={() => {
|
|
611
|
+
editor.reset_canvas();
|
|
612
|
+
handle_tool_change({ tool: "image" });
|
|
613
|
+
background_image = false;
|
|
614
|
+
has_drawn = false;
|
|
615
|
+
}}
|
|
616
|
+
tool={current_tool}
|
|
617
|
+
can_save={true}
|
|
618
|
+
on:save={handle_save}
|
|
619
|
+
on:pan={(e) => {
|
|
620
|
+
handle_tool_change({ tool: "pan" });
|
|
621
|
+
}}
|
|
622
|
+
enable_download={show_download_button}
|
|
623
|
+
on:download={() => handle_download()}
|
|
624
|
+
/>
|
|
625
|
+
{/if}
|
|
626
|
+
|
|
627
|
+
{#if current_subtool !== "crop"}
|
|
628
|
+
<Toolbar
|
|
629
|
+
{sources}
|
|
630
|
+
{transforms}
|
|
631
|
+
background={background_image}
|
|
632
|
+
on:tool_change={(e) => handle_tool_change(e.detail)}
|
|
633
|
+
on:subtool_change={(e) => handle_subtool_change(e.detail)}
|
|
634
|
+
show_brush_size={brush_size_visible}
|
|
635
|
+
show_brush_color={brush_color_visible}
|
|
636
|
+
show_eraser_size={eraser_size_visible}
|
|
637
|
+
{brush_options}
|
|
638
|
+
{eraser_options}
|
|
639
|
+
bind:selected_color
|
|
640
|
+
bind:selected_size
|
|
641
|
+
bind:selected_eraser_size
|
|
642
|
+
bind:selected_opacity
|
|
643
|
+
bind:preview
|
|
644
|
+
tool={current_tool}
|
|
645
|
+
subtool={current_subtool}
|
|
646
|
+
/>
|
|
647
|
+
{/if}
|
|
648
|
+
|
|
649
|
+
{#if current_tool === "image" && current_subtool === "webcam"}
|
|
650
|
+
<div class="modal">
|
|
651
|
+
<div class="modal-inner">
|
|
652
|
+
<Webcam
|
|
653
|
+
{upload}
|
|
654
|
+
{root}
|
|
655
|
+
on:capture={handle_capture}
|
|
656
|
+
on:error
|
|
657
|
+
on:drag
|
|
658
|
+
streaming={false}
|
|
659
|
+
mode="image"
|
|
660
|
+
include_audio={false}
|
|
661
|
+
{i18n}
|
|
662
|
+
mirror_webcam={webcam_options.mirror}
|
|
663
|
+
webcam_constraints={webcam_options.constraints}
|
|
664
|
+
/>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
{/if}
|
|
668
|
+
|
|
669
|
+
{#if current_subtool !== "crop" && !layer_options.disabled}
|
|
670
|
+
<SecondaryToolbar
|
|
671
|
+
enable_additional_layers={layer_options.allow_additional_layers}
|
|
672
|
+
layers={editor.layers}
|
|
673
|
+
on:new_layer={() => {
|
|
674
|
+
editor.add_layer();
|
|
675
|
+
}}
|
|
676
|
+
on:change_layer={(e) => {
|
|
677
|
+
editor.set_layer(e.detail);
|
|
678
|
+
if (current_tool === "draw") {
|
|
679
|
+
handle_tool_change({ tool: "draw" });
|
|
680
|
+
}
|
|
681
|
+
}}
|
|
682
|
+
on:move_layer={(e) => {
|
|
683
|
+
editor.move_layer(e.detail.id, e.detail.direction);
|
|
684
|
+
}}
|
|
685
|
+
on:delete_layer={(e) => {
|
|
686
|
+
editor.delete_layer(e.detail);
|
|
687
|
+
}}
|
|
688
|
+
on:toggle_layer_visibility={(e) => {
|
|
689
|
+
editor.toggle_layer_visibility(e.detail);
|
|
690
|
+
}}
|
|
691
|
+
/>
|
|
692
|
+
{/if}
|
|
693
|
+
{/if}
|
|
694
|
+
<div
|
|
695
|
+
class="pixi-target"
|
|
696
|
+
class:visible={current_subtool !== "crop"}
|
|
697
|
+
bind:this={pixi_target}
|
|
698
|
+
></div>
|
|
699
|
+
<div
|
|
700
|
+
class="pixi-target-crop"
|
|
701
|
+
class:visible={current_subtool === "crop"}
|
|
702
|
+
bind:this={pixi_target_crop}
|
|
703
|
+
></div>
|
|
704
|
+
|
|
705
|
+
{#if current_subtool === "crop"}
|
|
706
|
+
<div class="crop-confirm-button">
|
|
707
|
+
<IconButton
|
|
708
|
+
Icon={Check}
|
|
709
|
+
label="Confirm crop"
|
|
710
|
+
show_label={true}
|
|
711
|
+
size="large"
|
|
712
|
+
padded={true}
|
|
713
|
+
color="white"
|
|
714
|
+
background="var(--color-green-500)"
|
|
715
|
+
label_position="right"
|
|
716
|
+
on:click={handle_crop_confirm}
|
|
717
|
+
/>
|
|
380
718
|
</div>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
class:no-border={!bg && $active_tool === "bg" && !history}
|
|
384
|
-
style:width="{$editor_box.child_width + 1}px"
|
|
385
|
-
style:height="{$editor_box.child_height + 1}px"
|
|
386
|
-
style:top="{$editor_box.child_top - $editor_box.parent_top - 0.5}px"
|
|
387
|
-
style:left="{$editor_box.child_left - $editor_box.parent_left - 0.5}px"
|
|
388
|
-
></div>
|
|
389
|
-
</div>
|
|
719
|
+
{/if}
|
|
720
|
+
<slot></slot>
|
|
390
721
|
</div>
|
|
391
722
|
|
|
392
723
|
<style>
|
|
393
|
-
.
|
|
724
|
+
.image-container {
|
|
394
725
|
display: flex;
|
|
395
|
-
width: 100%;
|
|
396
726
|
height: 100%;
|
|
397
|
-
|
|
727
|
+
flex-direction: column;
|
|
398
728
|
justify-content: center;
|
|
729
|
+
align-items: center;
|
|
730
|
+
max-height: 100%;
|
|
731
|
+
border-radius: var(--radius-sm);
|
|
399
732
|
}
|
|
400
|
-
|
|
733
|
+
|
|
734
|
+
.pixi-target {
|
|
735
|
+
width: 100%;
|
|
736
|
+
height: 100%;
|
|
401
737
|
position: absolute;
|
|
402
|
-
|
|
738
|
+
top: 0;
|
|
739
|
+
left: 0;
|
|
740
|
+
z-index: 1;
|
|
741
|
+
display: block;
|
|
742
|
+
opacity: 0;
|
|
403
743
|
pointer-events: none;
|
|
404
|
-
border-radius: var(--radius-
|
|
744
|
+
border-radius: var(--radius-sm);
|
|
405
745
|
}
|
|
406
746
|
|
|
407
|
-
.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
747
|
+
.pixi-target-crop {
|
|
748
|
+
width: 100%;
|
|
749
|
+
height: 100%;
|
|
750
|
+
position: absolute;
|
|
751
|
+
top: 0;
|
|
752
|
+
left: 0;
|
|
753
|
+
z-index: 2;
|
|
754
|
+
display: block;
|
|
755
|
+
opacity: 0;
|
|
756
|
+
pointer-events: none;
|
|
757
|
+
border-radius: var(--radius-sm);
|
|
413
758
|
}
|
|
414
759
|
|
|
415
|
-
.
|
|
416
|
-
|
|
760
|
+
.visible {
|
|
761
|
+
opacity: 1;
|
|
762
|
+
pointer-events: auto;
|
|
417
763
|
}
|
|
418
764
|
|
|
419
|
-
.
|
|
420
|
-
|
|
421
|
-
|
|
765
|
+
.pixi-target {
|
|
766
|
+
width: 100%;
|
|
767
|
+
height: 100%;
|
|
768
|
+
position: absolute;
|
|
769
|
+
top: 0;
|
|
770
|
+
left: 0;
|
|
771
|
+
right: 0;
|
|
772
|
+
bottom: 0;
|
|
422
773
|
overflow: hidden;
|
|
423
|
-
height: fit-content;
|
|
424
|
-
width: auto;
|
|
425
774
|
}
|
|
426
775
|
|
|
427
|
-
.
|
|
776
|
+
.modal {
|
|
777
|
+
position: absolute;
|
|
778
|
+
height: 100%;
|
|
779
|
+
width: 100%;
|
|
780
|
+
left: 0;
|
|
781
|
+
right: 0;
|
|
782
|
+
margin: auto;
|
|
783
|
+
z-index: var(--layer-top);
|
|
428
784
|
display: flex;
|
|
429
|
-
justify-content: center;
|
|
430
785
|
align-items: center;
|
|
431
|
-
padding: 0 var(--spacing-xl) 0 0;
|
|
432
|
-
border: 1px solid var(--block-border-color);
|
|
433
|
-
border-radius: var(--radius-sm);
|
|
434
|
-
margin: var(--spacing-xxl) 0 var(--spacing-xxl) 0;
|
|
435
|
-
width: fit-content;
|
|
436
|
-
margin: 0 auto;
|
|
437
786
|
}
|
|
438
787
|
|
|
439
|
-
.
|
|
440
|
-
display: flex;
|
|
788
|
+
.modal-inner {
|
|
441
789
|
height: 100%;
|
|
442
|
-
|
|
443
|
-
|
|
790
|
+
width: 100%;
|
|
791
|
+
background: var(--block-background-fill);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.dark-bg {
|
|
795
|
+
background: #333;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.crop-confirm-button {
|
|
799
|
+
position: absolute;
|
|
800
|
+
bottom: 8px;
|
|
801
|
+
left: 0;
|
|
802
|
+
right: 0;
|
|
803
|
+
margin: auto;
|
|
804
|
+
z-index: var(--layer-top);
|
|
805
|
+
display: flex;
|
|
444
806
|
align-items: center;
|
|
445
|
-
|
|
807
|
+
justify-content: center;
|
|
446
808
|
}
|
|
447
809
|
</style>
|