@pooder/kit 1.0.0 → 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";
@@ -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,6 +429,94 @@ 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
  }
@@ -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);
@@ -761,8 +882,7 @@ var FilmTool = class {
761
882
  } else {
762
883
  img = await Image2.fromURL(url, { crossOrigin: "anonymous" });
763
884
  img.scaleToWidth(width);
764
- if (img.getScaledHeight() < height)
765
- img.scaleToHeight(height);
885
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
766
886
  img.set({
767
887
  originX: "left",
768
888
  originY: "top",
@@ -783,7 +903,11 @@ var FilmTool = class {
783
903
  };
784
904
 
785
905
  // src/hole.ts
786
- import { Circle as Circle2, Group, Point } from "@pooder/core";
906
+ import {
907
+ Circle as Circle2,
908
+ Group,
909
+ Point
910
+ } from "@pooder/core";
787
911
  var HoleTool = class {
788
912
  constructor() {
789
913
  this.name = "HoleTool";
@@ -791,7 +915,8 @@ var HoleTool = class {
791
915
  innerRadius: 15,
792
916
  outerRadius: 25,
793
917
  style: "solid",
794
- holes: []
918
+ holes: [],
919
+ constraintTarget: "bleed"
795
920
  };
796
921
  this.schema = {
797
922
  innerRadius: {
@@ -811,6 +936,11 @@ var HoleTool = class {
811
936
  options: ["solid", "dashed"],
812
937
  label: "Line Style"
813
938
  },
939
+ constraintTarget: {
940
+ type: "select",
941
+ options: ["original", "bleed"],
942
+ label: "Constraint Target"
943
+ },
814
944
  holes: {
815
945
  type: "json",
816
946
  label: "Holes"
@@ -825,7 +955,10 @@ var HoleTool = class {
825
955
  const g = this.getDielineGeometry(editor);
826
956
  if (g) {
827
957
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
828
- defaultPos = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
958
+ defaultPos = getNearestPointOnDieline(topCenter, {
959
+ ...g,
960
+ holes: []
961
+ });
829
962
  }
830
963
  this.options = {
831
964
  innerRadius: 15,
@@ -892,7 +1025,7 @@ var HoleTool = class {
892
1025
  if (!dielineTool) return null;
893
1026
  const geometry = dielineTool.getGeometry(editor);
894
1027
  if (!geometry) return null;
895
- const offset = dielineTool.options.offset || 0;
1028
+ const offset = this.options.constraintTarget === "original" ? 0 : dielineTool.options.offset || 0;
896
1029
  return {
897
1030
  ...geometry,
898
1031
  width: Math.max(0, geometry.width + offset * 2),
@@ -900,6 +1033,39 @@ var HoleTool = class {
900
1033
  radius: Math.max(0, geometry.radius + offset)
901
1034
  };
902
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
+ }
903
1069
  setup(editor) {
904
1070
  if (!this.handleMoving) {
905
1071
  this.handleMoving = (e) => {
@@ -932,7 +1098,10 @@ var HoleTool = class {
932
1098
  const g = this.getDielineGeometry(editor);
933
1099
  if (g) {
934
1100
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
935
- const snapped = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
1101
+ const snapped = getNearestPointOnDieline(topCenter, {
1102
+ ...g,
1103
+ holes: []
1104
+ });
936
1105
  defaultPos = snapped;
937
1106
  }
938
1107
  opts.holes = [defaultPos];
@@ -961,6 +1130,12 @@ var HoleTool = class {
961
1130
  editor.canvas.requestRenderAll();
962
1131
  }
963
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
+ }
964
1139
  }
965
1140
  syncHolesFromCanvas(editor) {
966
1141
  const objects = editor.canvas.getObjects().filter((obj) => {
@@ -1049,7 +1224,10 @@ var HoleTool = class {
1049
1224
  holes: []
1050
1225
  // We don't need holes for boundary calculation
1051
1226
  };
1052
- const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, options);
1227
+ const nearest = getNearestPointOnDieline(
1228
+ { x: p.x, y: p.y },
1229
+ options
1230
+ );
1053
1231
  const nearestP = new Point(nearest.x, nearest.y);
1054
1232
  const dist = p.distanceFrom(nearestP);
1055
1233
  const v = p.subtract(nearestP);
@@ -1076,10 +1254,16 @@ var HoleTool = class {
1076
1254
  };
1077
1255
 
1078
1256
  // src/image.ts
1079
- 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";
1080
1263
  var ImageTool = class {
1081
1264
  constructor() {
1082
1265
  this.name = "ImageTool";
1266
+ this._loadingUrl = null;
1083
1267
  this.options = {
1084
1268
  url: "",
1085
1269
  opacity: 1
@@ -1095,14 +1279,44 @@ var ImageTool = class {
1095
1279
  max: 1,
1096
1280
  step: 0.1,
1097
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
1098
1312
  }
1099
1313
  };
1100
1314
  this.commands = {
1101
1315
  setUserImage: {
1102
- execute: (editor, url, opacity) => {
1103
- if (this.options.url === url && this.options.opacity === opacity) return true;
1104
- this.options.url = url;
1105
- 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 };
1106
1320
  this.updateImage(editor, this.options);
1107
1321
  return true;
1108
1322
  },
@@ -1118,7 +1332,12 @@ var ImageTool = class {
1118
1332
  min: 0,
1119
1333
  max: 1,
1120
1334
  required: true
1121
- }
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" }
1122
1341
  }
1123
1342
  }
1124
1343
  };
@@ -1163,43 +1382,101 @@ var ImageTool = class {
1163
1382
  }
1164
1383
  updateImage(editor, opts) {
1165
1384
  var _a, _b;
1166
- let { url, opacity } = opts;
1385
+ let { url, opacity, width, height, angle, left, top } = opts;
1167
1386
  const layer = editor.getLayer("user");
1168
1387
  if (!layer) {
1169
1388
  console.warn("[ImageTool] User layer not found");
1170
1389
  return;
1171
1390
  }
1172
1391
  const userImage = editor.getObject("user-image", "user");
1392
+ if (this._loadingUrl === url) return;
1173
1393
  if (userImage) {
1174
1394
  const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1175
1395
  if (currentSrc !== url) {
1176
- this.loadImage(editor, layer, url, opacity, userImage);
1396
+ this.loadImage(editor, layer, opts);
1177
1397
  } else {
1178
- if (userImage.opacity !== opacity) {
1179
- 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);
1180
1419
  editor.canvas.requestRenderAll();
1181
1420
  }
1182
1421
  }
1183
1422
  } else {
1184
- this.loadImage(editor, layer, url, opacity);
1423
+ this.loadImage(editor, layer, opts);
1185
1424
  }
1186
1425
  }
1187
- loadImage(editor, layer, url, opacity, oldImage) {
1426
+ loadImage(editor, layer, opts) {
1427
+ const { url } = opts;
1428
+ this._loadingUrl = url;
1188
1429
  Image3.fromURL(url).then((image) => {
1189
- if (oldImage) {
1190
- const { left, top, scaleX, scaleY, angle } = oldImage;
1191
- image.set({ left, top, scaleX, scaleY, angle });
1192
- 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;
1193
1457
  }
1194
1458
  image.set({
1195
- opacity,
1459
+ opacity: opacity !== void 0 ? opacity : 1,
1196
1460
  data: {
1197
1461
  id: "user-image"
1198
1462
  }
1199
1463
  });
1200
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
+ });
1201
1477
  editor.canvas.requestRenderAll();
1202
1478
  }).catch((err) => {
1479
+ if (this._loadingUrl === url) this._loadingUrl = null;
1203
1480
  console.error("Failed to load image", url, err);
1204
1481
  });
1205
1482
  }
@@ -1227,7 +1504,8 @@ var WhiteInkTool = class {
1227
1504
  this.commands = {
1228
1505
  setWhiteInkImage: {
1229
1506
  execute: (editor, customMask, opacity, enableClip = true) => {
1230
- 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;
1231
1509
  this.options.customMask = customMask;
1232
1510
  this.options.opacity = opacity;
1233
1511
  this.options.enableClip = enableClip;
@@ -1348,7 +1626,14 @@ var WhiteInkTool = class {
1348
1626
  if (whiteInk) {
1349
1627
  const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1350
1628
  if (currentSrc !== customMask) {
1351
- this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
1629
+ this.loadWhiteInk(
1630
+ editor,
1631
+ layer,
1632
+ customMask,
1633
+ opacity,
1634
+ enableClip,
1635
+ whiteInk
1636
+ );
1352
1637
  } else {
1353
1638
  if (whiteInk.opacity !== opacity) {
1354
1639
  whiteInk.set({ opacity });
@@ -1377,10 +1662,12 @@ var WhiteInkTool = class {
1377
1662
  if (oldImage) {
1378
1663
  layer.remove(oldImage);
1379
1664
  }
1380
- (_a = image.filters) == null ? void 0 : _a.push(new filters.BlendColor({
1381
- color: "#FFFFFF",
1382
- mode: "add"
1383
- }));
1665
+ (_a = image.filters) == null ? void 0 : _a.push(
1666
+ new filters.BlendColor({
1667
+ color: "#FFFFFF",
1668
+ mode: "add"
1669
+ })
1670
+ );
1384
1671
  image.applyFilters();
1385
1672
  image.set({
1386
1673
  opacity,
@@ -1448,7 +1735,12 @@ var WhiteInkTool = class {
1448
1735
  };
1449
1736
 
1450
1737
  // src/ruler.ts
1451
- 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";
1452
1744
  var RulerTool = class {
1453
1745
  constructor() {
1454
1746
  this.name = "RulerTool";
@@ -1492,7 +1784,8 @@ var RulerTool = class {
1492
1784
  setTheme: {
1493
1785
  execute: (editor, theme) => {
1494
1786
  const newOptions = { ...this.options, ...theme };
1495
- if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
1787
+ if (JSON.stringify(newOptions) === JSON.stringify(this.options))
1788
+ return true;
1496
1789
  this.options = newOptions;
1497
1790
  this.updateRuler(editor);
1498
1791
  return true;
@@ -1646,12 +1939,83 @@ var RulerTool = class {
1646
1939
  editor.canvas.requestRenderAll();
1647
1940
  }
1648
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
+ };
1649
2012
  export {
1650
2013
  BackgroundTool,
1651
2014
  DielineTool,
1652
2015
  FilmTool,
1653
2016
  HoleTool,
1654
2017
  ImageTool,
2018
+ MirrorTool,
1655
2019
  RulerTool,
1656
2020
  WhiteInkTool
1657
2021
  };