@pooder/kit 6.0.1 → 6.1.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.
Files changed (91) hide show
  1. package/.test-dist/src/extensions/background/BackgroundTool.js +524 -0
  2. package/.test-dist/src/extensions/background/index.js +17 -0
  3. package/.test-dist/src/extensions/dieline/DielineTool.js +748 -0
  4. package/.test-dist/src/extensions/dieline/commands.js +127 -0
  5. package/.test-dist/src/extensions/dieline/config.js +107 -0
  6. package/.test-dist/src/extensions/dieline/index.js +21 -0
  7. package/.test-dist/src/extensions/dieline/model.js +2 -0
  8. package/.test-dist/src/extensions/dieline/renderer.js +2 -0
  9. package/.test-dist/src/extensions/feature/FeatureTool.js +914 -0
  10. package/.test-dist/src/extensions/feature/index.js +17 -0
  11. package/.test-dist/src/extensions/film/FilmTool.js +207 -0
  12. package/.test-dist/src/extensions/film/index.js +17 -0
  13. package/.test-dist/src/extensions/image/ImageTool.js +1499 -0
  14. package/.test-dist/src/extensions/image/commands.js +162 -0
  15. package/.test-dist/src/extensions/image/config.js +129 -0
  16. package/.test-dist/src/extensions/image/index.js +21 -0
  17. package/.test-dist/src/extensions/image/model.js +2 -0
  18. package/.test-dist/src/extensions/image/renderer.js +5 -0
  19. package/.test-dist/src/extensions/mirror/MirrorTool.js +104 -0
  20. package/.test-dist/src/extensions/mirror/index.js +17 -0
  21. package/.test-dist/src/extensions/ruler/RulerTool.js +442 -0
  22. package/.test-dist/src/extensions/ruler/index.js +17 -0
  23. package/.test-dist/src/extensions/sceneLayout.js +2 -93
  24. package/.test-dist/src/extensions/sceneLayoutModel.js +15 -200
  25. package/.test-dist/src/extensions/size/SizeTool.js +332 -0
  26. package/.test-dist/src/extensions/size/index.js +17 -0
  27. package/.test-dist/src/extensions/white-ink/WhiteInkTool.js +1003 -0
  28. package/.test-dist/src/extensions/white-ink/commands.js +148 -0
  29. package/.test-dist/src/extensions/white-ink/config.js +31 -0
  30. package/.test-dist/src/extensions/white-ink/index.js +21 -0
  31. package/.test-dist/src/extensions/white-ink/model.js +2 -0
  32. package/.test-dist/src/extensions/white-ink/renderer.js +5 -0
  33. package/.test-dist/src/services/SceneLayoutService.js +96 -0
  34. package/.test-dist/src/services/index.js +1 -0
  35. package/.test-dist/src/shared/constants/layers.js +25 -0
  36. package/.test-dist/src/shared/imaging/sourceSizeCache.js +82 -0
  37. package/.test-dist/src/shared/index.js +22 -0
  38. package/.test-dist/src/shared/runtime/sessionState.js +74 -0
  39. package/.test-dist/src/shared/runtime/subscriptions.js +30 -0
  40. package/.test-dist/src/shared/scene/frame.js +34 -0
  41. package/.test-dist/src/shared/scene/sceneLayoutModel.js +202 -0
  42. package/.test-dist/tests/run.js +116 -0
  43. package/CHANGELOG.md +14 -0
  44. package/dist/index.d.mts +390 -367
  45. package/dist/index.d.ts +390 -367
  46. package/dist/index.js +5138 -4927
  47. package/dist/index.mjs +1149 -1977
  48. package/dist/tracer-PO7CRBYY.mjs +1016 -0
  49. package/package.json +2 -2
  50. package/src/extensions/{background.ts → background/BackgroundTool.ts} +33 -50
  51. package/src/extensions/background/index.ts +1 -0
  52. package/src/extensions/{dieline.ts → dieline/DielineTool.ts} +14 -218
  53. package/src/extensions/dieline/commands.ts +109 -0
  54. package/src/extensions/dieline/config.ts +106 -0
  55. package/src/extensions/dieline/index.ts +5 -0
  56. package/src/extensions/dieline/model.ts +1 -0
  57. package/src/extensions/dieline/renderer.ts +1 -0
  58. package/src/extensions/{feature.ts → feature/FeatureTool.ts} +27 -21
  59. package/src/extensions/feature/index.ts +1 -0
  60. package/src/extensions/{film.ts → film/FilmTool.ts} +36 -48
  61. package/src/extensions/film/index.ts +1 -0
  62. package/src/extensions/{image.ts → image/ImageTool.ts} +123 -402
  63. package/src/extensions/image/commands.ts +176 -0
  64. package/src/extensions/image/config.ts +128 -0
  65. package/src/extensions/image/index.ts +5 -0
  66. package/src/extensions/image/model.ts +1 -0
  67. package/src/extensions/image/renderer.ts +1 -0
  68. package/src/extensions/{mirror.ts → mirror/MirrorTool.ts} +1 -1
  69. package/src/extensions/mirror/index.ts +1 -0
  70. package/src/extensions/{ruler.ts → ruler/RulerTool.ts} +4 -5
  71. package/src/extensions/ruler/index.ts +1 -0
  72. package/src/extensions/sceneLayout.ts +1 -140
  73. package/src/extensions/sceneLayoutModel.ts +1 -364
  74. package/src/extensions/{size.ts → size/SizeTool.ts} +7 -6
  75. package/src/extensions/size/index.ts +1 -0
  76. package/src/extensions/{white-ink.ts → white-ink/WhiteInkTool.ts} +130 -317
  77. package/src/extensions/white-ink/commands.ts +157 -0
  78. package/src/extensions/white-ink/config.ts +30 -0
  79. package/src/extensions/white-ink/index.ts +5 -0
  80. package/src/extensions/white-ink/model.ts +1 -0
  81. package/src/extensions/white-ink/renderer.ts +1 -0
  82. package/src/services/SceneLayoutService.ts +139 -0
  83. package/src/services/index.ts +1 -0
  84. package/src/shared/constants/layers.ts +23 -0
  85. package/src/shared/imaging/sourceSizeCache.ts +103 -0
  86. package/src/shared/index.ts +6 -0
  87. package/src/shared/runtime/sessionState.ts +105 -0
  88. package/src/shared/runtime/subscriptions.ts +45 -0
  89. package/src/shared/scene/frame.ts +46 -0
  90. package/src/shared/scene/sceneLayoutModel.ts +367 -0
  91. package/tests/run.ts +146 -0
