@pooder/kit 0.0.2 → 2.0.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/dist/index.mjs CHANGED
@@ -1,5 +1,9 @@
1
1
  // src/background.ts
2
- import { Image, PooderLayer, Rect } from "@pooder/core";
2
+ import {
3
+ Image,
4
+ PooderLayer,
5
+ Rect
6
+ } from "@pooder/core";
3
7
  var BackgroundTool = class {
4
8
  constructor() {
5
9
  this.name = "BackgroundTool";
@@ -82,10 +86,10 @@ var BackgroundTool = class {
82
86
  editor.canvas.add(backgroundLayer);
83
87
  editor.canvas.sendObjectToBack(backgroundLayer);
84
88
  }
85
- this.updateBackground(editor, this.options);
86
89
  }
87
90
  onMount(editor) {
88
91
  this.initLayer(editor);
92
+ this.updateBackground(editor, this.options);
89
93
  }
90
94
  onUnmount(editor) {
91
95
  const layer = editor.getLayer("background");
@@ -149,8 +153,7 @@ var BackgroundTool = class {
149
153
  }
150
154
  });
151
155
  img.scaleToWidth(width);
152
- if (img.getScaledHeight() < height)
153
- img.scaleToHeight(height);
156
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
154
157
  layer.add(img);
155
158
  }
156
159
  }
@@ -162,7 +165,11 @@ var BackgroundTool = class {
162
165
  };
163
166
 
164
167
  // src/dieline.ts
165
- import { Path, PooderLayer as PooderLayer2, Pattern } from "@pooder/core";
168
+ import {
169
+ Path,
170
+ PooderLayer as PooderLayer2,
171
+ Pattern
172
+ } from "@pooder/core";
166
173
 
167
174
  // src/geometry.ts
168
175
  import paper from "paper";
@@ -315,7 +322,8 @@ var DielineTool = class {
315
322
  offset: 0,
316
323
  style: "solid",
317
324
  insideColor: "rgba(0,0,0,0)",
318
- outsideColor: "#ffffff"
325
+ outsideColor: "#ffffff",
326
+ showBleedLines: true
319
327
  };
