@pooder/kit 3.5.0 → 4.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
@@ -753,13 +753,16 @@ function createFeatureItem(feature, center) {
753
753
  }
754
754
  return item;
755
755
  }
756
- function getDielineShape(options) {
756
+ function getPerimeterShape(options) {
757
757
  let mainShape = createBaseShape(options);
758
758
  const { features } = options;
759
759
  if (features && features.length > 0) {
760
+ const edgeFeatures = features.filter(
761
+ (f) => !f.placement || f.placement === "edge"
762
+ );
760
763
  const adds = [];
761
764
  const subtracts = [];
762
- features.forEach((f) => {
765
+ edgeFeatures.forEach((f) => {
763
766
  const pos = resolveFeaturePosition(f, options);
764
767
  const center = new paper2.Point(pos.x, pos.y);
765
768
  const item = createFeatureItem(f, center);
@@ -798,14 +801,42 @@ function getDielineShape(options) {
798
801
  }
799
802
  return mainShape;
800
803
  }
804
+ function applySurfaceFeatures(shape, features, options) {
805
+ const internalFeatures = features.filter((f) => f.placement === "internal");
806
+ if (internalFeatures.length === 0) return shape;
807
+ let result = shape;
808
+ for (const f of internalFeatures) {
809
+ const pos = resolveFeaturePosition(f, options);
810
+ const center = new paper2.Point(pos.x, pos.y);
811
+ const item = createFeatureItem(f, center);
812
+ try {
813
+ if (f.operation === "add") {
814
+ const temp = result.unite(item);
815
+ result.remove();
816
+ item.remove();
817
+ result = temp;
818
+ } else {
819
+ const temp = result.subtract(item);
820
+ result.remove();
821
+ item.remove();
822
+ result = temp;
823
+ }
824
+ } catch (e) {
825
+ console.error("Geometry: Failed to apply surface feature", e);
826
+ item.remove();
827
+ }
828
+ }
829
+ return result;
830
+ }
801
831
  function generateDielinePath(options) {
802
832
  const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
803
833
  const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
804
834
  ensurePaper(paperWidth, paperHeight);
805
835
  paper2.project.activeLayer.removeChildren();
806
- const mainShape = getDielineShape(options);
807
- const pathData = mainShape.pathData;
808
- mainShape.remove();
836
+ const perimeter = getPerimeterShape(options);
837
+ const finalShape = applySurfaceFeatures(perimeter, options.features, options);
838
+ const pathData = finalShape.pathData;
839
+ finalShape.remove();
809
840
  return pathData;
810
841
  }
811
842
  function generateMaskPath(options) {
@@ -816,7 +847,8 @@ function generateMaskPath(options) {
816
847
  point: [0, 0],
817
848
  size: [canvasWidth, canvasHeight]
818
849
  });
819
- const mainShape = getDielineShape(options);
850
+ const perimeter = getPerimeterShape(options);
851
+ const mainShape = applySurfaceFeatures(perimeter, options.features, options);
820
852
  const finalMask = maskRect.subtract(mainShape);
821
853
  maskRect.remove();
822
854
  mainShape.remove();
@@ -829,8 +861,10 @@ function generateBleedZonePath(originalOptions, offsetOptions, offset) {
829
861
  const paperHeight = originalOptions.canvasHeight || originalOptions.height * 2 || 2e3;
830
862
  ensurePaper(paperWidth, paperHeight);
831
863
  paper2.project.activeLayer.removeChildren();
832
- const shapeOriginal = getDielineShape(originalOptions);
833
- const shapeOffset = getDielineShape(offsetOptions);
864
+ const pOriginal = getPerimeterShape(originalOptions);
865
+ const shapeOriginal = applySurfaceFeatures(pOriginal, originalOptions.features, originalOptions);
866
+ const pOffset = getPerimeterShape(offsetOptions);
867
+ const shapeOffset = applySurfaceFeatures(pOffset, offsetOptions.features, offsetOptions);
834
868
  let bleedZone;
835
869
  if (offset > 0) {
836
870
  bleedZone = shapeOffset.subtract(shapeOriginal);
@@ -1304,16 +1338,10 @@ var DielineTool = class {
1304
1338
  radius: (f.radius || 0) * featureScale
1305
1339
  };
1306
1340
  });
1307
- const originalFeatures = absoluteFeatures.filter(
1308
- (f) => !f.target || f.target === "original" || f.target === "both"
1309
- );
1310
- const offsetFeatures = absoluteFeatures.filter(
1311
- (f) => f.target === "offset" || f.target === "both"
1312
- );
1341
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
1313
1342
  const cutW = Math.max(0, visualWidth + visualOffset * 2);
1314
1343
  const cutH = Math.max(0, visualHeight + visualOffset * 2);
1315
1344
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
1316
- const maskFeatures = visualOffset !== 0 ? offsetFeatures : originalFeatures;
1317
1345
  const maskPathData = generateMaskPath({
1318
1346
  canvasWidth: canvasW,
1319
1347
  canvasHeight: canvasH,
@@ -1323,7 +1351,7 @@ var DielineTool = class {
1323
1351
  radius: cutR,
1324
1352
  x: cx,
1325
1353
  y: cy,
1326
- features: maskFeatures,
1354
+ features: cutFeatures,
1327
1355
  pathData: this.state.pathData
1328
1356
  });
1329
1357
  const mask = new Path(maskPathData, {
@@ -1345,7 +1373,7 @@ var DielineTool = class {
1345
1373
  radius: cutR,
1346
1374
  x: cx,
1347
1375
  y: cy,
1348
- features: maskFeatures,
1376
+ features: cutFeatures,
1349
1377
  // Use same features as mask for consistency
1350
1378
  pathData: this.state.pathData,
1351
1379
  canvasWidth: canvasW,
@@ -1371,7 +1399,7 @@ var DielineTool = class {
1371
1399
  radius: visualRadius,
1372
1400
  x: cx,
1373
1401
  y: cy,
1374
- features: originalFeatures,
1402
+ features: cutFeatures,
1375
1403
  pathData: this.state.pathData,
1376
1404
  canvasWidth: canvasW,
1377
1405
  canvasHeight: canvasH
@@ -1383,7 +1411,7 @@ var DielineTool = class {
1383
1411
  radius: cutR,
1384
1412
  x: cx,
1385
1413
  y: cy,
1386
- features: offsetFeatures,
1414
+ features: cutFeatures,
1387
1415
  pathData: this.state.pathData,
1388
1416
  canvasWidth: canvasW,
1389
1417
  canvasHeight: canvasH
@@ -1412,7 +1440,7 @@ var DielineTool = class {
1412
1440
  radius: cutR,
1413
1441
  x: cx,
1414
1442
  y: cy,
1415
- features: offsetFeatures,
1443
+ features: cutFeatures,
1416
1444
  pathData: this.state.pathData,
1417
1445
  canvasWidth: canvasW,
1418
1446
  canvasHeight: canvasH
@@ -1436,7 +1464,7 @@ var DielineTool = class {
1436
1464
  radius: visualRadius,
1437
1465
  x: cx,
1438
1466
  y: cy,
1439
- features: originalFeatures,
1467
+ features: absoluteFeatures,
1440
1468
  pathData: this.state.pathData,
1441
1469
  canvasWidth: canvasW,
1442
1470
  canvasHeight: canvasH
@@ -1536,9 +1564,7 @@ var DielineTool = class {
1536
1564
  radius: (f.radius || 0) * featureScale
1537
1565
  };
1538
1566
  });
1539
- const originalFeatures = absoluteFeatures.filter(
1540
- (f) => !f.target || f.target === "original" || f.target === "both"
1541
- );
1567
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
1542
1568
  const generatedPathData = generateDielinePath({
1543
1569
  shape,
1544
1570
  width: visualWidth,
@@ -1546,7 +1572,7 @@ var DielineTool = class {
1546
1572
  radius: visualRadius,
1547
1573
  x: cx,
1548
1574
  y: cy,
1549
- features: originalFeatures,
1575
+ features: cutFeatures,
1550
1576
  pathData,
1551
1577
  canvasWidth: canvasW,
1552
1578
  canvasHeight: canvasH
@@ -1747,10 +1773,15 @@ var FeatureTool = class {
1747
1773
  };
1748
1774
  this.features = [];
1749
1775
  this.isUpdatingConfig = false;
1776
+ this.isToolActive = false;
1750
1777
  this.handleMoving = null;
1751
1778
  this.handleModified = null;
1752
1779
  this.handleDielineChange = null;
1753
1780
  this.currentGeometry = null;
1781
+ this.onToolActivated = (event) => {
1782
+ this.isToolActive = event.id === this.id;
1783
+ this.updateVisibility();
1784
+ };
1754
1785
  if (options) {
1755
1786
  Object.assign(this, options);
1756
1787
  }
@@ -1775,13 +1806,32 @@ var FeatureTool = class {
1775
1806
  }
1776
1807
  });
1777
1808
  }
1809
+ context.eventBus.on("tool:activated", this.onToolActivated);
1778
1810
  this.setup();
1779
1811
  }
1780
1812
  deactivate(context) {
1813
+ context.eventBus.off("tool:activated", this.onToolActivated);
1781
1814
  this.teardown();
1782
1815
  this.canvasService = void 0;
1783
1816
  this.context = void 0;
1784
1817
  }
1818
+ updateVisibility() {
1819
+ if (!this.canvasService) return;
1820
+ const canvas = this.canvasService.canvas;
1821
+ const markers = canvas.getObjects().filter((obj) => {
1822
+ var _a;
1823
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
1824
+ });
1825
+ markers.forEach((marker) => {
1826
+ marker.set({
1827
+ visible: this.isToolActive,
1828
+ // Or just selectable: false if we want them visible but locked
1829
+ selectable: this.isToolActive,
1830
+ evented: this.isToolActive
1831
+ });
1832
+ });
1833
+ canvas.requestRenderAll();
1834
+ }
1785
1835
  contribute() {
1786
1836
  return {
1787
1837
  [ContributionPointIds4.COMMANDS]: [
@@ -1834,7 +1884,7 @@ var FeatureTool = class {
1834
1884
  const newFeature = {
1835
1885
  id: Date.now().toString(),
1836
1886
  operation: type,
1837
- target: "original",
1887
+ placement: "edge",
1838
1888
  shape: "rect",
1839
1889
  x: 0.5,
1840
1890
  y: 0,
@@ -1868,6 +1918,7 @@ var FeatureTool = class {
1868
1918
  groupId,
1869
1919
  operation: "add",
1870
1920
  shape: "circle",
1921
+ placement: "edge",
1871
1922
  x: 0.5,
1872
1923
  y: 0,
1873
1924
  radius: lugRadius,
@@ -1879,6 +1930,7 @@ var FeatureTool = class {
1879
1930
  groupId,
1880
1931
  operation: "subtract",
1881
1932
  shape: "circle",
1933
+ placement: "edge",
1882
1934
  x: 0.5,
1883
1935
  y: 0,
1884
1936
  radius: holeRadius,
@@ -1895,14 +1947,6 @@ var FeatureTool = class {
1895
1947
  return true;
1896
1948
  }
1897
1949
  getGeometryForFeature(geometry, feature) {
1898
- if ((feature == null ? void 0 : feature.target) === "offset" && geometry.offset !== 0) {
1899
- return {
1900
- ...geometry,
1901
- width: geometry.width + geometry.offset * 2,
1902
- height: geometry.height + geometry.offset * 2,
1903
- radius: geometry.radius === 0 ? 0 : Math.max(0, geometry.radius + geometry.offset)
1904
- };
1905
- }
1906
1950
  return geometry;
1907
1951
  }
1908
1952
  setup() {
@@ -1959,7 +2003,7 @@ var FeatureTool = class {
1959
2003
  const markerStrokeWidth = (target.strokeWidth || 2) * (target.scaleX || 1);
1960
2004
  const minDim = Math.min(target.getScaledWidth(), target.getScaledHeight());
1961
2005
  const limit = Math.max(0, minDim / 2 - markerStrokeWidth);
1962
- const snapped = this.constrainPosition(p, geometry, limit);
2006
+ const snapped = this.constrainPosition(p, geometry, limit, feature);
1963
2007
  target.set({
1964
2008
  left: snapped.x,
1965
2009
  top: snapped.y
@@ -2043,7 +2087,17 @@ var FeatureTool = class {
2043
2087
  objects.forEach((obj) => canvas.remove(obj));
2044
2088
  this.canvasService.requestRenderAll();
2045
2089
  }
2046
- constrainPosition(p, geometry, limit) {
2090
+ constrainPosition(p, geometry, limit, feature) {
2091
+ if (feature && feature.placement === "internal") {
2092
+ const minX = geometry.x - geometry.width / 2;
2093
+ const maxX = geometry.x + geometry.width / 2;
2094
+ const minY = geometry.y - geometry.height / 2;
2095
+ const maxY = geometry.y + geometry.height / 2;
2096
+ return {
2097
+ x: Math.max(minX, Math.min(maxX, p.x)),
2098
+ y: Math.max(minY, Math.min(maxY, p.y))
2099
+ };
2100
+ }
2047
2101
  const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, {
2048
2102
  ...geometry,
2049
2103
  features: []
@@ -2168,7 +2222,9 @@ var FeatureTool = class {
2168
2222
  const pos = resolveFeaturePosition(feature, geometry2);
2169
2223
  const marker = createMarkerShape(feature, pos);
2170
2224
  marker.set({
2171
- selectable: true,
2225
+ visible: this.isToolActive,
2226
+ selectable: this.isToolActive,
2227
+ evented: this.isToolActive,
2172
2228
  hasControls: false,
2173
2229
  hasBorders: false,
2174
2230
  hoverCursor: "move",
@@ -2211,7 +2267,9 @@ var FeatureTool = class {
2211
2267
  return createMarkerShape(feature, pos);
2212
2268
  });
2213
2269
  const groupObj = new Group(shapes, {
2214
- selectable: true,
2270
+ visible: this.isToolActive,
2271
+ selectable: this.isToolActive,
2272
+ evented: this.isToolActive,
2215
2273
  hasControls: false,
2216
2274
  hasBorders: false,
2217
2275
  hoverCursor: "move",
@@ -2285,7 +2343,8 @@ var FeatureTool = class {
2285
2343
  const snapped = this.constrainPosition(
2286
2344
  new Point(marker.left, marker.top),
2287
2345
  geometry,
2288
- limit
2346
+ limit,
2347
+ feature
2289
2348
  );
2290
2349
  marker.set({ left: snapped.x, top: snapped.y });
2291
2350
  marker.setCoords();
@@ -2309,6 +2368,11 @@ var ImageTool = class {
2309
2368
  this.objectMap = /* @__PURE__ */ new Map();
2310
2369
  this.loadResolvers = /* @__PURE__ */ new Map();
2311
2370
  this.isUpdatingConfig = false;
2371
+ this.isToolActive = false;
2372
+ this.onToolActivated = (event) => {
2373
+ this.isToolActive = event.id === this.id;
2374
+ this.updateInteractivity();
2375
+ };
2312
2376
  }
2313
2377
  activate(context) {
2314
2378
  this.context = context;
@@ -2317,6 +2381,7 @@ var ImageTool = class {
2317
2381
  console.warn("CanvasService not found for ImageTool");
2318
2382
  return;
2319
2383
  }
2384
+ context.eventBus.on("tool:activated", this.onToolActivated);
2320
2385
  const configService = context.services.get(
2321
2386
  "ConfigurationService"
2322
2387
  );
@@ -2334,6 +2399,7 @@ var ImageTool = class {
2334
2399
  this.updateImages();
2335
2400
  }
2336
2401
  deactivate(context) {
2402
+ context.eventBus.off("tool:activated", this.onToolActivated);
2337
2403
  if (this.canvasService) {
2338
2404
  const layer = this.canvasService.getLayer("user");
2339
2405
  if (layer) {
@@ -2347,6 +2413,18 @@ var ImageTool = class {
2347
2413
  this.context = void 0;
2348
2414
  }
2349
2415
  }
2416
+ updateInteractivity() {
2417
+ var _a;
2418
+ this.objectMap.forEach((obj) => {
2419
+ obj.set({
2420
+ selectable: this.isToolActive,
2421
+ evented: this.isToolActive,
2422
+ hasControls: this.isToolActive,
2423
+ hasBorders: this.isToolActive
2424
+ });
2425
+ });
2426
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2427
+ }
2350
2428
  contribute() {
2351
2429
  return {
2352
2430
  [ContributionPointIds5.CONFIGURATIONS]: [
@@ -2534,6 +2612,14 @@ var ImageTool = class {
2534
2612
  const layout = this.getLayoutInfo();
2535
2613
  this.items.forEach((item, index) => {
2536
2614
  let obj = this.objectMap.get(item.id);
2615
+ if (obj && obj.getSrc) {
2616
+ const currentSrc = obj.getSrc();
2617
+ if (currentSrc !== item.url) {
2618
+ layer.remove(obj);
2619
+ this.objectMap.delete(item.id);
2620
+ obj = void 0;
2621
+ }
2622
+ }
2537
2623
  if (!obj) {
2538
2624
  this.loadImage(item, layer, layout);
2539
2625
  } else {
@@ -2590,7 +2676,11 @@ var ImageTool = class {
2590
2676
  originY: "center",
2591
2677
  data: { id: item.id },
2592
2678
  uniformScaling: true,
2593
- lockScalingFlip: true
2679
+ lockScalingFlip: true,
2680
+ selectable: this.isToolActive,
2681
+ evented: this.isToolActive,
2682
+ hasControls: this.isToolActive,
2683
+ hasBorders: this.isToolActive
2594
2684
  });
2595
2685
  image.setControlsVisibility({
2596
2686
  mt: false,
@@ -3447,6 +3537,24 @@ var CanvasService = class {
3447
3537
  ...options
3448
3538
  });
3449
3539
  }
3540
+ if (options == null ? void 0 : options.eventBus) {
3541
+ this.setEventBus(options.eventBus);
3542
+ }
3543
+ }
3544
+ setEventBus(eventBus) {
3545
+ this.eventBus = eventBus;
3546
+ this.setupEvents();
3547
+ }
3548
+ setupEvents() {
3549
+ if (!this.eventBus) return;
3550
+ const bus = this.eventBus;
3551
+ const forward = (name) => (e) => bus.emit(name, e);
3552
+ this.canvas.on("selection:created", forward("selection:created"));
3553
+ this.canvas.on("selection:updated", forward("selection:updated"));
3554
+ this.canvas.on("selection:cleared", forward("selection:cleared"));
3555
+ this.canvas.on("object:modified", forward("object:modified"));
3556
+ this.canvas.on("object:added", forward("object:added"));
3557
+ this.canvas.on("object:removed", forward("object:removed"));
3450
3558
  }
3451
3559
  dispose() {
3452
3560
  this.canvas.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "3.5.0",
3
+ "version": "4.0.0",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "paper": "^0.12.18",
21
21
  "fabric": "^7.0.0",
22
- "@pooder/core": "1.2.0"
22
+ "@pooder/core": "2.0.0"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup src/index.ts --format cjs,esm --dts",
@@ -1,65 +1,89 @@
1
- import { Canvas, Group, FabricObject } from "fabric";
2
- import { Service } from "@pooder/core";
3
-
4
- export default class CanvasService implements Service {
5
- public canvas: Canvas;
6
-
7
- constructor(el: HTMLCanvasElement | string | Canvas, options?: any) {
8
- if (el instanceof Canvas) {
9
- this.canvas = el;
10
- } else {
11
- this.canvas = new Canvas(el, {
12
- preserveObjectStacking: true,
13
- ...options,
14
- });
15
- }
16
- }
17
-
18
- dispose() {
19
- this.canvas.dispose();
20
- }
21
-
22
- /**
23
- * Get a layer (Group) by its ID.
24
- * We assume layers are Groups directly on the canvas with a data.id property.
25
- */
26
- getLayer(id: string): Group | undefined {
27
- return this.canvas.getObjects().find((obj: any) => obj.data?.id === id) as
28
- | Group
29
- | undefined;
30
- }
31
-
32
- /**
33
- * Create a layer (Group) with the given ID if it doesn't exist.
34
- */
35
- createLayer(id: string, options: any = {}): Group {
36
- let layer = this.getLayer(id);
37
- if (!layer) {
38
- const defaultOptions = {
39
- selectable: false,
40
- evented: false,
41
- ...options,
42
- data: { ...options.data, id },
43
- };
44
- layer = new Group([], defaultOptions);
45
- this.canvas.add(layer);
46
- }
47
- return layer;
48
- }
49
-
50
- /**
51
- * Find an object by ID, optionally within a specific layer.
52
- */
53
- getObject(id: string, layerId?: string): FabricObject | undefined {
54
- if (layerId) {
55
- const layer = this.getLayer(layerId);
56
- if (!layer) return undefined;
57
- return layer.getObjects().find((obj: any) => obj.data?.id === id);
58
- }
59
- return this.canvas.getObjects().find((obj: any) => obj.data?.id === id);
60
- }
61
-
62
- requestRenderAll() {
63
- this.canvas.requestRenderAll();
64
- }
65
- }
1
+ import { Canvas, Group, FabricObject } from "fabric";
2
+ import { Service, EventBus } from "@pooder/core";
3
+
4
+ export default class CanvasService implements Service {
5
+ public canvas: Canvas;
6
+ private eventBus?: EventBus;
7
+
8
+ constructor(el: HTMLCanvasElement | string | Canvas, options?: any) {
9
+ if (el instanceof Canvas) {
10
+ this.canvas = el;
11
+ } else {
12
+ this.canvas = new Canvas(el, {
13
+ preserveObjectStacking: true,
14
+ ...options,
15
+ });
16
+ }
17
+
18
+ if (options?.eventBus) {
19
+ this.setEventBus(options.eventBus);
20
+ }
21
+ }
22
+
23
+ setEventBus(eventBus: EventBus) {
24
+ this.eventBus = eventBus;
25
+ this.setupEvents();
26
+ }
27
+
28
+ private setupEvents() {
29
+ if (!this.eventBus) return;
30
+ const bus = this.eventBus;
31
+
32
+ const forward = (name: string) => (e: any) => bus.emit(name, e);
33
+
34
+ this.canvas.on("selection:created", forward("selection:created"));
35
+ this.canvas.on("selection:updated", forward("selection:updated"));
36
+ this.canvas.on("selection:cleared", forward("selection:cleared"));
37
+ this.canvas.on("object:modified", forward("object:modified"));
38
+ this.canvas.on("object:added", forward("object:added"));
39
+ this.canvas.on("object:removed", forward("object:removed"));
40
+ }
41
+
42
+ dispose() {
43
+ this.canvas.dispose();
44
+ }
45
+
46
+ /**
47
+ * Get a layer (Group) by its ID.
48
+ * We assume layers are Groups directly on the canvas with a data.id property.
49
+ */
50
+ getLayer(id: string): Group | undefined {
51
+ return this.canvas.getObjects().find((obj: any) => obj.data?.id === id) as
52
+ | Group
53
+ | undefined;
54
+ }
55
+
56
+ /**
57
+ * Create a layer (Group) with the given ID if it doesn't exist.
58
+ */
59
+ createLayer(id: string, options: any = {}): Group {
60
+ let layer = this.getLayer(id);
61
+ if (!layer) {
62
+ const defaultOptions = {
63
+ selectable: false,
64
+ evented: false,
65
+ ...options,
66
+ data: { ...options.data, id },
67
+ };
68
+ layer = new Group([], defaultOptions);
69
+ this.canvas.add(layer);
70
+ }
71
+ return layer;
72
+ }
73
+
74
+ /**
75
+ * Find an object by ID, optionally within a specific layer.
76
+ */
77
+ getObject(id: string, layerId?: string): FabricObject | undefined {
78
+ if (layerId) {
79
+ const layer = this.getLayer(layerId);
80
+ if (!layer) return undefined;
81
+ return layer.getObjects().find((obj: any) => obj.data?.id === id);
82
+ }
83
+ return this.canvas.getObjects().find((obj: any) => obj.data?.id === id);
84
+ }
85
+
86
+ requestRenderAll() {
87
+ this.canvas.requestRenderAll();
88
+ }
89
+ }