@@ -2,8 +2,6 @@ import {
2
2
  Extension,
3
3
  ExtensionContext,
4
4
  ContributionPointIds,
5
- CommandContribution,
6
- ConfigurationContribution,
7
5
  ConfigurationService,
8
6
  ToolSessionService,
9
7
  WorkbenchService,
@@ -16,15 +14,36 @@ import {
16
14
  Point,
17
15
  controlsUtils,
18
16
  } from "fabric";
19
- import { CanvasService, RenderLayoutRect, RenderObjectSpec } from "../services";
20
- import { isDielineShape, normalizeShapeStyle } from "./dielineShape";
21
- import type { DielineShape, DielineShapeStyle } from "./dielineShape";
22
- import { generateDielinePath, getPathBounds } from "./geometry";
17
+ import { CanvasService, RenderLayoutRect, RenderObjectSpec } from "../../services";
18
+ import { isDielineShape, normalizeShapeStyle } from "../dielineShape";
19
+ import type { DielineShape, DielineShapeStyle } from "../dielineShape";
20
+ import { generateDielinePath, getPathBounds } from "../geometry";
23
21
  import {
24
22
  buildSceneGeometry,
25
23
  computeSceneLayout,
26
24
  readSizeState,
27
- } from "./sceneLayoutModel";
25
+ } from "../../shared/scene/sceneLayoutModel";
26
+ import {
27
+ type FrameRect,
28
+ resolveCutFrameRect,
29
+ toLayoutSceneRect as toSceneLayoutRect,
30
+ } from "../../shared/scene/frame";
31
+ import {
32
+ createSourceSizeCache,
33
+ getCoverScale as getCoverScaleFromRect,
34
+ type SourceSize,
35
+ } from "../../shared/imaging/sourceSizeCache";
36
+ import { SubscriptionBag } from "../../shared/runtime/subscriptions";
37
+ import {
38
+ applyCommittedSnapshot,
39
+ runDeferredConfigUpdate,
40
+ } from "../../shared/runtime/sessionState";
41
+ import {
42
+ IMAGE_OBJECT_LAYER_ID,
43
+ IMAGE_OVERLAY_LAYER_ID,
44
+ } from "../../shared/constants/layers";
45
+ import { createImageCommands } from "./commands";
46
+ import { createImageConfigurations } from "./config";
28
47
 
