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