320
328
  this.schema = {
321
329
  shape: {
@@ -330,6 +338,7 @@ var DielineTool = class {
330
338
  // Complex object, simplified for now or need custom handler
331
339
  borderLength: { type: "number", min: 0, max: 500, label: "Margin" },
332
340
  offset: { type: "number", min: -100, max: 100, label: "Bleed Offset" },
341
+ showBleedLines: { type: "boolean", label: "Show Bleed Lines" },
333
342
  style: {
334
343
  type: "select",
335
344
  options: ["solid", "dashed"],
@@ -349,7 +358,8 @@ var DielineTool = class {
349
358
  offset: 0,
350
359
  style: "solid",
351
360
  insideColor: "rgba(0,0,0,0)",
352
- outsideColor: "#ffffff"
361
+ outsideColor: "#ffffff",
362
+ showBleedLines: true
353
363
  };
354
364
  this.updateDieline(editor);
355
365
  return true;
@@ -363,7 +373,8 @@ var DielineTool = class {
363
373
  },
364
374
  setDimensions: {
365
375
  execute: (editor, width, height) => {
366
- if (this.options.width === width && this.options.height === height) return true;
376
+ if (this.options.width === width && this.options.height === height)
377
+ return true;
367
378
  this.options.width = width;
368
379
  this.options.height = height;
369
380
  this.updateDieline(editor);
@@ -418,11 +429,100 @@ var DielineTool = class {
418
429
  required: true
419
430
  }
420
431
  }
432
+ },
433
+ exportCutImage: {
434
+ execute: (editor) => {
435
+ var _a, _b, _c, _d;
436
+ const { shape, width, height, radius, position } = this.options;
437
+ const canvasW = editor.canvas.width || 800;
438
+ const canvasH = editor.canvas.height || 600;
439
+ const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
440
+ const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
441
+ const holeTool = editor.getExtension("HoleTool");
442
+ const holes = holeTool ? holeTool.options.holes || [] : [];
443
+ const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
444
+ const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
445
+ const holeData = holes.map((h) => ({
446
+ x: h.x,
447
+ y: h.y,
448
+ innerRadius,
449
+ outerRadius
450
+ }));
451
+ const pathData = generateDielinePath({
452
+ shape,
453
+ width,
454
+ height,
455
+ radius,
456
+ x: cx,
457
+ y: cy,
458
+ holes: holeData
459
+ });
460
+ const clipPath = new Path(pathData, {
461
+ left: 0,
462
+ top: 0,
463
+ originX: "left",
464
+ originY: "top",
465
+ absolutePositioned: true
466
+ });
467
+ const layer = this.getLayer(editor, "dieline-overlay");
468
+ const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
469
+ if (layer) layer.visible = false;
470
+ const holeMarkers = editor.canvas.getObjects().filter((o) => {
471
+ var _a2;
472
+ return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
473
+ });
474
+ holeMarkers.forEach((o) => o.visible = false);
475
+ const rulerLayer = editor.canvas.getObjects().find((obj) => {
476
+ var _a2;
477
+ return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
478
+ });
479
+ const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
480
+ if (rulerLayer) rulerLayer.visible = false;
481
+ const originalClip = editor.canvas.clipPath;
482
+ editor.canvas.clipPath = clipPath;
483
+ const bbox = clipPath.getBoundingRect();
484
+ const holeDataRelative = holes.map((h) => ({
485
+ x: h.x - bbox.left,
486
+ y: h.y - bbox.top,
487
+ innerRadius,
488
+ outerRadius
489
+ }));
490
+ const clipPathCorrected = new Path(pathData, {
491
+ absolutePositioned: true,
492
+ left: 0,
493
+ top: 0
494
+ });
495
+ const tempPath = new Path(pathData);
496
+ const tempBounds = tempPath.getBoundingRect();
497
+ clipPathCorrected.set({
498
+ left: tempBounds.left,
499
+ top: tempBounds.top,
500
+ originX: "left",
501
+ originY: "top"
502
+ });
503
+ editor.canvas.clipPath = clipPathCorrected;
504
+ const exportBbox = clipPathCorrected.getBoundingRect();
505
+ const dataURL = editor.canvas.toDataURL({
506
+ format: "png",
507
+ multiplier: 2,
508
+ left: exportBbox.left,
509
+ top: exportBbox.top,
510
+ width: exportBbox.width,
511
+ height: exportBbox.height
512
+ });
513
+ editor.canvas.clipPath = originalClip;
514
+ if (layer) layer.visible = wasVisible;
515
+ if (rulerLayer) rulerLayer.visible = rulerWasVisible;
516
+ holeMarkers.forEach((o) => o.visible = true);
517
+ editor.canvas.requestRenderAll();
518
+ return dataURL;
519
+ }
421
520
  }
422
521
  };
423
522
  }
424
523
  onMount(editor) {
425
524
  this.createLayer(editor);
525
+ this.updateDieline(editor);
426
526
  }
427
527
  onUnmount(editor) {
428
528
  this.destroyLayer(editor);
@@ -454,7 +554,6 @@ var DielineTool = class {
454
554
  editor.canvas.add(layer);
455
555
  }
456
556
  editor.canvas.bringObjectToFront(layer);
457
- this.updateDieline(editor);
458
557
  }
459
558
  destroyLayer(editor) {
460
559
  const layer = this.getLayer(editor, "dieline-overlay");
@@ -484,7 +583,17 @@ var DielineTool = class {
484
583
  }
485
584
  updateDieline(editor) {
486
585
  var _a, _b;
487
- const { shape, radius, offset, style, insideColor, outsideColor, position, borderLength } = this.options;
586
+ const {
587
+ shape,
588
+ radius,
589
+ offset,
590
+ style,
591
+ insideColor,
592
+ outsideColor,
593
+ position,
594
+ borderLength,
595
+ showBleedLines
596
+ } = this.options;
488
597
  let { width, height } = this.options;
489
598
  const canvasW = editor.canvas.width || 800;
490
599
  const canvasH = editor.canvas.height || 600;
@@ -498,6 +607,9 @@ var DielineTool = class {
498
607
  if (!layer) return;
499
608
  layer.remove(...layer.getObjects());
500
609
  const holeTool = editor.getExtension("HoleTool");
610
+ if (holeTool && typeof holeTool.enforceConstraints === "function") {
611
+ holeTool.enforceConstraints(editor);
612
+ }
501
613
  const holes = holeTool ? holeTool.options.holes || [] : [];
502
614
  const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
503
615
  const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
@@ -554,27 +666,32 @@ var DielineTool = class {
554
666
  layer.add(insideObj);
555
667
  }
556
668
  if (offset !== 0) {
557
- const bleedPathData = generateBleedZonePath({
558
- shape,
559
- width,
560
- height,
561
- radius,
562
- x: cx,
563
- y: cy,
564
- holes: holeData
565
- }, offset);
566
- const pattern = this.createHatchPattern("red");
567
- if (pattern) {
568
- const bleedObj = new Path(bleedPathData, {
569
- fill: pattern,
570
- stroke: null,
571
- selectable: false,
572
- evented: false,
573
- objectCaching: false,
574
- originX: "left",
575
- originY: "top"
576
- });
577
- layer.add(bleedObj);
669
+ const bleedPathData = generateBleedZonePath(
670
+ {
671
+ shape,
672
+ width,
673
+ height,
674
+ radius,
675
+ x: cx,
676
+ y: cy,
677
+ holes: holeData
678
+ },
679
+ offset
680
+ );
681
+ if (showBleedLines !== false) {
682
+ const pattern = this.createHatchPattern("red");
683
+ if (pattern) {
684
+ const bleedObj = new Path(bleedPathData, {
685
+ fill: pattern,
686
+ stroke: null,
687
+ selectable: false,
688
+ evented: false,
689
+ objectCaching: false,
690
+ originX: "left",
691
+ originY: "top"
692
+ });
693
+ layer.add(bleedObj);
694
+ }
578
695
  }
579
696
  const offsetPathData = generateDielinePath({
580
697
  shape,
@@ -646,7 +763,10 @@ var DielineTool = class {
646
763
  };
647
764
 
648
765
  // src/film.ts
649
- import { Image as Image2, PooderLayer as PooderLayer3 } from "@pooder/core";
766
+ import {
767
+ Image as Image2,
768
+ PooderLayer as PooderLayer3
769
+ } from "@pooder/core";
650
770
  var FilmTool = class {
651
771
  constructor() {
652
772
  this.name = "FilmTool";
@@ -670,7 +790,8 @@ var FilmTool = class {
670
790
  this.commands = {
671
791
  setFilmImage: {
672
792
  execute: (editor, url, opacity) => {
673
- if (this.options.url === url && this.options.opacity === opacity) return true;
793
+ if (this.options.url === url && this.options.opacity === opacity)
794
+ return true;
674
795
  this.options.url = url;
675
796
  this.options.opacity = opacity;
676
797
  this.updateFilm(editor, this.options);
@@ -708,6 +829,7 @@ var FilmTool = class {
708
829
  }
709
830
  }
710
831
  onUpdate(editor, state) {
832
+ this.updateFilm(editor, this.options);
711
833
  }
712
834
  initLayer(editor) {
713
835
  let overlayLayer = editor.getLayer("overlay");
@@ -760,8 +882,7 @@ var FilmTool = class {
760
882
  } else {
761
883
  img = await Image2.fromURL(url, { crossOrigin: "anonymous" });
762
884
  img.scaleToWidth(width);
763
- if (img.getScaledHeight() < height)
764
- img.scaleToHeight(height);
885
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
765
886
  img.set({
766
887
  originX: "left",
767
888
  originY: "top",
@@ -782,7 +903,11 @@ var FilmTool = class {
782
903
  };
783
904
 
784
905
  // src/hole.ts
785
- import { Circle as Circle2, Group, Point } from "@pooder/core";
906
+ import {
907
+ Circle as Circle2,
908
+ Group,
909
+ Point
910
+ } from "@pooder/core";
786
911
  var HoleTool = class {
787
912
  constructor() {
788
913
  this.name = "HoleTool";
@@ -790,7 +915,8 @@ var HoleTool = class {
790
915
  innerRadius: 15,
791
916
  outerRadius: 25,
792
917
  style: "solid",
793
- holes: []
918
+ holes: [],
919
+ constraintTarget: "bleed"
794
920
  };
795
921
  this.schema = {
796
922
  innerRadius: {
@@ -810,6 +936,11 @@ var HoleTool = class {
810
936
  options: ["solid", "dashed"],
811
937
  label: "Line Style"
812
938
  },
939
+ constraintTarget: {
940
+ type: "select",
941
+ options: ["original", "bleed"],
942
+ label: "Constraint Target"
943
+ },
813
944
  holes: {
814
945
  type: "json",
815
946
  label: "Holes"
@@ -824,7 +955,10 @@ var HoleTool = class {
824
955
  const g = this.getDielineGeometry(editor);
825
956
  if (g) {
826
957
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
827
- defaultPos = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
958
+ defaultPos = getNearestPointOnDieline(topCenter, {
959
+ ...g,
960
+ holes: []
961
+ });
828
962
  }
829
963
  this.options = {
830
964
  innerRadius: 15,
@@ -891,7 +1025,7 @@ var HoleTool = class {
891
1025
  if (!dielineTool) return null;
892
1026
  const geometry = dielineTool.getGeometry(editor);
893
1027
  if (!geometry) return null;
894
- const offset = dielineTool.options.offset || 0;
1028
+ const offset = this.options.constraintTarget === "original" ? 0 : dielineTool.options.offset || 0;
895
1029
  return {
896
1030
  ...geometry,
897
1031
  width: Math.max(0, geometry.width + offset * 2),
@@ -899,6 +1033,39 @@ var HoleTool = class {
899
1033
  radius: Math.max(0, geometry.radius + offset)
900
1034
  };
901
1035
  }
1036
+ enforceConstraints(editor) {
1037
+ const geometry = this.getDielineGeometry(editor);
1038
+ if (!geometry) return;
1039
+ const objects = editor.canvas.getObjects().filter((obj) => {
1040
+ var _a;
1041
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1042
+ });
1043
+ let changed = false;
1044
+ objects.sort(
1045
+ (a, b) => {
1046
+ var _a, _b, _c, _d;
1047
+ return ((_b = (_a = a.data) == null ? void 0 : _a.index) != null ? _b : 0) - ((_d = (_c = b.data) == null ? void 0 : _c.index) != null ? _d : 0);
1048
+ }
1049
+ );
1050
+ const newHoles = [];
1051
+ objects.forEach((obj) => {
1052
+ const currentPos = new Point(obj.left, obj.top);
1053
+ const newPos = this.calculateConstrainedPosition(currentPos, geometry);
1054
+ if (currentPos.distanceFrom(newPos) > 0.1) {
1055
+ obj.set({
1056
+ left: newPos.x,
1057
+ top: newPos.y
1058
+ });
1059
+ obj.setCoords();
1060
+ changed = true;
1061
+ }
1062
+ newHoles.push({ x: obj.left, y: obj.top });
1063
+ });
1064
+ if (changed) {
1065
+ this.options.holes = newHoles;
1066
+ editor.canvas.requestRenderAll();
1067
+ }
1068
+ }
902
1069
  setup(editor) {
903
1070
  if (!this.handleMoving) {
904
1071
  this.handleMoving = (e) => {
@@ -931,7 +1098,10 @@ var HoleTool = class {
931
1098
  const g = this.getDielineGeometry(editor);
932
1099
  if (g) {
933
1100
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
934
- const snapped = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
1101
+ const snapped = getNearestPointOnDieline(topCenter, {
1102
+ ...g,
1103
+ holes: []
1104
+ });
935
1105
  defaultPos = snapped;
936
1106
  }
937
1107
  opts.holes = [defaultPos];
@@ -960,6 +1130,12 @@ var HoleTool = class {
960
1130
  editor.canvas.requestRenderAll();
961
1131
  }
962
1132
  onUpdate(editor, state) {
1133
+ this.enforceConstraints(editor);
1134
+ this.redraw(editor);
1135
+ const dielineTool = editor.getExtension("DielineTool");
1136
+ if (dielineTool && dielineTool.updateDieline) {
1137
+ dielineTool.updateDieline(editor);
1138
+ }
963
1139
  }
964
1140
  syncHolesFromCanvas(editor) {
965
1141
  const objects = editor.canvas.getObjects().filter((obj) => {
@@ -1048,7 +1224,10 @@ var HoleTool = class {
1048
1224
  holes: []
1049
1225
  // We don't need holes for boundary calculation
1050
1226
  };
1051
- const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, options);
1227
+ const nearest = getNearestPointOnDieline(
1228
+ { x: p.x, y: p.y },
1229
+ options
1230
+ );
1052
1231
  const nearestP = new Point(nearest.x, nearest.y);
1053
1232
  const dist = p.distanceFrom(nearestP);
1054
1233
  const v = p.subtract(nearestP);
@@ -1075,10 +1254,16 @@ var HoleTool = class {
1075
1254
  };
1076
1255
 
1077
1256
  // src/image.ts
1078
- import { Image as Image3, PooderLayer as PooderLayer4 } from "@pooder/core";
1257
+ import {
1258
+ Image as Image3,
1259
+ PooderLayer as PooderLayer4,
1260
+ util,
1261
+ Point as Point2
1262
+ } from "@pooder/core";
1079
1263
  var ImageTool = class {
1080
1264
  constructor() {
1081
1265
  this.name = "ImageTool";
1266
+ this._loadingUrl = null;
1082
1267
  this.options = {
1083
1268
  url: "",
1084
1269
  opacity: 1
@@ -1094,14 +1279,44 @@ var ImageTool = class {
1094
1279
  max: 1,
1095
1280
  step: 0.1,
1096
1281
  label: "Opacity"
1282
+ },
1283
+ width: {
1284
+ type: "number",
1285
+ label: "Width",
1286
+ min: 0,
1287
+ max: 5e3
1288
+ },
1289
+ height: {
1290
+ type: "number",
1291
+ label: "Height",
1292
+ min: 0,
1293
+ max: 5e3
1294
+ },
1295
+ angle: {
1296
+ type: "number",
1297
+ label: "Rotation",
1298
+ min: 0,
1299
+ max: 360
1300
+ },
1301
+ left: {
1302
+ type: "number",
1303
+ label: "Left",
1304
+ min: 0,
1305
+ max: 1e3
1306
+ },
1307
+ top: {
1308
+ type: "number",
1309
+ label: "Top",
1310
+ min: 0,
1311
+ max: 1e3
1097
1312
  }
1098
1313
  };
1099
1314
  this.commands = {
1100
1315
  setUserImage: {
1101
- execute: (editor, url, opacity) => {
1102
- if (this.options.url === url && this.options.opacity === opacity) return true;
1103
- this.options.url = url;
1104
- this.options.opacity = opacity;
1316
+ execute: (editor, url, opacity, width, height, angle, left, top) => {
1317
+ if (this.options.url === url && this.options.opacity === opacity && this.options.width === width && this.options.height === height && this.options.angle === angle && this.options.left === left && this.options.top === top)
1318
+ return true;
1319
+ this.options = { url, opacity, width, height, angle, left, top };
1105
1320
  this.updateImage(editor, this.options);
1106
1321
  return true;
1107
1322
  },
@@ -1117,13 +1332,19 @@ var ImageTool = class {
1117
1332
  min: 0,
1118
1333
  max: 1,
1119
1334
  required: true
1120
- }
1335
+ },
1336
+ width: { type: "number", label: "Width" },
1337
+ height: { type: "number", label: "Height" },
1338
+ angle: { type: "number", label: "Angle" },
1339
+ left: { type: "number", label: "Left" },
1340
+ top: { type: "number", label: "Top" }
1121
1341
  }
1122
1342
  }
1123
1343
  };
1124
1344
  }
1125
1345
  onMount(editor) {
1126
1346
  this.ensureLayer(editor);
1347
+ this.updateImage(editor, this.options);
1127
1348
  }
1128
1349
  onUnmount(editor) {
1129
1350
  const layer = editor.getLayer("user");
@@ -1161,43 +1382,101 @@ var ImageTool = class {
1161
1382
  }
1162
1383
  updateImage(editor, opts) {
1163
1384
  var _a, _b;
1164
- let { url, opacity } = opts;
1385
+ let { url, opacity, width, height, angle, left, top } = opts;
1165
1386
  const layer = editor.getLayer("user");
1166
1387
  if (!layer) {
1167
1388
  console.warn("[ImageTool] User layer not found");
1168
1389
  return;
1169
1390
  }
1170
1391
  const userImage = editor.getObject("user-image", "user");
1392
+ if (this._loadingUrl === url) return;
1171
1393
  if (userImage) {
1172
1394
  const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1173
1395
  if (currentSrc !== url) {
1174
- this.loadImage(editor, layer, url, opacity, userImage);
1396
+ this.loadImage(editor, layer, opts);
1175
1397
  } else {
1176
- if (userImage.opacity !== opacity) {
1177
- userImage.set({ opacity });
1398
+ const updates = {};
1399
+ const centerX = editor.state.width / 2;
1400
+ const centerY = editor.state.height / 2;
1401
+ if (userImage.opacity !== opacity) updates.opacity = opacity;
1402
+ if (angle !== void 0 && userImage.angle !== angle)
1403
+ updates.angle = angle;
1404
+ if (left !== void 0) {
1405
+ const localLeft = left - centerX;
1406
+ if (Math.abs(userImage.left - localLeft) > 1)
1407
+ updates.left = localLeft;
1408
+ }
1409
+ if (top !== void 0) {
1410
+ const localTop = top - centerY;
1411
+ if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
1412
+ }
1413
+ if (width !== void 0 && userImage.width)
1414
+ updates.scaleX = width / userImage.width;
1415
+ if (height !== void 0 && userImage.height)
1416
+ updates.scaleY = height / userImage.height;
1417
+ if (Object.keys(updates).length > 0) {
1418
+ userImage.set(updates);
1178
1419
  editor.canvas.requestRenderAll();
1179
1420
  }
1180
1421
  }
1181
1422
  } else {
1182
- this.loadImage(editor, layer, url, opacity);
1423
+ this.loadImage(editor, layer, opts);
1183
1424
  }
1184
1425
  }
1185
- loadImage(editor, layer, url, opacity, oldImage) {
1426
+ loadImage(editor, layer, opts) {
1427
+ const { url } = opts;
1428
+ this._loadingUrl = url;
1186
1429
  Image3.fromURL(url).then((image) => {
1187
- if (oldImage) {
1188
- const { left, top, scaleX, scaleY, angle } = oldImage;
1189
- image.set({ left, top, scaleX, scaleY, angle });
1190
- layer.remove(oldImage);
1430
+ if (this._loadingUrl !== url) return;
1431
+ this._loadingUrl = null;
1432
+ const currentOpts = this.options;
1433
+ const { opacity, width, height, angle, left, top } = currentOpts;
1434
+ const existingImage = editor.getObject("user-image", "user");
1435
+ if (existingImage) {
1436
+ const defaultLeft = existingImage.left;
1437
+ const defaultTop = existingImage.top;
1438
+ const defaultAngle = existingImage.angle;
1439
+ const defaultScaleX = existingImage.scaleX;
1440
+ const defaultScaleY = existingImage.scaleY;
1441
+ image.set({
1442
+ left: left !== void 0 ? left : defaultLeft,
1443
+ top: top !== void 0 ? top : defaultTop,
1444
+ angle: angle !== void 0 ? angle : defaultAngle,
1445
+ scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
1446
+ scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
1447
+ });
1448
+ layer.remove(existingImage);
1449
+ } else {
1450
+ if (width !== void 0 && image.width)
1451
+ image.scaleX = width / image.width;
1452
+ if (height !== void 0 && image.height)
1453
+ image.scaleY = height / image.height;
1454
+ if (angle !== void 0) image.angle = angle;
1455
+ if (left !== void 0) image.left = left;
1456
+ if (top !== void 0) image.top = top;
1191
1457
  }
1192
1458
  image.set({
1193
- opacity,
1459
+ opacity: opacity !== void 0 ? opacity : 1,
1194
1460
  data: {
1195
1461
  id: "user-image"
1196
1462
  }
1197
1463
  });
1198
1464
  layer.add(image);
1465
+ image.on("modified", (e) => {
1466
+ const matrix = image.calcTransformMatrix();
1467
+ const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
1468
+ this.options.left = globalPoint.x;
1469
+ this.options.top = globalPoint.y;
1470
+ this.options.angle = e.target.angle;
1471
+ if (image.width)
1472
+ this.options.width = e.target.width * e.target.scaleX;
1473
+ if (image.height)
1474
+ this.options.height = e.target.height * e.target.scaleY;
1475
+ editor.emit("update");
1476
+ });
1199
1477
  editor.canvas.requestRenderAll();
1200
1478
  }).catch((err) => {
1479
+ if (this._loadingUrl === url) this._loadingUrl = null;
1201
1480
  console.error("Failed to load image", url, err);
1202
1481
  });
1203
1482
  }
@@ -1225,7 +1504,8 @@ var WhiteInkTool = class {
1225
1504
  this.commands = {
1226
1505
  setWhiteInkImage: {
1227
1506
  execute: (editor, customMask, opacity, enableClip = true) => {
1228
- if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip) return true;
1507
+ if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip)
1508
+ return true;
1229
1509
  this.options.customMask = customMask;
1230
1510
  this.options.opacity = opacity;
1231
1511
  this.options.enableClip = enableClip;
@@ -1257,6 +1537,7 @@ var WhiteInkTool = class {
1257
1537
  }
1258
1538
  onMount(editor) {
1259
1539
  this.setup(editor);
1540
+ this.updateWhiteInk(editor, this.options);
1260
1541
  }
1261
1542
  onUnmount(editor) {
1262
1543
  this.teardown(editor);
@@ -1297,7 +1578,6 @@ var WhiteInkTool = class {
1297
1578
  editor.canvas.on("object:rotating", this.syncHandler);
1298
1579
  editor.canvas.on("object:modified", this.syncHandler);
1299
1580
  }
1300
- this.updateWhiteInk(editor, this.options);
1301
1581
  }
1302
1582
  teardown(editor) {
1303
1583
  if (this.syncHandler) {
@@ -1346,7 +1626,14 @@ var WhiteInkTool = class {
1346
1626
  if (whiteInk) {
1347
1627
  const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1348
1628
  if (currentSrc !== customMask) {
1349
- this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
1629
+ this.loadWhiteInk(
1630
+ editor,
1631
+ layer,
1632
+ customMask,
1633
+ opacity,
1634
+ enableClip,
1635
+ whiteInk
1636
+ );
1350
1637
  } else {
1351
1638
  if (whiteInk.opacity !== opacity) {
1352
1639
  whiteInk.set({ opacity });
@@ -1375,10 +1662,12 @@ var WhiteInkTool = class {
1375
1662
  if (oldImage) {
1376
1663
  layer.remove(oldImage);
1377
1664
  }
1378
- (_a = image.filters) == null ? void 0 : _a.push(new filters.BlendColor({
1379
- color: "#FFFFFF",
1380
- mode: "add"
1381
- }));
1665
+ (_a = image.filters) == null ? void 0 : _a.push(
1666
+ new filters.BlendColor({
1667
+ color: "#FFFFFF",
1668
+ mode: "add"
1669
+ })
1670
+ );
1382
1671
  image.applyFilters();
1383
1672
  image.set({
1384
1673
  opacity,
@@ -1446,7 +1735,12 @@ var WhiteInkTool = class {
1446
1735
  };
1447
1736
 
1448
1737
  // src/ruler.ts
1449
- import { PooderLayer as PooderLayer6, Rect as Rect3, Line, Text } from "@pooder/core";
1738
+ import {
1739
+ PooderLayer as PooderLayer6,
1740
+ Rect as Rect3,
1741
+ Line,
1742
+ Text
1743
+ } from "@pooder/core";
1450
1744
  var RulerTool = class {
1451
1745
  constructor() {
1452
1746
  this.name = "RulerTool";
@@ -1490,7 +1784,8 @@ var RulerTool = class {
1490
1784
  setTheme: {
1491
1785
  execute: (editor, theme) => {
1492
1786
  const newOptions = { ...this.options, ...theme };
1493
- if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
1787
+ if (JSON.stringify(newOptions) === JSON.stringify(this.options))
1788
+ return true;
1494
1789
  this.options = newOptions;
1495
1790
  this.updateRuler(editor);
1496
1791
  return true;
@@ -1507,11 +1802,13 @@ var RulerTool = class {
1507
1802
  }
1508
1803
  onMount(editor) {
1509
1804
  this.createLayer(editor);
1805
+ this.updateRuler(editor);
1510
1806
  }
1511
1807
  onUnmount(editor) {
1512
1808
  this.destroyLayer(editor);
1513
1809
  }
1514
1810
  onUpdate(editor, state) {
1811
+ this.updateRuler(editor);
1515
1812
  }
1516
1813
  onDestroy(editor) {
1517
1814
  this.destroyLayer(editor);
@@ -1537,7 +1834,6 @@ var RulerTool = class {
1537
1834
  editor.canvas.add(layer);
1538
1835
  }
1539
1836
  editor.canvas.bringObjectToFront(layer);
1540
- this.updateRuler(editor);
1541
1837
  }
1542
1838
  destroyLayer(editor) {
1543
1839
  const layer = this.getLayer(editor);
@@ -1643,12 +1939,83 @@ var RulerTool = class {
1643
1939
  editor.canvas.requestRenderAll();
1644
1940
  }
1645
1941
  };
1942
+
1943
+ // src/mirror.ts
1944
+ var MirrorTool = class {
1945
+ constructor() {
1946
+ this.name = "MirrorTool";
1947
+ this.options = {
1948
+ enabled: false
1949
+ };
1950
+ this.schema = {
1951
+ enabled: {
1952
+ type: "boolean",
1953
+ label: "Mirror View"
1954
+ }
1955
+ };
1956
+ this.commands = {
1957
+ toggleMirror: {
1958
+ execute: (editor) => {
1959
+ this.options.enabled = !this.options.enabled;
1960
+ this.applyMirror(editor, this.options.enabled);
1961
+ return true;
1962
+ }
1963
+ },
1964
+ setMirror: {
1965
+ execute: (editor, enabled) => {
1966
+ if (this.options.enabled === enabled) return true;
1967
+ this.options.enabled = enabled;
1968
+ this.applyMirror(editor, enabled);
1969
+ return true;
1970
+ },
1971
+ schema: {
1972
+ enabled: {
1973
+ type: "boolean",
1974
+ label: "Enabled",
1975
+ required: true
1976
+ }
1977
+ }
1978
+ }
1979
+ };
1980
+ }
1981
+ onMount(editor) {
1982
+ if (this.options.enabled) {
1983
+ this.applyMirror(editor, true);
1984
+ }
1985
+ }
1986
+ onUpdate(editor) {
1987
+ this.applyMirror(editor, this.options.enabled);
1988
+ }
1989
+ onUnmount(editor) {
1990
+ this.applyMirror(editor, false);
1991
+ }
1992
+ applyMirror(editor, enabled) {
1993
+ const canvas = editor.canvas;
1994
+ if (!canvas) return;
1995
+ const width = canvas.width || 800;
1996
+ let vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
1997
+ vpt = [...vpt];
1998
+ const isFlipped = vpt[0] < 0;
1999
+ if (enabled && !isFlipped) {
2000
+ vpt[0] = -vpt[0];
2001
+ vpt[4] = width - vpt[4];
2002
+ canvas.setViewportTransform(vpt);
2003
+ canvas.requestRenderAll();
2004
+ } else if (!enabled && isFlipped) {
2005
+ vpt[0] = -vpt[0];
2006
+ vpt[4] = width - vpt[4];
2007
+ canvas.setViewportTransform(vpt);
2008
+ canvas.requestRenderAll();
2009
+ }
2010
+ }
2011
+ };
1646
2012
  export {
1647
2013
  BackgroundTool,
1648
2014
  DielineTool,
1649
2015
  FilmTool,
1650
2016
  HoleTool,
1651
2017
  ImageTool,
2018
+ MirrorTool,
1652
2019
  RulerTool,
1653
2020
  WhiteInkTool
1654
2021
  };