@gradio/imageeditor 0.14.5 → 0.15.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.
- package/CHANGELOG.md +23 -0
- package/InteractiveImageEditor.svelte +4 -7
- package/dist/InteractiveImageEditor.svelte +4 -3
- package/dist/InteractiveImageEditor.svelte.d.ts +1 -1
- package/dist/shared/Controls.svelte +32 -1
- package/dist/shared/Controls.svelte.d.ts +2 -0
- package/dist/shared/ImageEditor.svelte +23 -1
- package/dist/shared/ImageEditor.svelte.d.ts +2 -1
- package/dist/shared/brush/brush-cursor.js +0 -42
- package/dist/shared/brush/brush-textures.d.ts +19 -2
- package/dist/shared/brush/brush-textures.js +120 -82
- package/dist/shared/brush/brush.js +0 -52
- package/dist/shared/{utils → core}/commands.d.ts +19 -58
- package/dist/shared/core/commands.js +70 -0
- package/dist/shared/core/editor.d.ts +4 -91
- package/dist/shared/core/editor.js +18 -708
- package/dist/shared/core/layers.d.ts +143 -0
- package/dist/shared/core/layers.js +774 -0
- package/dist/shared/image/image.d.ts +14 -4
- package/dist/shared/image/image.js +91 -18
- package/dist/shared/resize/resize.d.ts +31 -0
- package/dist/shared/resize/resize.js +127 -290
- package/dist/shared/zoom/zoom.js +0 -51
- package/package.json +7 -7
- package/shared/Controls.svelte +33 -1
- package/shared/ImageEditor.svelte +25 -9
- package/shared/Toolbar.svelte +1 -4
- package/shared/brush/ColorSwatch.svelte +0 -3
- package/shared/brush/brush-cursor.ts +0 -42
- package/shared/brush/brush-textures.ts +166 -83
- package/shared/brush/brush.ts +0 -53
- package/shared/core/commands.ts +118 -0
- package/shared/core/editor.ts +33 -957
- package/shared/core/layers.ts +1068 -0
- package/shared/image/image.ts +139 -21
- package/shared/resize/resize.ts +160 -299
- package/shared/zoom/zoom.ts +0 -51
- package/dist/shared/utils/commands.js +0 -73
- package/shared/utils/commands.ts +0 -174
package/dist/shared/zoom/zoom.js
CHANGED
|
@@ -10,10 +10,8 @@ export class ZoomTool {
|
|
|
10
10
|
name = "zoom";
|
|
11
11
|
min_zoom = writable(true);
|
|
12
12
|
image_editor_context;
|
|
13
|
-
// Constants
|
|
14
13
|
max_zoom = 10;
|
|
15
14
|
border_padding = 30;
|
|
16
|
-
// State
|
|
17
15
|
pad_bottom = 0;
|
|
18
16
|
is_pinching = false;
|
|
19
17
|
is_dragging = false;
|
|
@@ -52,30 +50,23 @@ export class ZoomTool {
|
|
|
52
50
|
* @returns {void}
|
|
53
51
|
*/
|
|
54
52
|
set_zoom(zoom_level) {
|
|
55
|
-
// Calculate the min zoom level
|
|
56
53
|
const fit_zoom = this.calculate_min_zoom(this.local_dimensions.width, this.local_dimensions.height);
|
|
57
|
-
// Determine the target zoom level
|
|
58
54
|
let target_zoom;
|
|
59
55
|
const is_fit_zoom = zoom_level === "fit";
|
|
60
56
|
if (is_fit_zoom) {
|
|
61
57
|
target_zoom = fit_zoom;
|
|
62
58
|
}
|
|
63
59
|
else {
|
|
64
|
-
// Ensure zoom_level is between 0 and 1, then scale it to the range [min_zoom, max_zoom]
|
|
65
60
|
target_zoom = Math.max(0, Math.min(this.max_zoom, zoom_level));
|
|
66
61
|
}
|
|
67
|
-
// Get the canvas dimensions
|
|
68
62
|
const canvas = this.image_editor_context.app.screen;
|
|
69
63
|
const canvas_width = canvas.width;
|
|
70
64
|
const canvas_height = canvas.height;
|
|
71
|
-
// Determine the center point for zooming
|
|
72
65
|
let center_point;
|
|
73
|
-
// Always use the center of the screen
|
|
74
66
|
center_point = {
|
|
75
67
|
x: canvas_width / 2,
|
|
76
68
|
y: canvas_height / 2
|
|
77
69
|
};
|
|
78
|
-
// Apply the zoom using the existing zoom_to_point method
|
|
79
70
|
this.zoom_to_point(target_zoom, center_point, true, is_fit_zoom);
|
|
80
71
|
}
|
|
81
72
|
/**
|
|
@@ -90,26 +81,19 @@ export class ZoomTool {
|
|
|
90
81
|
this.current_tool = tool;
|
|
91
82
|
this.current_subtool = subtool;
|
|
92
83
|
this.pad_bottom = context.pad_bottom;
|
|
93
|
-
// Initialize zoom and position
|
|
94
84
|
const { width, height } = await this.get_container_dimensions();
|
|
95
85
|
const fit_zoom = this.calculate_min_zoom(width, height);
|
|
96
|
-
// Use fit zoom or 1, whichever is smaller
|
|
97
86
|
const min_zoom = Math.min(fit_zoom, 1);
|
|
98
|
-
// Set initial zoom
|
|
99
87
|
this.local_scale = min_zoom;
|
|
100
|
-
// Use simple center positioning
|
|
101
88
|
const canvas = this.image_editor_context.app.screen;
|
|
102
89
|
const center_x = canvas.width / 2;
|
|
103
90
|
const center_y = canvas.height / 2;
|
|
104
|
-
// Calculate initial position to center the scaled image
|
|
105
91
|
const scaled_width = width * min_zoom;
|
|
106
92
|
const scaled_height = height * min_zoom;
|
|
107
93
|
const initial_x = center_x - scaled_width / 2;
|
|
108
94
|
const initial_y = center_y - scaled_height / 2;
|
|
109
95
|
this.local_position = { x: initial_x, y: initial_y };
|
|
110
|
-
// Setup event listeners
|
|
111
96
|
this.setup_event_listeners();
|
|
112
|
-
// Update context with initial values
|
|
113
97
|
await this.image_editor_context.set_image_properties({
|
|
114
98
|
scale: min_zoom,
|
|
115
99
|
position: { x: initial_x, y: initial_y }
|
|
@@ -132,13 +116,10 @@ export class ZoomTool {
|
|
|
132
116
|
const stage = this.image_editor_context.app.stage;
|
|
133
117
|
const canvas = this.image_editor_context.app.canvas;
|
|
134
118
|
canvas.addEventListener("wheel", this.prevent_default, { passive: false });
|
|
135
|
-
// Configure stage for interaction
|
|
136
119
|
stage.eventMode = "static";
|
|
137
120
|
stage.hitArea = this.image_editor_context.app.screen;
|
|
138
|
-
// Add wheel event with options
|
|
139
121
|
const wheel_listener = this.handle_wheel.bind(this);
|
|
140
122
|
stage.addEventListener("wheel", wheel_listener, { passive: false });
|
|
141
|
-
// Setup touch/pointer events based on device
|
|
142
123
|
if ("ontouchstart" in window) {
|
|
143
124
|
stage.addEventListener("touchstart", this.handle_touch_start.bind(this));
|
|
144
125
|
stage.addEventListener("touchmove", this.handle_touch_move.bind(this));
|
|
@@ -160,14 +141,12 @@ export class ZoomTool {
|
|
|
160
141
|
const is_trackpad = event.deltaMode === 0 && Math.abs(event.deltaY) < 50;
|
|
161
142
|
const scroll_speed = is_trackpad ? 30 : 10;
|
|
162
143
|
if (event.altKey || event.metaKey) {
|
|
163
|
-
// Handle zooming with our new simplified logic
|
|
164
144
|
const local_point = this.image_editor_context.app.stage.toLocal(event.global);
|
|
165
145
|
const zoom_delta = -event.deltaY * (is_trackpad ? 0.001 : 0.0005);
|
|
166
146
|
const new_scale = this.local_scale * (1 + zoom_delta);
|
|
167
147
|
this.zoom_to_point(new_scale, local_point, true);
|
|
168
148
|
}
|
|
169
149
|
else {
|
|
170
|
-
// Handle panning with bounds
|
|
171
150
|
const delta_x = event.deltaX;
|
|
172
151
|
const delta_y = event.deltaY;
|
|
173
152
|
const raw_position = {
|
|
@@ -180,12 +159,10 @@ export class ZoomTool {
|
|
|
180
159
|
const available_width = canvas.width - this.border_padding * 2;
|
|
181
160
|
const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
|
|
182
161
|
let final_position = { ...raw_position };
|
|
183
|
-
// If image is smaller than available space, center it
|
|
184
162
|
if (scaled_width <= available_width) {
|
|
185
163
|
final_position.x = (canvas.width - scaled_width) / 2;
|
|
186
164
|
}
|
|
187
165
|
else {
|
|
188
|
-
// Apply 50% rule for horizontal bounds
|
|
189
166
|
const max_offset = canvas.width / 2;
|
|
190
167
|
const left_bound = max_offset;
|
|
191
168
|
const right_bound = canvas.width - max_offset - scaled_width;
|
|
@@ -196,7 +173,6 @@ export class ZoomTool {
|
|
|
196
173
|
(canvas.height - this.pad_bottom - scaled_height) / 2;
|
|
197
174
|
}
|
|
198
175
|
else {
|
|
199
|
-
// Apply 50% rule for vertical bounds, accounting for pad_bottom
|
|
200
176
|
const max_offset = (canvas.height - this.pad_bottom) / 2;
|
|
201
177
|
const top_bound = max_offset;
|
|
202
178
|
const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
|
|
@@ -257,14 +233,10 @@ export class ZoomTool {
|
|
|
257
233
|
!viewport_height) {
|
|
258
234
|
return 1;
|
|
259
235
|
}
|
|
260
|
-
// Calculate available space accounting for padding
|
|
261
236
|
const available_width = viewport_width - this.border_padding * 2;
|
|
262
237
|
const available_height = viewport_height - this.border_padding * 2 - this.pad_bottom;
|
|
263
|
-
// Calculate zoom ratios for both dimensions
|
|
264
238
|
const width_ratio = available_width / container_width;
|
|
265
239
|
const height_ratio = available_height / container_height;
|
|
266
|
-
// Use the smaller ratio to ensure the image fits in both dimensions
|
|
267
|
-
// Note: we no longer cap at 1, as 'fit' can be above 1
|
|
268
240
|
return Math.min(width_ratio, height_ratio);
|
|
269
241
|
}
|
|
270
242
|
/**
|
|
@@ -274,7 +246,6 @@ export class ZoomTool {
|
|
|
274
246
|
*/
|
|
275
247
|
handle_touch_start(event) {
|
|
276
248
|
event.preventDefault();
|
|
277
|
-
// Cast to any to access TouchEvent properties
|
|
278
249
|
const touchEvent = event;
|
|
279
250
|
if (touchEvent.touches && touchEvent.touches.length === 2) {
|
|
280
251
|
this.is_pinching = true;
|
|
@@ -293,7 +264,6 @@ export class ZoomTool {
|
|
|
293
264
|
*/
|
|
294
265
|
handle_touch_move(event) {
|
|
295
266
|
event.preventDefault();
|
|
296
|
-
// Cast to any to access TouchEvent properties
|
|
297
267
|
const touchEvent = event;
|
|
298
268
|
if (this.is_pinching &&
|
|
299
269
|
touchEvent.touches &&
|
|
@@ -308,7 +278,6 @@ export class ZoomTool {
|
|
|
308
278
|
};
|
|
309
279
|
const scale_val = current_distance / this.last_pinch_distance;
|
|
310
280
|
this.last_pinch_distance = current_distance;
|
|
311
|
-
// Update zoom based on pinch
|
|
312
281
|
this.zoom_to_point(this.local_scale * scale_val, pinch_center);
|
|
313
282
|
}
|
|
314
283
|
else if (this.is_dragging &&
|
|
@@ -319,7 +288,6 @@ export class ZoomTool {
|
|
|
319
288
|
const current_position = new Point(touchEvent.touches[0].pageX - rect.left, touchEvent.touches[0].pageY - rect.top);
|
|
320
289
|
const dx = current_position.x - this.last_touch_position.x;
|
|
321
290
|
const dy = current_position.y - this.last_touch_position.y;
|
|
322
|
-
// Update position based on drag
|
|
323
291
|
this.image_editor_context.set_image_properties({
|
|
324
292
|
position: {
|
|
325
293
|
x: this.local_position.x + dx,
|
|
@@ -336,7 +304,6 @@ export class ZoomTool {
|
|
|
336
304
|
*/
|
|
337
305
|
handle_touch_end(event) {
|
|
338
306
|
event.preventDefault();
|
|
339
|
-
// Cast to any to access TouchEvent properties
|
|
340
307
|
const touchEvent = event;
|
|
341
308
|
if (touchEvent.touches && touchEvent.touches.length < 2) {
|
|
342
309
|
this.is_pinching = false;
|
|
@@ -364,7 +331,6 @@ export class ZoomTool {
|
|
|
364
331
|
const canvas = this.image_editor_context.app.screen;
|
|
365
332
|
const scaled_width = this.local_dimensions.width * this.local_scale;
|
|
366
333
|
const scaled_height = this.local_dimensions.height * this.local_scale;
|
|
367
|
-
// Calculate center position, accounting for pad_bottom
|
|
368
334
|
const center_position = {
|
|
369
335
|
x: (canvas.width - scaled_width) / 2,
|
|
370
336
|
y: (canvas.height - scaled_height - this.pad_bottom) / 2
|
|
@@ -403,34 +369,26 @@ export class ZoomTool {
|
|
|
403
369
|
* @private
|
|
404
370
|
*/
|
|
405
371
|
zoom_to_point(new_zoom, point, hard, is_fit_zoom) {
|
|
406
|
-
// Get the cursor position relative to the image's top-left corner
|
|
407
372
|
const cursor_relative_to_image = {
|
|
408
373
|
x: (point.x - this.local_position.x) / this.local_scale,
|
|
409
374
|
y: (point.y - this.local_position.y) / this.local_scale
|
|
410
375
|
};
|
|
411
|
-
// Calculate what percentage of the image width/height this represents
|
|
412
376
|
const image_percentages = {
|
|
413
377
|
x: cursor_relative_to_image.x / this.local_dimensions.width,
|
|
414
378
|
y: cursor_relative_to_image.y / this.local_dimensions.height
|
|
415
379
|
};
|
|
416
|
-
// Apply zoom limits
|
|
417
380
|
const fit_zoom = this.calculate_min_zoom(this.local_dimensions.width, this.local_dimensions.height);
|
|
418
|
-
// Use the fit zoom or 1, whichever is smaller as the minimum zoom
|
|
419
381
|
const min_zoom = Math.min(fit_zoom, 1);
|
|
420
382
|
new_zoom = Math.min(Math.max(new_zoom, min_zoom), this.max_zoom);
|
|
421
|
-
// Calculate new dimensions
|
|
422
383
|
const new_scaled_width = this.local_dimensions.width * new_zoom;
|
|
423
384
|
const new_scaled_height = this.local_dimensions.height * new_zoom;
|
|
424
|
-
// Calculate new position maintaining the same relative cursor position
|
|
425
385
|
let new_position = {
|
|
426
386
|
x: point.x - new_scaled_width * image_percentages.x,
|
|
427
387
|
y: point.y - new_scaled_height * image_percentages.y
|
|
428
388
|
};
|
|
429
|
-
// Center the image if we're at minimum zoom or if this is a fit zoom operation
|
|
430
389
|
if (new_zoom === min_zoom || is_fit_zoom) {
|
|
431
390
|
const canvas_width = this.image_editor_context.app.screen.width;
|
|
432
391
|
const canvas_height = this.image_editor_context.app.screen.height;
|
|
433
|
-
// Center the image, accounting for pad_bottom
|
|
434
392
|
new_position = {
|
|
435
393
|
x: (canvas_width - new_scaled_width) / 2,
|
|
436
394
|
y: (canvas_height - this.pad_bottom - new_scaled_height) / 2
|
|
@@ -449,7 +407,6 @@ export class ZoomTool {
|
|
|
449
407
|
* @private
|
|
450
408
|
*/
|
|
451
409
|
handle_pointer_down(event) {
|
|
452
|
-
// Only allow dragging if the current tool is 'pan'
|
|
453
410
|
if (event.button === 0 && this.current_tool === "pan") {
|
|
454
411
|
this.is_pointer_dragging = true;
|
|
455
412
|
this.drag_start.copyFrom(event.global);
|
|
@@ -463,7 +420,6 @@ export class ZoomTool {
|
|
|
463
420
|
* @private
|
|
464
421
|
*/
|
|
465
422
|
handle_pointer_move(event) {
|
|
466
|
-
// Only handle move if we're dragging and the tool is 'pan'
|
|
467
423
|
if (this.is_pointer_dragging && this.current_tool === "pan") {
|
|
468
424
|
const raw_position = {
|
|
469
425
|
x: event.global.x - this.drag_start.x,
|
|
@@ -475,12 +431,10 @@ export class ZoomTool {
|
|
|
475
431
|
const available_width = canvas.width - this.border_padding * 2;
|
|
476
432
|
const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
|
|
477
433
|
let final_position = { ...raw_position };
|
|
478
|
-
// If image is smaller than available space, center it
|
|
479
434
|
if (scaled_width <= available_width) {
|
|
480
435
|
final_position.x = (canvas.width - scaled_width) / 2;
|
|
481
436
|
}
|
|
482
437
|
else {
|
|
483
|
-
// Allow free movement during drag, bounds will be applied on pointer up
|
|
484
438
|
final_position.x = raw_position.x;
|
|
485
439
|
}
|
|
486
440
|
if (scaled_height <= available_height) {
|
|
@@ -488,7 +442,6 @@ export class ZoomTool {
|
|
|
488
442
|
(canvas.height - this.pad_bottom - scaled_height) / 2;
|
|
489
443
|
}
|
|
490
444
|
else {
|
|
491
|
-
// Allow free movement during drag, bounds will be applied on pointer up
|
|
492
445
|
final_position.y = raw_position.y;
|
|
493
446
|
}
|
|
494
447
|
this.image_editor_context.set_image_properties({
|
|
@@ -503,7 +456,6 @@ export class ZoomTool {
|
|
|
503
456
|
* @private
|
|
504
457
|
*/
|
|
505
458
|
handle_pointer_up(event) {
|
|
506
|
-
// Only handle up if we're dragging and the tool is 'pan'
|
|
507
459
|
if (this.is_pointer_dragging && this.current_tool === "pan") {
|
|
508
460
|
this.is_pointer_dragging = false;
|
|
509
461
|
const raw_position = {
|
|
@@ -516,12 +468,10 @@ export class ZoomTool {
|
|
|
516
468
|
const available_width = canvas.width - this.border_padding * 2;
|
|
517
469
|
const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
|
|
518
470
|
let final_position = { ...raw_position };
|
|
519
|
-
// If image is smaller than available space, center it
|
|
520
471
|
if (scaled_width <= available_width) {
|
|
521
472
|
final_position.x = (canvas.width - scaled_width) / 2;
|
|
522
473
|
}
|
|
523
474
|
else {
|
|
524
|
-
// Apply 50% rule for horizontal snapping
|
|
525
475
|
const max_offset = canvas.width / 2;
|
|
526
476
|
const left_bound = max_offset;
|
|
527
477
|
const right_bound = canvas.width - max_offset - scaled_width;
|
|
@@ -532,7 +482,6 @@ export class ZoomTool {
|
|
|
532
482
|
(canvas.height - this.pad_bottom - scaled_height) / 2;
|
|
533
483
|
}
|
|
534
484
|
else {
|
|
535
|
-
// Apply 50% rule for vertical snapping, accounting for pad_bottom
|
|
536
485
|
const max_offset = (canvas.height - this.pad_bottom) / 2;
|
|
537
486
|
const top_bound = max_offset;
|
|
538
487
|
const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"pixi-filters": "^6.1.0",
|
|
12
12
|
"pixi.js": "^8.7.3",
|
|
13
13
|
"tinycolor2": "^1.6.0",
|
|
14
|
-
"@gradio/client": "^1.
|
|
15
|
-
"@gradio/atoms": "^0.16.
|
|
14
|
+
"@gradio/client": "^1.15.1",
|
|
15
|
+
"@gradio/atoms": "^0.16.1",
|
|
16
16
|
"@gradio/icons": "^0.12.0",
|
|
17
|
-
"@gradio/image": "^0.22.
|
|
18
|
-
"@gradio/statustracker": "^0.10.
|
|
19
|
-
"@gradio/
|
|
17
|
+
"@gradio/image": "^0.22.7",
|
|
18
|
+
"@gradio/statustracker": "^0.10.12",
|
|
19
|
+
"@gradio/upload": "^0.16.6",
|
|
20
20
|
"@gradio/utils": "^0.10.2",
|
|
21
|
-
"@gradio/
|
|
21
|
+
"@gradio/wasm": "^0.18.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@gradio/preview": "^0.13.0"
|
package/shared/Controls.svelte
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createEventDispatcher } from "svelte";
|
|
3
3
|
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Check,
|
|
6
|
+
Trash,
|
|
7
|
+
ZoomIn,
|
|
8
|
+
ZoomOut,
|
|
9
|
+
Pan,
|
|
10
|
+
Download,
|
|
11
|
+
Undo,
|
|
12
|
+
Redo
|
|
13
|
+
} from "@gradio/icons";
|
|
14
|
+
import type { Writable } from "svelte/store";
|
|
5
15
|
|
|
6
16
|
export let can_save = false;
|
|
7
17
|
export let changeable = false;
|
|
@@ -9,6 +19,8 @@
|
|
|
9
19
|
export let tool: string;
|
|
10
20
|
export let min_zoom = true;
|
|
11
21
|
export let enable_download = false;
|
|
22
|
+
export let can_undo: boolean;
|
|
23
|
+
export let can_redo: boolean;
|
|
12
24
|
|
|
13
25
|
const dispatch = createEventDispatcher<{
|
|
14
26
|
remove_image: void;
|
|
@@ -114,6 +126,26 @@
|
|
|
114
126
|
</div>
|
|
115
127
|
<div class="separator"></div>
|
|
116
128
|
|
|
129
|
+
<IconButton
|
|
130
|
+
Icon={Undo}
|
|
131
|
+
label="Undo"
|
|
132
|
+
on:click={(event) => {
|
|
133
|
+
dispatch("undo");
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
}}
|
|
136
|
+
disabled={!can_undo}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<IconButton
|
|
140
|
+
Icon={Redo}
|
|
141
|
+
label="Redo"
|
|
142
|
+
on:click={(event) => {
|
|
143
|
+
dispatch("redo");
|
|
144
|
+
event.stopPropagation();
|
|
145
|
+
}}
|
|
146
|
+
disabled={!can_redo}
|
|
147
|
+
/>
|
|
148
|
+
|
|
117
149
|
{#if changeable}
|
|
118
150
|
<IconButton
|
|
119
151
|
disabled={!can_save}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { Client, FileData } from "@gradio/client";
|
|
16
16
|
import tinycolor, { type ColorInput } from "tinycolor2";
|
|
17
17
|
import { ZoomTool } from "./zoom/zoom";
|
|
18
|
-
import { type CommandManager, type CommandNode } from "./
|
|
18
|
+
import { type CommandManager, type CommandNode } from "./core/commands";
|
|
19
19
|
import { ImageEditor } from "./core/editor";
|
|
20
20
|
import { type Brush, type Eraser } from "./brush/types";
|
|
21
21
|
import { BrushTool } from "./brush/brush";
|
|
@@ -249,6 +249,8 @@
|
|
|
249
249
|
|
|
250
250
|
let crop: ImageEditor;
|
|
251
251
|
let crop_zoom: ZoomTool;
|
|
252
|
+
export let can_undo = false;
|
|
253
|
+
let can_redo = false;
|
|
252
254
|
async function init_image_editor(): Promise<void> {
|
|
253
255
|
brush = new BrushTool();
|
|
254
256
|
zoom = new ZoomTool();
|
|
@@ -306,6 +308,8 @@
|
|
|
306
308
|
|
|
307
309
|
editor.on("change", () => {
|
|
308
310
|
dispatch("change");
|
|
311
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
312
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
309
313
|
});
|
|
310
314
|
|
|
311
315
|
if (background || layers.length > 0) {
|
|
@@ -339,13 +343,6 @@
|
|
|
339
343
|
current_subtool === "crop" &&
|
|
340
344
|
crop_zoom.set_zoom("fit");
|
|
341
345
|
|
|
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
346
|
/**
|
|
350
347
|
* Handles file uploads
|
|
351
348
|
* @param {File[]} files - The uploaded files
|
|
@@ -364,8 +361,12 @@
|
|
|
364
361
|
dispatch("upload");
|
|
365
362
|
dispatch("input");
|
|
366
363
|
dispatch("change");
|
|
364
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
365
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
367
366
|
}
|
|
368
367
|
|
|
368
|
+
$: background_image = can_undo && editor.command_manager.contains("AddImage");
|
|
369
|
+
|
|
369
370
|
/**
|
|
370
371
|
* Handles tool change events
|
|
371
372
|
* @param {{ tool: ToolbarTool }} param0 - Object containing the selected tool
|
|
@@ -496,7 +497,6 @@
|
|
|
496
497
|
})()
|
|
497
498
|
);
|
|
498
499
|
|
|
499
|
-
// Type-safe brush size handling
|
|
500
500
|
$: brush?.set_brush_size(
|
|
501
501
|
typeof selected_size === "number" ? selected_size : 25
|
|
502
502
|
);
|
|
@@ -583,6 +583,18 @@
|
|
|
583
583
|
link.click();
|
|
584
584
|
URL.revokeObjectURL(url);
|
|
585
585
|
}
|
|
586
|
+
|
|
587
|
+
function handle_undo(): void {
|
|
588
|
+
editor.undo();
|
|
589
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
590
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function handle_redo(): void {
|
|
594
|
+
editor.redo();
|
|
595
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
596
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
597
|
+
}
|
|
586
598
|
</script>
|
|
587
599
|
|
|
588
600
|
<div
|
|
@@ -622,6 +634,10 @@
|
|
|
622
634
|
}}
|
|
623
635
|
enable_download={show_download_button}
|
|
624
636
|
on:download={() => handle_download()}
|
|
637
|
+
{can_undo}
|
|
638
|
+
{can_redo}
|
|
639
|
+
on:undo={handle_undo}
|
|
640
|
+
on:redo={handle_redo}
|
|
625
641
|
/>
|
|
626
642
|
{/if}
|
|
627
643
|
|
package/shared/Toolbar.svelte
CHANGED
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
import type { Source, Transform } from "./types";
|
|
31
31
|
import {
|
|
32
32
|
type Brush as BrushType,
|
|
33
|
-
type Eraser as EraserType
|
|
34
|
-
type ColorTuple
|
|
33
|
+
type Eraser as EraserType
|
|
35
34
|
} from "./brush/types";
|
|
36
35
|
import tinycolor from "tinycolor2";
|
|
37
36
|
|
|
@@ -78,13 +77,11 @@
|
|
|
78
77
|
})();
|
|
79
78
|
|
|
80
79
|
export let preview = false;
|
|
81
|
-
// export let opacity = 1;
|
|
82
80
|
export let show_brush_color = false;
|
|
83
81
|
export let show_brush_size = false;
|
|
84
82
|
export let show_eraser_size = false;
|
|
85
83
|
export let sources: Source[];
|
|
86
84
|
export let transforms: Transform[];
|
|
87
|
-
// Required by the component interface
|
|
88
85
|
let recent_colors: string[] = [];
|
|
89
86
|
|
|
90
87
|
let enable_layers = true;
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
|
|
22
22
|
$: _colors = show_empty ? colors : colors.filter((c) => c);
|
|
23
23
|
|
|
24
|
-
// Helper function to extract color from either a color input or a tuple
|
|
25
24
|
function get_color(color_input: ColorInput | ColorTuple): string {
|
|
26
25
|
if (Array.isArray(color_input)) {
|
|
27
26
|
return color_input[0] as string;
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
return color_input as string;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
// Helper function to extract opacity from a tuple or return undefined
|
|
33
31
|
function get_opacity(
|
|
34
32
|
color_input: ColorInput | ColorTuple
|
|
35
33
|
): number | undefined {
|
|
@@ -37,7 +35,6 @@
|
|
|
37
35
|
return color_input[1];
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
// For string colors, check if there's opacity in the color itself
|
|
41
38
|
const color = tinycolor(color_input);
|
|
42
39
|
return color.getAlpha();
|
|
43
40
|
}
|