@pooder/kit 3.3.0 → 3.5.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +53 -57
- package/dist/index.d.ts +53 -57
- package/dist/index.js +1081 -930
- package/dist/index.mjs +1080 -929
- package/package.json +1 -1
- package/src/CanvasService.ts +65 -65
- package/src/background.ts +230 -230
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +282 -218
- package/src/feature.ts +724 -0
- package/src/film.ts +194 -194
- package/src/geometry.ts +118 -370
- package/src/image.ts +471 -496
- package/src/index.ts +1 -1
- package/src/mirror.ts +128 -128
- package/src/ruler.ts +500 -500
- package/src/tracer.ts +570 -372
- package/src/white-ink.ts +373 -373
- package/src/hole.ts +0 -786
package/src/image.ts
CHANGED
|
@@ -1,496 +1,471 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Extension,
|
|
3
|
-
ExtensionContext,
|
|
4
|
-
ContributionPointIds,
|
|
5
|
-
CommandContribution,
|
|
6
|
-
ConfigurationContribution,
|
|
7
|
-
ConfigurationService,
|
|
8
|
-
} from "@pooder/core";
|
|
9
|
-
import { Image, Point, util, Object as FabricObject } from "fabric";
|
|
10
|
-
import CanvasService from "./CanvasService";
|
|
11
|
-
import { Coordinate } from "./coordinate";
|
|
12
|
-
|
|
13
|
-
export interface ImageItem {
|
|
14
|
-
id: string;
|
|
15
|
-
url: string;
|
|
16
|
-
opacity: number;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private canvasService?: CanvasService;
|
|
34
|
-
private context?: ExtensionContext;
|
|
35
|
-
private isUpdatingConfig = false;
|
|
36
|
-
|
|
37
|
-
activate(context: ExtensionContext) {
|
|
38
|
-
this.context = context;
|
|
39
|
-
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
40
|
-
if (!this.canvasService) {
|
|
41
|
-
console.warn("CanvasService not found for ImageTool");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const configService = context.services.get<ConfigurationService>(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
newItems.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
layer.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
473
|
-
updates.angle = image.angle;
|
|
474
|
-
|
|
475
|
-
// Physical Dimensions
|
|
476
|
-
if (image.width) {
|
|
477
|
-
const pixelWidth = image.width * image.scaleX;
|
|
478
|
-
updates.width = pixelWidth / layoutScale;
|
|
479
|
-
}
|
|
480
|
-
if (image.height) {
|
|
481
|
-
const pixelHeight = image.height * image.scaleY;
|
|
482
|
-
updates.height = pixelHeight / layoutScale;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
this.updateImageInConfig(id, updates);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private updateImageInConfig(id: string, updates: Partial<ImageItem>) {
|
|
489
|
-
const index = this.items.findIndex(i => i.id === id);
|
|
490
|
-
if (index !== -1) {
|
|
491
|
-
const newItems = [...this.items];
|
|
492
|
-
newItems[index] = { ...newItems[index], ...updates };
|
|
493
|
-
this.updateConfig(newItems, true);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
Extension,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
ContributionPointIds,
|
|
5
|
+
CommandContribution,
|
|
6
|
+
ConfigurationContribution,
|
|
7
|
+
ConfigurationService,
|
|
8
|
+
} from "@pooder/core";
|
|
9
|
+
import { Image, Point, util, Object as FabricObject } from "fabric";
|
|
10
|
+
import CanvasService from "./CanvasService";
|
|
11
|
+
import { Coordinate } from "./coordinate";
|
|
12
|
+
|
|
13
|
+
export interface ImageItem {
|
|
14
|
+
id: string;
|
|
15
|
+
url: string;
|
|
16
|
+
opacity: number;
|
|
17
|
+
scale?: number;
|
|
18
|
+
angle?: number;
|
|
19
|
+
left?: number;
|
|
20
|
+
top?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ImageTool implements Extension {
|
|
24
|
+
id = "pooder.kit.image";
|
|
25
|
+
|
|
26
|
+
metadata = {
|
|
27
|
+
name: "ImageTool",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
private items: ImageItem[] = [];
|
|
31
|
+
private objectMap: Map<string, FabricObject> = new Map();
|
|
32
|
+
private loadResolvers: Map<string, () => void> = new Map();
|
|
33
|
+
private canvasService?: CanvasService;
|
|
34
|
+
private context?: ExtensionContext;
|
|
35
|
+
private isUpdatingConfig = false;
|
|
36
|
+
|
|
37
|
+
activate(context: ExtensionContext) {
|
|
38
|
+
this.context = context;
|
|
39
|
+
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
40
|
+
if (!this.canvasService) {
|
|
41
|
+
console.warn("CanvasService not found for ImageTool");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const configService = context.services.get<ConfigurationService>(
|
|
46
|
+
"ConfigurationService",
|
|
47
|
+
);
|
|
48
|
+
if (configService) {
|
|
49
|
+
// Load initial config
|
|
50
|
+
this.items = configService.get("image.items", []) || [];
|
|
51
|
+
|
|
52
|
+
// Listen for changes
|
|
53
|
+
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
54
|
+
if (this.isUpdatingConfig) return;
|
|
55
|
+
|
|
56
|
+
if (e.key === "image.items") {
|
|
57
|
+
this.items = e.value || [];
|
|
58
|
+
this.updateImages();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.ensureLayer();
|
|
64
|
+
this.updateImages();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
deactivate(context: ExtensionContext) {
|
|
68
|
+
if (this.canvasService) {
|
|
69
|
+
const layer = this.canvasService.getLayer("user");
|
|
70
|
+
if (layer) {
|
|
71
|
+
this.objectMap.forEach((obj) => {
|
|
72
|
+
layer.remove(obj);
|
|
73
|
+
});
|
|
74
|
+
this.objectMap.clear();
|
|
75
|
+
this.canvasService.requestRenderAll();
|
|
76
|
+
}
|
|
77
|
+
this.canvasService = undefined;
|
|
78
|
+
this.context = undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
contribute() {
|
|
83
|
+
return {
|
|
84
|
+
[ContributionPointIds.CONFIGURATIONS]: [
|
|
85
|
+
{
|
|
86
|
+
id: "image.items",
|
|
87
|
+
type: "array",
|
|
88
|
+
label: "Images",
|
|
89
|
+
default: [],
|
|
90
|
+
},
|
|
91
|
+
] as ConfigurationContribution[],
|
|
92
|
+
[ContributionPointIds.COMMANDS]: [
|
|
93
|
+
{
|
|
94
|
+
command: "addImage",
|
|
95
|
+
title: "Add Image",
|
|
96
|
+
handler: async (url: string, options?: Partial<ImageItem>) => {
|
|
97
|
+
const id = this.generateId();
|
|
98
|
+
const newItem: ImageItem = {
|
|
99
|
+
id,
|
|
100
|
+
url,
|
|
101
|
+
opacity: 1,
|
|
102
|
+
...options,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const promise = new Promise<string>((resolve) => {
|
|
106
|
+
this.loadResolvers.set(id, () => resolve(id));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.updateConfig([...this.items, newItem]);
|
|
110
|
+
return promise;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
command: "fitImageToArea",
|
|
115
|
+
title: "Fit Image to Area",
|
|
116
|
+
handler: (
|
|
117
|
+
id: string,
|
|
118
|
+
area: { width: number; height: number; left?: number; top?: number },
|
|
119
|
+
) => {
|
|
120
|
+
const item = this.items.find((i) => i.id === id);
|
|
121
|
+
const obj = this.objectMap.get(id);
|
|
122
|
+
if (item && obj && obj.width && obj.height) {
|
|
123
|
+
const scale = Math.max(
|
|
124
|
+
area.width / obj.width,
|
|
125
|
+
area.height / obj.height,
|
|
126
|
+
);
|
|
127
|
+
this.updateImageInConfig(id, {
|
|
128
|
+
scale,
|
|
129
|
+
left: area.left ?? 0.5,
|
|
130
|
+
top: area.top ?? 0.5,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
command: "removeImage",
|
|
137
|
+
title: "Remove Image",
|
|
138
|
+
handler: (id: string) => {
|
|
139
|
+
const newItems = this.items.filter((item) => item.id !== id);
|
|
140
|
+
if (newItems.length !== this.items.length) {
|
|
141
|
+
this.updateConfig(newItems);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
command: "updateImage",
|
|
147
|
+
title: "Update Image",
|
|
148
|
+
handler: (id: string, updates: Partial<ImageItem>) => {
|
|
149
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
150
|
+
if (index !== -1) {
|
|
151
|
+
const newItems = [...this.items];
|
|
152
|
+
newItems[index] = { ...newItems[index], ...updates };
|
|
153
|
+
this.updateConfig(newItems);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
command: "clearImages",
|
|
159
|
+
title: "Clear Images",
|
|
160
|
+
handler: () => {
|
|
161
|
+
this.updateConfig([]);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
command: "bringToFront",
|
|
166
|
+
title: "Bring Image to Front",
|
|
167
|
+
handler: (id: string) => {
|
|
168
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
169
|
+
if (index !== -1 && index < this.items.length - 1) {
|
|
170
|
+
const newItems = [...this.items];
|
|
171
|
+
const [item] = newItems.splice(index, 1);
|
|
172
|
+
newItems.push(item);
|
|
173
|
+
this.updateConfig(newItems);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
command: "sendToBack",
|
|
179
|
+
title: "Send Image to Back",
|
|
180
|
+
handler: (id: string) => {
|
|
181
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
182
|
+
if (index > 0) {
|
|
183
|
+
const newItems = [...this.items];
|
|
184
|
+
const [item] = newItems.splice(index, 1);
|
|
185
|
+
newItems.unshift(item);
|
|
186
|
+
this.updateConfig(newItems);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
] as CommandContribution[],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private generateId(): string {
|
|
195
|
+
return Math.random().toString(36).substring(2, 9);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private updateConfig(newItems: ImageItem[], skipCanvasUpdate = false) {
|
|
199
|
+
if (!this.context) return;
|
|
200
|
+
this.isUpdatingConfig = true;
|
|
201
|
+
this.items = newItems;
|
|
202
|
+
const configService = this.context.services.get<ConfigurationService>(
|
|
203
|
+
"ConfigurationService",
|
|
204
|
+
);
|
|
205
|
+
if (configService) {
|
|
206
|
+
configService.update("image.items", newItems);
|
|
207
|
+
}
|
|
208
|
+
// Update canvas immediately to reflect changes locally before config event comes back
|
|
209
|
+
// (Optional, but good for responsiveness)
|
|
210
|
+
if (!skipCanvasUpdate) {
|
|
211
|
+
this.updateImages();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Reset flag after a short delay to allow config propagation
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
this.isUpdatingConfig = false;
|
|
217
|
+
}, 50);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private ensureLayer() {
|
|
221
|
+
if (!this.canvasService) return;
|
|
222
|
+
let userLayer = this.canvasService.getLayer("user");
|
|
223
|
+
if (!userLayer) {
|
|
224
|
+
userLayer = this.canvasService.createLayer("user", {
|
|
225
|
+
width: this.canvasService.canvas.width,
|
|
226
|
+
height: this.canvasService.canvas.height,
|
|
227
|
+
left: 0,
|
|
228
|
+
top: 0,
|
|
229
|
+
originX: "left",
|
|
230
|
+
originY: "top",
|
|
231
|
+
selectable: false,
|
|
232
|
+
evented: true,
|
|
233
|
+
subTargetCheck: true,
|
|
234
|
+
interactive: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Try to insert below dieline-overlay
|
|
238
|
+
const dielineLayer = this.canvasService.getLayer("dieline-overlay");
|
|
239
|
+
if (dielineLayer) {
|
|
240
|
+
const index = this.canvasService.canvas
|
|
241
|
+
.getObjects()
|
|
242
|
+
.indexOf(dielineLayer);
|
|
243
|
+
// If dieline is at 0, move user to 0 (dieline shifts to 1)
|
|
244
|
+
if (index >= 0) {
|
|
245
|
+
this.canvasService.canvas.moveObjectTo(userLayer, index);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Ensure background is behind
|
|
249
|
+
const bgLayer = this.canvasService.getLayer("background");
|
|
250
|
+
if (bgLayer) {
|
|
251
|
+
this.canvasService.canvas.sendObjectToBack(bgLayer);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.canvasService.requestRenderAll();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private getLayoutInfo() {
|
|
259
|
+
const canvasW = this.canvasService?.canvas.width || 800;
|
|
260
|
+
const canvasH = this.canvasService?.canvas.height || 600;
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
layoutScale: 1,
|
|
264
|
+
layoutOffsetX: 0,
|
|
265
|
+
layoutOffsetY: 0,
|
|
266
|
+
visualWidth: canvasW,
|
|
267
|
+
visualHeight: canvasH,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private updateImages() {
|
|
272
|
+
if (!this.canvasService) return;
|
|
273
|
+
const layer = this.canvasService.getLayer("user");
|
|
274
|
+
if (!layer) {
|
|
275
|
+
console.warn("[ImageTool] User layer not found");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 1. Remove objects that are no longer in items
|
|
280
|
+
const currentIds = new Set(this.items.map((i) => i.id));
|
|
281
|
+
for (const [id, obj] of this.objectMap) {
|
|
282
|
+
if (!currentIds.has(id)) {
|
|
283
|
+
layer.remove(obj);
|
|
284
|
+
this.objectMap.delete(id);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 2. Add or Update objects
|
|
289
|
+
const layout = this.getLayoutInfo();
|
|
290
|
+
|
|
291
|
+
this.items.forEach((item, index) => {
|
|
292
|
+
let obj = this.objectMap.get(item.id);
|
|
293
|
+
|
|
294
|
+
if (!obj) {
|
|
295
|
+
// New object, load it
|
|
296
|
+
this.loadImage(item, layer, layout);
|
|
297
|
+
} else {
|
|
298
|
+
// Existing object, update properties
|
|
299
|
+
// We remove and re-add to ensure coordinates are correctly converted
|
|
300
|
+
// from absolute (updateObjectProperties) to relative (layer.add)
|
|
301
|
+
layer.remove(obj);
|
|
302
|
+
this.updateObjectProperties(obj, item, layout);
|
|
303
|
+
layer.add(obj);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
layer.dirty = true;
|
|
308
|
+
this.canvasService.requestRenderAll();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private updateObjectProperties(
|
|
312
|
+
obj: FabricObject,
|
|
313
|
+
item: ImageItem,
|
|
314
|
+
layout: any,
|
|
315
|
+
) {
|
|
316
|
+
const {
|
|
317
|
+
layoutScale,
|
|
318
|
+
layoutOffsetX,
|
|
319
|
+
layoutOffsetY,
|
|
320
|
+
visualWidth,
|
|
321
|
+
visualHeight,
|
|
322
|
+
} = layout;
|
|
323
|
+
const updates: any = {};
|
|
324
|
+
|
|
325
|
+
// Opacity
|
|
326
|
+
if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
|
|
327
|
+
|
|
328
|
+
// Angle
|
|
329
|
+
if (item.angle !== undefined && obj.angle !== item.angle)
|
|
330
|
+
updates.angle = item.angle;
|
|
331
|
+
|
|
332
|
+
// Position (Normalized -> Absolute)
|
|
333
|
+
if (item.left !== undefined) {
|
|
334
|
+
const globalLeft = layoutOffsetX + item.left * visualWidth;
|
|
335
|
+
if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
|
|
336
|
+
}
|
|
337
|
+
if (item.top !== undefined) {
|
|
338
|
+
const globalTop = layoutOffsetY + item.top * visualHeight;
|
|
339
|
+
if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Scale
|
|
343
|
+
if (item.scale !== undefined) {
|
|
344
|
+
const targetScale = item.scale * layoutScale;
|
|
345
|
+
if (Math.abs(obj.scaleX - targetScale) > 0.001) {
|
|
346
|
+
updates.scaleX = targetScale;
|
|
347
|
+
updates.scaleY = targetScale;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Center origin if not set
|
|
352
|
+
if (obj.originX !== "center") {
|
|
353
|
+
updates.originX = "center";
|
|
354
|
+
updates.originY = "center";
|
|
355
|
+
// Adjust position because origin changed (Fabric logic)
|
|
356
|
+
// For simplicity, we just set it, next cycle will fix pos if needed,
|
|
357
|
+
// or we can calculate the shift. Ideally we set origin on creation.
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (Object.keys(updates).length > 0) {
|
|
361
|
+
obj.set(updates);
|
|
362
|
+
obj.setCoords();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private loadImage(item: ImageItem, layer: any, layout: any) {
|
|
367
|
+
Image.fromURL(item.url, { crossOrigin: "anonymous" })
|
|
368
|
+
.then((image) => {
|
|
369
|
+
// Double check if item still exists
|
|
370
|
+
if (!this.items.find((i) => i.id === item.id)) return;
|
|
371
|
+
|
|
372
|
+
image.set({
|
|
373
|
+
originX: "center",
|
|
374
|
+
originY: "center",
|
|
375
|
+
data: { id: item.id },
|
|
376
|
+
uniformScaling: true,
|
|
377
|
+
lockScalingFlip: true,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
image.setControlsVisibility({
|
|
381
|
+
mt: false,
|
|
382
|
+
mb: false,
|
|
383
|
+
ml: false,
|
|
384
|
+
mr: false,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Initial Layout
|
|
388
|
+
let { scale, left, top } = item;
|
|
389
|
+
|
|
390
|
+
if (scale === undefined) {
|
|
391
|
+
scale = 1; // Default scale if not provided and not fitted yet
|
|
392
|
+
item.scale = scale;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (left === undefined && top === undefined) {
|
|
396
|
+
left = 0.5;
|
|
397
|
+
top = 0.5;
|
|
398
|
+
item.left = left;
|
|
399
|
+
item.top = top;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Apply Props
|
|
403
|
+
this.updateObjectProperties(image, item, layout);
|
|
404
|
+
|
|
405
|
+
layer.add(image);
|
|
406
|
+
this.objectMap.set(item.id, image);
|
|
407
|
+
|
|
408
|
+
// Notify addImage that load is complete
|
|
409
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
410
|
+
if (resolver) {
|
|
411
|
+
resolver();
|
|
412
|
+
this.loadResolvers.delete(item.id);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Bind Events
|
|
416
|
+
image.on("modified", (e: any) => {
|
|
417
|
+
this.handleObjectModified(item.id, image);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
layer.dirty = true;
|
|
421
|
+
this.canvasService?.requestRenderAll();
|
|
422
|
+
|
|
423
|
+
// Save defaults if we set them
|
|
424
|
+
if (item.scale !== scale || item.left !== left || item.top !== top) {
|
|
425
|
+
this.updateImageInConfig(item.id, { scale, left, top }, true);
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
.catch((err) => {
|
|
429
|
+
console.error("Failed to load image", item.url, err);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private handleObjectModified(id: string, image: FabricObject) {
|
|
434
|
+
const layout = this.getLayoutInfo();
|
|
435
|
+
const {
|
|
436
|
+
layoutScale,
|
|
437
|
+
layoutOffsetX,
|
|
438
|
+
layoutOffsetY,
|
|
439
|
+
visualWidth,
|
|
440
|
+
visualHeight,
|
|
441
|
+
} = layout;
|
|
442
|
+
|
|
443
|
+
const matrix = image.calcTransformMatrix();
|
|
444
|
+
const globalPoint = util.transformPoint(new Point(0, 0), matrix);
|
|
445
|
+
|
|
446
|
+
const updates: Partial<ImageItem> = {};
|
|
447
|
+
|
|
448
|
+
// Normalize Position
|
|
449
|
+
updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
|
|
450
|
+
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
451
|
+
updates.angle = image.angle;
|
|
452
|
+
|
|
453
|
+
// Scale
|
|
454
|
+
updates.scale = image.scaleX / layoutScale;
|
|
455
|
+
|
|
456
|
+
this.updateImageInConfig(id, updates, true);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private updateImageInConfig(
|
|
460
|
+
id: string,
|
|
461
|
+
updates: Partial<ImageItem>,
|
|
462
|
+
skipCanvasUpdate = false,
|
|
463
|
+
) {
|
|
464
|
+
const index = this.items.findIndex((i) => i.id === id);
|
|
465
|
+
if (index !== -1) {
|
|
466
|
+
const newItems = [...this.items];
|
|
467
|
+
newItems[index] = { ...newItems[index], ...updates };
|
|
468
|
+
this.updateConfig(newItems, skipCanvasUpdate);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|