29
48
  export interface ImageItem {
30
49
  id: string;
@@ -38,18 +57,6 @@ export interface ImageItem {
38
57
  committedUrl?: string;
39
58
  }
40
59
 
41
- interface FrameRect {
42
- left: number;
43
- top: number;
44
- width: number;
45
- height: number;
46
- }
47
-
48
- interface SourceSize {
49
- width: number;
50
- height: number;
51
- }
52
-
53
60
  interface RenderImageState {
54
61
  src: string;
55
62
  left: number;
@@ -125,8 +132,6 @@ interface ExportUserCroppedImageResult {
125
132
  imageIds: string[];
126
133
  }
127
134
 
128
- const IMAGE_OBJECT_LAYER_ID = "image.user";
129
- const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
130
135
  type ImageControlCapability = "rotate" | "scale" | "flipX" | "flipY";
131
136
 
132
137
  interface ImageControlDescriptor {
@@ -178,7 +183,9 @@ export class ImageTool implements Extension {
178
183
  private workingItems: ImageItem[] = [];
179
184
  private hasWorkingChanges = false;
180
185
  private loadResolvers: Map<string, () => void> = new Map();
181
- private sourceSizeBySrc: Map<string, SourceSize> = new Map();
186
+ private sourceSizeCache = createSourceSizeCache((src) =>
187
+ this.loadImageSize(src),
188
+ );
182
189
  private canvasService?: CanvasService;
183
190
  private context?: ExtensionContext;
184
191
  private isUpdatingConfig = false;
@@ -193,10 +200,12 @@ export class ImageTool implements Extension {
193
200
  private imageSpecs: RenderObjectSpec[] = [];
194
201
  private overlaySpecs: RenderObjectSpec[] = [];
195
202
  private renderProducerDisposable?: { dispose: () => void };
203
+ private readonly subscriptions = new SubscriptionBag();
196
204
  private imageControlsByCapabilityKey: Map<string, Record<string, Control>> =
197
205
  new Map();
198
206
 
199
207
  activate(context: ExtensionContext) {
208
+ this.subscriptions.disposeAll();
200
209
  this.context = context;
201
210
  this.canvasService = context.services.get<CanvasService>("CanvasService");
202
211
  if (!this.canvasService) {
@@ -239,48 +248,63 @@ export class ImageTool implements Extension {
239
248
  { priority: 300 },
240
249
  );
241
250
 
242
- context.eventBus.on("tool:activated", this.onToolActivated);
243
- context.eventBus.on("object:modified", this.onObjectModified);
244
- context.eventBus.on("selection:created", this.onSelectionChanged);
245
- context.eventBus.on("selection:updated", this.onSelectionChanged);
246
- context.eventBus.on("selection:cleared", this.onSelectionCleared);
247
- context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
248
- context.eventBus.on("scene:geometry:change", this.onSceneGeometryChanged);
251
+ this.subscriptions.on(context.eventBus, "tool:activated", this.onToolActivated);
252
+ this.subscriptions.on(context.eventBus, "object:modified", this.onObjectModified);
253
+ this.subscriptions.on(
254
+ context.eventBus,
255
+ "selection:created",
256
+ this.onSelectionChanged,
257
+ );
258
+ this.subscriptions.on(
259
+ context.eventBus,
260
+ "selection:updated",
261
+ this.onSelectionChanged,
262
+ );
263
+ this.subscriptions.on(
264
+ context.eventBus,
265
+ "selection:cleared",
266
+ this.onSelectionCleared,
267
+ );
268
+ this.subscriptions.on(
269
+ context.eventBus,
270
+ "scene:layout:change",
271
+ this.onSceneLayoutChanged,
272
+ );
273
+ this.subscriptions.on(
274
+ context.eventBus,
275
+ "scene:geometry:change",
276
+ this.onSceneGeometryChanged,
277
+ );
249
278
 
250
279
  const configService = context.services.get<ConfigurationService>(
251
280
  "ConfigurationService",
252
281
  );
253
282
  if (configService) {
254
- this.items = this.normalizeItems(
255
- configService.get("image.items", []) || [],
256
- );
257
- this.workingItems = this.cloneItems(this.items);
258
- this.hasWorkingChanges = false;
283
+ this.applyCommittedItems(configService.get("image.items", []) || []);
259
284
 
260
- configService.onAnyChange((e: { key: string; value: any }) => {
261
- if (this.isUpdatingConfig) return;
285
+ this.subscriptions.onConfigChange(
286
+ configService,
287
+ (e: { key: string; value: any }) => {
288
+ if (this.isUpdatingConfig) return;
262
289
 
263
- if (e.key === "image.items") {
264
- this.items = this.normalizeItems(e.value || []);
265
- if (!this.isToolActive || !this.hasWorkingChanges) {
266
- this.workingItems = this.cloneItems(this.items);
267
- this.hasWorkingChanges = false;
290
+ if (e.key === "image.items") {
291
+ this.applyCommittedItems(e.value || []);
292
+ this.updateImages();
293
+ return;
268
294
  }
269
- this.updateImages();
270
- return;
271
- }
272
295
 
273
- if (
274
- e.key.startsWith("size.") ||
275
- e.key.startsWith("image.frame.") ||
276
- e.key.startsWith("image.control.")
277
- ) {
278
- if (e.key.startsWith("image.control.")) {
279
- this.imageControlsByCapabilityKey.clear();
296
+ if (
297
+ e.key.startsWith("size.") ||
298
+ e.key.startsWith("image.frame.") ||
299
+ e.key.startsWith("image.control.")
300
+ ) {
301
+ if (e.key.startsWith("image.control.")) {
302
+ this.imageControlsByCapabilityKey.clear();
303
+ }
304
+ this.updateImages();
280
305
  }
281
- this.updateImages();
282
- }
283
- });
306
+ },
307
+ );
284
308
  }
285
309
 
286
310
  const toolSessionService =
@@ -294,18 +318,13 @@ export class ImageTool implements Extension {
294
318
  }
295
319
 
296
320
  deactivate(context: ExtensionContext) {
297
- context.eventBus.off("tool:activated", this.onToolActivated);
298
- context.eventBus.off("object:modified", this.onObjectModified);
299
- context.eventBus.off("selection:created", this.onSelectionChanged);
300
- context.eventBus.off("selection:updated", this.onSelectionChanged);
301
- context.eventBus.off("selection:cleared", this.onSelectionCleared);
302
- context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
303
- context.eventBus.off("scene:geometry:change", this.onSceneGeometryChanged);
321
+ this.subscriptions.disposeAll();
304
322
  this.dirtyTrackerDisposable?.dispose();
305
323
  this.dirtyTrackerDisposable = undefined;
306
324
  this.cropShapeHatchPattern = undefined;
307
325
  this.cropShapeHatchPatternColor = undefined;
308
326
  this.cropShapeHatchPatternKey = undefined;
327
+ this.sourceSizeCache.clear();
309
328
  this.imageSpecs = [];
310
329
  this.overlaySpecs = [];
311
330
  this.imageControlsByCapabilityKey.clear();
@@ -547,287 +566,8 @@ export class ImageTool implements Extension {
547
566
  },
548
567
  },
549
568
  ],
550
- [ContributionPointIds.CONFIGURATIONS]: [
551
- {
552
- id: "image.items",
553
- type: "array",
554
- label: "Images",
555
- default: [],
556
- },
557
- {
558
- id: "image.debug",
559
- type: "boolean",
560
- label: "Image Debug Log",
561
- default: false,
562
- },
563
- {
564
- id: "image.control.cornerSize",
565
- type: "number",
566
- label: "Image Control Corner Size",
567
- min: 4,
568
- max: 64,
569
- step: 1,
570
- default: 14,
571
- },
572
- {
573
- id: "image.control.touchCornerSize",
574
- type: "number",
575
- label: "Image Control Touch Corner Size",
576
- min: 8,
577
- max: 96,
578
- step: 1,
579
- default: 24,
580
- },
581
- {
582
- id: "image.control.cornerStyle",
583
- type: "select",
584
- label: "Image Control Corner Style",
585
- options: ["circle", "rect"],
586
- default: "circle",
587
- },
588
- {
589
- id: "image.control.cornerColor",
590
- type: "color",
591
- label: "Image Control Corner Color",
592
- default: "#ffffff",
593
- },
594
- {
595
- id: "image.control.cornerStrokeColor",
596
- type: "color",
597
- label: "Image Control Corner Stroke Color",
598
- default: "#1677ff",
599
- },
600
- {
601
- id: "image.control.transparentCorners",
602
- type: "boolean",
603
- label: "Image Control Transparent Corners",
604
- default: false,
605
- },
606
- {
607
- id: "image.control.borderColor",
608
- type: "color",
609
- label: "Image Control Border Color",
610
- default: "#1677ff",
611
- },
612
- {
613
- id: "image.control.borderScaleFactor",
614
- type: "number",
615
- label: "Image Control Border Width",
616
- min: 0.5,
617
- max: 8,
618
- step: 0.1,
619
- default: 1.5,
620
- },
621
- {
622
- id: "image.control.padding",
623
- type: "number",
624
- label: "Image Control Padding",
625
- min: 0,
626
- max: 64,
627
- step: 1,
628
- default: 0,
629
- },
630
- {
631
- id: "image.frame.strokeColor",
632
- type: "color",
633
- label: "Image Frame Stroke Color",
634
- default: "#808080",
635
- },
636
- {
637
- id: "image.frame.strokeWidth",
638
- type: "number",
639
- label: "Image Frame Stroke Width",
640
- min: 0,
641
- max: 20,
642
- step: 0.5,
643
- default: 2,
644
- },
645
- {
646
- id: "image.frame.strokeStyle",
647
- type: "select",
648
- label: "Image Frame Stroke Style",
649
- options: ["solid", "dashed", "hidden"],
650
- default: "dashed",
651
- },
652
- {
653
- id: "image.frame.dashLength",
654
- type: "number",
655
- label: "Image Frame Dash Length",
656
- min: 1,
657
- max: 40,
658
- step: 1,
659
- default: 8,
660
- },
661
- {
662
- id: "image.frame.innerBackground",
663
- type: "color",
664
- label: "Image Frame Inner Background",
665
- default: "rgba(0,0,0,0)",
666
- },
667
- {
668
- id: "image.frame.outerBackground",
669
- type: "color",
670
- label: "Image Frame Outer Background",
671
- default: "#f5f5f5",
672
- },
673
- ] as ConfigurationContribution[],
674
- [ContributionPointIds.COMMANDS]: [
675
- {
676
- command: "addImage",
677
- title: "Add Image",
678
- handler: async (url: string, options?: Partial<ImageItem>) => {
679
- const result = await this.upsertImageEntry(url, {
680
- mode: "add",
681
- addOptions: options,
682
- });
683
- return result.id;
684
- },
685
- },
686
- {
687
- command: "upsertImage",
688
- title: "Upsert Image",
689
- handler: async (url: string, options: UpsertImageOptions = {}) => {
690
- return await this.upsertImageEntry(url, options);
691
- },
692
- },
693
- {
694
- command: "getWorkingImages",
695
- title: "Get Working Images",
696
- handler: () => {
697
- return this.cloneItems(this.workingItems);
698
- },
699
- },
700
- {
701
- command: "setWorkingImage",
702
- title: "Set Working Image",
703
- handler: (id: string, updates: Partial<ImageItem>) => {
704
- this.updateImageInWorking(id, updates);
705
- },
706
- },
707
- {
708
- command: "resetWorkingImages",
709
- title: "Reset Working Images",
710
- handler: () => {
711
- this.workingItems = this.cloneItems(this.items);
712
- this.hasWorkingChanges = false;
713
- this.updateImages();
714
- this.emitWorkingChange();
715
- },
716
- },
717
- {
718
- command: "completeImages",
719
- title: "Complete Images",
720
- handler: async () => {
721
- return await this.commitWorkingImagesAsCropped();
722
- },
723
- },
724
- {
725
- command: "exportUserCroppedImage",
726
- title: "Export User Cropped Image",
727
- handler: async (options: ExportUserCroppedImageOptions = {}) => {
728
- return await this.exportUserCroppedImage(options);
729
- },
730
- },
731
- {
732
- command: "fitImageToArea",
733
- title: "Fit Image to Area",
734
- handler: async (
735
- id: string,
736
- area: {
737
- width: number;
738
- height: number;
739
- left?: number;
740
- top?: number;
741
- },
742
- ) => {
743
- await this.fitImageToArea(id, area);
744
- },
745
- },
746
- {
747
- command: "fitImageToDefaultArea",
748
- title: "Fit Image to Default Area",
749
- handler: async (id: string) => {
750
- await this.fitImageToDefaultArea(id);
751
- },
752
- },
753
- {
754
- command: "focusImage",
755
- title: "Focus Image",
756
- handler: (
757
- id: string | null,
758
- options: { syncCanvasSelection?: boolean } = {},
759
- ) => {
760
- return this.setImageFocus(id, options);
761
- },
762
- },
763
- {
764
- command: "removeImage",
765
- title: "Remove Image",
766
- handler: (id: string) => {
767
- const removed = this.items.find((item) => item.id === id);
768
- const next = this.items.filter((item) => item.id !== id);
769
- if (next.length !== this.items.length) {
770
- this.purgeSourceSizeCacheForItem(removed);
771
- if (this.focusedImageId === id) {
772
- this.setImageFocus(null, {
773
- syncCanvasSelection: true,
774
- skipRender: true,
775
- });
776
- }
777
- this.updateConfig(next);
778
- }
779
- },
780
- },
781
- {
782
- command: "updateImage",
783
- title: "Update Image",
784
- handler: async (
785
- id: string,
786
- updates: Partial<ImageItem>,
787
- options: UpdateImageOptions = {},
788
- ) => {
789
- await this.updateImage(id, updates, options);
790
- },
791
- },
792
- {
793
- command: "clearImages",
794
- title: "Clear Images",
795
- handler: () => {
796
- this.sourceSizeBySrc.clear();
797
- this.setImageFocus(null, {
798
- syncCanvasSelection: true,
799
- skipRender: true,
800
- });
801
- this.updateConfig([]);
802
- },
803
- },
804
- {
805
- command: "bringToFront",
806
- title: "Bring Image to Front",
807
- handler: (id: string) => {
808
- const index = this.items.findIndex((item) => item.id === id);
809
- if (index !== -1 && index < this.items.length - 1) {
810
- const next = [...this.items];
811
- const [item] = next.splice(index, 1);
812
- next.push(item);
813
- this.updateConfig(next);
814
- }
815
- },
816
- },
817
- {
818
- command: "sendToBack",
819
- title: "Send Image to Back",
820
- handler: (id: string) => {
821
- const index = this.items.findIndex((item) => item.id === id);
822
- if (index > 0) {
823
- const next = [...this.items];
824
- const [item] = next.splice(index, 1);
825
- next.unshift(item);
826
- this.updateConfig(next);
827
- }
828
- },
829
- },
830
- ] as CommandContribution[],
569
+ [ContributionPointIds.CONFIGURATIONS]: createImageConfigurations(),
570
+ [ContributionPointIds.COMMANDS]: createImageCommands(this),
831
571
  };
832
572
  }
833
573
 
@@ -1000,53 +740,46 @@ export class ImageTool implements Extension {
1000
740
  return (configService.get(key, fallback) as T) ?? fallback;
1001
741
  }
1002
742
 
743
+ private applyCommittedItems(nextItems: ImageItem[]) {
744
+ const session = {
745
+ committed: this.items,
746
+ working: this.workingItems,
747
+ hasWorkingChanges: this.hasWorkingChanges,
748
+ };
749
+ applyCommittedSnapshot(session, this.normalizeItems(nextItems), {
750
+ clone: (items) => this.cloneItems(items),
751
+ toolActive: this.isToolActive,
752
+ preserveDirtyWorking: true,
753
+ });
754
+ this.items = session.committed;
755
+ this.workingItems = session.working;
756
+ this.hasWorkingChanges = session.hasWorkingChanges;
757
+ }
758
+
1003
759
  private updateConfig(newItems: ImageItem[], skipCanvasUpdate = false) {
1004
760
  if (!this.context) return;
761
+ this.applyCommittedItems(newItems);
762
+ runDeferredConfigUpdate(
763
+ this,
764
+ () => {
765
+ const configService = this.context?.services.get<ConfigurationService>(
766
+ "ConfigurationService",
767
+ );
768
+ configService?.update("image.items", this.items);
1005
769
 
1006
- this.isUpdatingConfig = true;
1007
- this.items = this.normalizeItems(newItems);
1008
- if (!this.isToolActive || !this.hasWorkingChanges) {
1009
- this.workingItems = this.cloneItems(this.items);
1010
- this.hasWorkingChanges = false;
1011
- }
1012
-
1013
- const configService = this.context.services.get<ConfigurationService>(
1014
- "ConfigurationService",
770
+ if (!skipCanvasUpdate) {
771
+ this.updateImages();
772
+ }
773
+ },
774
+ 50,
1015
775
  );
1016
- configService?.update("image.items", this.items);
1017
-
1018
- if (!skipCanvasUpdate) {
1019
- this.updateImages();
1020
- }
1021
-
1022
- setTimeout(() => {
1023
- this.isUpdatingConfig = false;
1024
- }, 50);
1025
776
  }
1026
777
 
1027
778
  private getFrameRect(): FrameRect {
1028
- if (!this.canvasService) {
1029
- return { left: 0, top: 0, width: 0, height: 0 };
1030
- }
1031
779
  const configService = this.context?.services.get<ConfigurationService>(
1032
780
  "ConfigurationService",
1033
781
  );
1034
- if (!configService) {
1035
- return { left: 0, top: 0, width: 0, height: 0 };
1036
- }
1037
-
1038
- const sizeState = readSizeState(configService);
1039
- const layout = computeSceneLayout(this.canvasService, sizeState);
1040
- if (!layout) {
1041
- return { left: 0, top: 0, width: 0, height: 0 };
1042
- }
1043
-
1044
- return this.canvasService.toSceneRect({
1045
- left: layout.cutRect.left,
1046
- top: layout.cutRect.top,
1047
- width: layout.cutRect.width,
1048
- height: layout.cutRect.height,
1049
- });
782
+ return resolveCutFrameRect(this.canvasService, configService);
1050
783
  }
1051
784
 
1052
785
  private getFrameRectScreen(frame?: FrameRect): FrameRect {
@@ -1057,13 +790,7 @@ export class ImageTool implements Extension {
1057
790
  }
1058
791
 
1059
792
  private toLayoutSceneRect(rect: FrameRect): RenderLayoutRect {
1060
- return {
1061
- left: rect.left,
1062
- top: rect.top,
1063
- width: rect.width,
1064
- height: rect.height,
1065
- space: "scene",
1066
- };
793
+ return toSceneLayoutRect(rect);
1067
794
  }
1068
795
 
1069
796
  private async resolveDefaultFitArea(): Promise<DielineFitArea | null> {
@@ -1126,26 +853,26 @@ export class ImageTool implements Extension {
1126
853
  const sources = [item.url, item.sourceUrl, item.committedUrl].filter(
1127
854
  (value): value is string => typeof value === "string" && value.length > 0,
1128
855
  );
1129
- sources.forEach((src) => this.sourceSizeBySrc.delete(src));
856
+ sources.forEach((src) => this.sourceSizeCache.deleteSourceSize(src));
1130
857
  }
1131
858
 
1132
859
  private rememberSourceSize(src: string, obj: any) {
1133
860
  const width = Number(obj?.width || 0);
1134
861
  const height = Number(obj?.height || 0);
1135
862
  if (src && width > 0 && height > 0) {
1136
- this.sourceSizeBySrc.set(src, { width, height });
863
+ this.sourceSizeCache.rememberSourceSize(src, { width, height });
1137
864
  }
1138
865
  }
1139
866
 
1140
867
  private getSourceSize(src: string, obj?: any): SourceSize {
1141
- const cached = src ? this.sourceSizeBySrc.get(src) : undefined;
868
+ const cached = src ? this.sourceSizeCache.getSourceSize(src) : undefined;
1142
869
  if (cached) return cached;
1143
870
 
1144
871
  const width = Number(obj?.width || 0);
1145
872
  const height = Number(obj?.height || 0);
1146
873
  if (src && width > 0 && height > 0) {
1147
874
  const size = { width, height };
1148
- this.sourceSizeBySrc.set(src, size);
875
+ this.sourceSizeCache.rememberSourceSize(src, size);
1149
876
  return size;
1150
877
  }
1151
878
 
@@ -1153,10 +880,10 @@ export class ImageTool implements Extension {
1153
880
  }
1154
881
 
1155
882
  private async ensureSourceSize(src: string): Promise<SourceSize | null> {
1156
- if (!src) return null;
1157
- const cached = this.sourceSizeBySrc.get(src);
1158
- if (cached) return cached;
883
+ return this.sourceSizeCache.ensureImageSize(src);
884
+ }
1159
885
 
886
+ private async loadImageSize(src: string): Promise<SourceSize | null> {
1160
887
  try {
1161
888
  const image = await FabricImage.fromURL(src, {
1162
889
  crossOrigin: "anonymous",
@@ -1164,9 +891,7 @@ export class ImageTool implements Extension {
1164
891
  const width = Number(image?.width || 0);
1165
892
  const height = Number(image?.height || 0);
1166
893
  if (width > 0 && height > 0) {
1167
- const size = { width, height };
1168
- this.sourceSizeBySrc.set(src, size);
1169
- return size;
894
+ return { width, height };
1170
895
  }
1171
896
  } catch (error) {
1172
897
  this.debug("image:size:load-failed", {
@@ -1179,11 +904,7 @@ export class ImageTool implements Extension {
1179
904
  }
1180
905
 
1181
906
  private getCoverScale(frame: FrameRect, size: SourceSize): number {
1182
- const sw = Math.max(1, size.width);
1183
- const sh = Math.max(1, size.height);
1184
- const fw = Math.max(1, frame.width);
1185
- const fh = Math.max(1, frame.height);
1186
- return Math.max(fw / sw, fh / sh);
907
+ return getCoverScaleFromRect(frame, size);
1187
908
  }
1188
909
 
1189
910
  private getFrameVisualConfig(): FrameVisualConfig {
@@ -2114,7 +1835,7 @@ export class ImageTool implements Extension {
2114
1835
  }),
2115
1836
  );
2116
1837
  if (previousCommitted && previousCommitted !== url) {
2117
- this.sourceSizeBySrc.delete(previousCommitted);
1838
+ this.sourceSizeCache.deleteSourceSize(previousCommitted);
2118
1839
  }
2119
1840
  }
2120
1841