@pooder/kit 4.1.0 → 4.2.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.
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ImageTool = void 0;
4
+ const core_1 = require("@pooder/core");
5
+ const fabric_1 = require("fabric");
6
+ class ImageTool {
7
+ constructor() {
8
+ this.id = "pooder.kit.image";
9
+ this.metadata = {
10
+ name: "ImageTool",
11
+ };
12
+ this.items = [];
13
+ this.objectMap = new Map();
14
+ this.loadResolvers = new Map();
15
+ this.isUpdatingConfig = false;
16
+ this.isToolActive = false;
17
+ this.onToolActivated = (event) => {
18
+ this.isToolActive = event.id === this.id;
19
+ this.updateInteractivity();
20
+ };
21
+ }
22
+ activate(context) {
23
+ this.context = context;
24
+ this.canvasService = context.services.get("CanvasService");
25
+ if (!this.canvasService) {
26
+ console.warn("CanvasService not found for ImageTool");
27
+ return;
28
+ }
29
+ // Listen to tool activation
30
+ context.eventBus.on("tool:activated", this.onToolActivated);
31
+ const configService = context.services.get("ConfigurationService");
32
+ if (configService) {
33
+ // Load initial config
34
+ this.items = configService.get("image.items", []) || [];
35
+ // Listen for changes
36
+ configService.onAnyChange((e) => {
37
+ if (this.isUpdatingConfig)
38
+ return;
39
+ if (e.key === "image.items") {
40
+ this.items = e.value || [];
41
+ this.updateImages();
42
+ }
43
+ });
44
+ }
45
+ this.ensureLayer();
46
+ this.updateImages();
47
+ }
48
+ deactivate(context) {
49
+ context.eventBus.off("tool:activated", this.onToolActivated);
50
+ if (this.canvasService) {
51
+ const layer = this.canvasService.getLayer("user");
52
+ if (layer) {
53
+ this.objectMap.forEach((obj) => {
54
+ layer.remove(obj);
55
+ });
56
+ this.objectMap.clear();
57
+ this.canvasService.requestRenderAll();
58
+ }
59
+ this.canvasService = undefined;
60
+ this.context = undefined;
61
+ }
62
+ }
63
+ updateInteractivity() {
64
+ this.objectMap.forEach((obj) => {
65
+ obj.set({
66
+ selectable: this.isToolActive,
67
+ evented: this.isToolActive,
68
+ hasControls: this.isToolActive,
69
+ hasBorders: this.isToolActive,
70
+ });
71
+ });
72
+ this.canvasService?.requestRenderAll();
73
+ }
74
+ contribute() {
75
+ return {
76
+ [core_1.ContributionPointIds.CONFIGURATIONS]: [
77
+ {
78
+ id: "image.items",
79
+ type: "array",
80
+ label: "Images",
81
+ default: [],
82
+ },
83
+ ],
84
+ [core_1.ContributionPointIds.COMMANDS]: [
85
+ {
86
+ command: "addImage",
87
+ title: "Add Image",
88
+ handler: async (url, options) => {
89
+ const id = this.generateId();
90
+ const newItem = {
91
+ id,
92
+ url,
93
+ opacity: 1,
94
+ ...options,
95
+ };
96
+ const promise = new Promise((resolve) => {
97
+ this.loadResolvers.set(id, () => resolve(id));
98
+ });
99
+ this.updateConfig([...this.items, newItem]);
100
+ return promise;
101
+ },
102
+ },
103
+ {
104
+ command: "fitImageToArea",
105
+ title: "Fit Image to Area",
106
+ handler: (id, area) => {
107
+ const item = this.items.find((i) => i.id === id);
108
+ const obj = this.objectMap.get(id);
109
+ if (item && obj && obj.width && obj.height) {
110
+ const scale = Math.max(area.width / obj.width, area.height / obj.height);
111
+ this.updateImageInConfig(id, {
112
+ scale,
113
+ left: area.left ?? 0.5,
114
+ top: area.top ?? 0.5,
115
+ });
116
+ }
117
+ },
118
+ },
119
+ {
120
+ command: "removeImage",
121
+ title: "Remove Image",
122
+ handler: (id) => {
123
+ const newItems = this.items.filter((item) => item.id !== id);
124
+ if (newItems.length !== this.items.length) {
125
+ this.updateConfig(newItems);
126
+ }
127
+ },
128
+ },
129
+ {
130
+ command: "updateImage",
131
+ title: "Update Image",
132
+ handler: (id, updates) => {
133
+ const index = this.items.findIndex((item) => item.id === id);
134
+ if (index !== -1) {
135
+ const newItems = [...this.items];
136
+ newItems[index] = { ...newItems[index], ...updates };
137
+ this.updateConfig(newItems);
138
+ }
139
+ },
140
+ },
141
+ {
142
+ command: "clearImages",
143
+ title: "Clear Images",
144
+ handler: () => {
145
+ this.updateConfig([]);
146
+ },
147
+ },
148
+ {
149
+ command: "bringToFront",
150
+ title: "Bring Image to Front",
151
+ handler: (id) => {
152
+ const index = this.items.findIndex((item) => item.id === id);
153
+ if (index !== -1 && index < this.items.length - 1) {
154
+ const newItems = [...this.items];
155
+ const [item] = newItems.splice(index, 1);
156
+ newItems.push(item);
157
+ this.updateConfig(newItems);
158
+ }
159
+ },
160
+ },
161
+ {
162
+ command: "sendToBack",
163
+ title: "Send Image to Back",
164
+ handler: (id) => {
165
+ const index = this.items.findIndex((item) => item.id === id);
166
+ if (index > 0) {
167
+ const newItems = [...this.items];
168
+ const [item] = newItems.splice(index, 1);
169
+ newItems.unshift(item);
170
+ this.updateConfig(newItems);
171
+ }
172
+ },
173
+ },
174
+ ],
175
+ };
176
+ }
177
+ generateId() {
178
+ return Math.random().toString(36).substring(2, 9);
179
+ }
180
+ updateConfig(newItems, skipCanvasUpdate = false) {
181
+ if (!this.context)
182
+ return;
183
+ this.isUpdatingConfig = true;
184
+ this.items = newItems;
185
+ const configService = this.context.services.get("ConfigurationService");
186
+ if (configService) {
187
+ configService.update("image.items", newItems);
188
+ }
189
+ // Update canvas immediately to reflect changes locally before config event comes back
190
+ // (Optional, but good for responsiveness)
191
+ if (!skipCanvasUpdate) {
192
+ this.updateImages();
193
+ }
194
+ // Reset flag after a short delay to allow config propagation
195
+ setTimeout(() => {
196
+ this.isUpdatingConfig = false;
197
+ }, 50);
198
+ }
199
+ ensureLayer() {
200
+ if (!this.canvasService)
201
+ return;
202
+ let userLayer = this.canvasService.getLayer("user");
203
+ if (!userLayer) {
204
+ userLayer = this.canvasService.createLayer("user", {
205
+ width: this.canvasService.canvas.width,
206
+ height: this.canvasService.canvas.height,
207
+ left: 0,
208
+ top: 0,
209
+ originX: "left",
210
+ originY: "top",
211
+ selectable: false,
212
+ evented: true,
213
+ subTargetCheck: true,
214
+ interactive: true,
215
+ });
216
+ // Try to insert below dieline-overlay
217
+ const dielineLayer = this.canvasService.getLayer("dieline-overlay");
218
+ if (dielineLayer) {
219
+ const index = this.canvasService.canvas
220
+ .getObjects()
221
+ .indexOf(dielineLayer);
222
+ // If dieline is at 0, move user to 0 (dieline shifts to 1)
223
+ if (index >= 0) {
224
+ this.canvasService.canvas.moveObjectTo(userLayer, index);
225
+ }
226
+ }
227
+ else {
228
+ // Ensure background is behind
229
+ const bgLayer = this.canvasService.getLayer("background");
230
+ if (bgLayer) {
231
+ this.canvasService.canvas.sendObjectToBack(bgLayer);
232
+ }
233
+ }
234
+ this.canvasService.requestRenderAll();
235
+ }
236
+ }
237
+ getLayoutInfo() {
238
+ const canvasW = this.canvasService?.canvas.width || 800;
239
+ const canvasH = this.canvasService?.canvas.height || 600;
240
+ return {
241
+ layoutScale: 1,
242
+ layoutOffsetX: 0,
243
+ layoutOffsetY: 0,
244
+ visualWidth: canvasW,
245
+ visualHeight: canvasH,
246
+ };
247
+ }
248
+ updateImages() {
249
+ if (!this.canvasService)
250
+ return;
251
+ const layer = this.canvasService.getLayer("user");
252
+ if (!layer) {
253
+ console.warn("[ImageTool] User layer not found");
254
+ return;
255
+ }
256
+ // 1. Remove objects that are no longer in items
257
+ const currentIds = new Set(this.items.map((i) => i.id));
258
+ for (const [id, obj] of this.objectMap) {
259
+ if (!currentIds.has(id)) {
260
+ layer.remove(obj);
261
+ this.objectMap.delete(id);
262
+ }
263
+ }
264
+ // 2. Add or Update objects
265
+ const layout = this.getLayoutInfo();
266
+ this.items.forEach((item, index) => {
267
+ let obj = this.objectMap.get(item.id);
268
+ // Check if URL changed, if so remove object to force reload
269
+ // We assume Fabric object has getSrc() or we check data.url if we stored it
270
+ // Since we don't store url on object easily accessible without casting,
271
+ // let's rely on checking if we need to reload.
272
+ // Actually, standard Fabric Image doesn't expose src easily on type without casting to any.
273
+ if (obj && obj.getSrc) {
274
+ const currentSrc = obj.getSrc();
275
+ if (currentSrc !== item.url) {
276
+ layer.remove(obj);
277
+ this.objectMap.delete(item.id);
278
+ obj = undefined;
279
+ }
280
+ }
281
+ if (!obj) {
282
+ // New object, load it
283
+ this.loadImage(item, layer, layout);
284
+ }
285
+ else {
286
+ // Existing object, update properties
287
+ // We remove and re-add to ensure coordinates are correctly converted
288
+ // from absolute (updateObjectProperties) to relative (layer.add)
289
+ layer.remove(obj);
290
+ this.updateObjectProperties(obj, item, layout);
291
+ layer.add(obj);
292
+ }
293
+ });
294
+ layer.dirty = true;
295
+ this.canvasService.requestRenderAll();
296
+ }
297
+ updateObjectProperties(obj, item, layout) {
298
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, } = layout;
299
+ const updates = {};
300
+ // Opacity
301
+ if (obj.opacity !== item.opacity)
302
+ updates.opacity = item.opacity;
303
+ // Angle
304
+ if (item.angle !== undefined && obj.angle !== item.angle)
305
+ updates.angle = item.angle;
306
+ // Position (Normalized -> Absolute)
307
+ if (item.left !== undefined) {
308
+ const globalLeft = layoutOffsetX + item.left * visualWidth;
309
+ if (Math.abs(obj.left - globalLeft) > 1)
310
+ updates.left = globalLeft;
311
+ }
312
+ if (item.top !== undefined) {
313
+ const globalTop = layoutOffsetY + item.top * visualHeight;
314
+ if (Math.abs(obj.top - globalTop) > 1)
315
+ updates.top = globalTop;
316
+ }
317
+ // Scale
318
+ if (item.scale !== undefined) {
319
+ const targetScale = item.scale * layoutScale;
320
+ if (Math.abs(obj.scaleX - targetScale) > 0.001) {
321
+ updates.scaleX = targetScale;
322
+ updates.scaleY = targetScale;
323
+ }
324
+ }
325
+ // Center origin if not set
326
+ if (obj.originX !== "center") {
327
+ updates.originX = "center";
328
+ updates.originY = "center";
329
+ // Adjust position because origin changed (Fabric logic)
330
+ // For simplicity, we just set it, next cycle will fix pos if needed,
331
+ // or we can calculate the shift. Ideally we set origin on creation.
332
+ }
333
+ if (Object.keys(updates).length > 0) {
334
+ obj.set(updates);
335
+ obj.setCoords();
336
+ }
337
+ }
338
+ loadImage(item, layer, layout) {
339
+ fabric_1.Image.fromURL(item.url, { crossOrigin: "anonymous" })
340
+ .then((image) => {
341
+ // Double check if item still exists
342
+ if (!this.items.find((i) => i.id === item.id))
343
+ return;
344
+ image.set({
345
+ originX: "center",
346
+ originY: "center",
347
+ data: { id: item.id },
348
+ uniformScaling: true,
349
+ lockScalingFlip: true,
350
+ selectable: this.isToolActive,
351
+ evented: this.isToolActive,
352
+ hasControls: this.isToolActive,
353
+ hasBorders: this.isToolActive,
354
+ });
355
+ image.setControlsVisibility({
356
+ mt: false,
357
+ mb: false,
358
+ ml: false,
359
+ mr: false,
360
+ });
361
+ // Initial Layout
362
+ let { scale, left, top } = item;
363
+ if (scale === undefined) {
364
+ scale = 1; // Default scale if not provided and not fitted yet
365
+ item.scale = scale;
366
+ }
367
+ if (left === undefined && top === undefined) {
368
+ left = 0.5;
369
+ top = 0.5;
370
+ item.left = left;
371
+ item.top = top;
372
+ }
373
+ // Apply Props
374
+ this.updateObjectProperties(image, item, layout);
375
+ layer.add(image);
376
+ this.objectMap.set(item.id, image);
377
+ // Notify addImage that load is complete
378
+ const resolver = this.loadResolvers.get(item.id);
379
+ if (resolver) {
380
+ resolver();
381
+ this.loadResolvers.delete(item.id);
382
+ }
383
+ // Bind Events
384
+ image.on("modified", (e) => {
385
+ this.handleObjectModified(item.id, image);
386
+ });
387
+ layer.dirty = true;
388
+ this.canvasService?.requestRenderAll();
389
+ // Save defaults if we set them
390
+ if (item.scale !== scale || item.left !== left || item.top !== top) {
391
+ this.updateImageInConfig(item.id, { scale, left, top }, true);
392
+ }
393
+ })
394
+ .catch((err) => {
395
+ console.error("Failed to load image", item.url, err);
396
+ });
397
+ }
398
+ handleObjectModified(id, image) {
399
+ const layout = this.getLayoutInfo();
400
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, } = layout;
401
+ const matrix = image.calcTransformMatrix();
402
+ const globalPoint = fabric_1.util.transformPoint(new fabric_1.Point(0, 0), matrix);
403
+ const updates = {};
404
+ // Normalize Position
405
+ updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
406
+ updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
407
+ updates.angle = image.angle;
408
+ // Scale
409
+ updates.scale = image.scaleX / layoutScale;
410
+ this.updateImageInConfig(id, updates, true);
411
+ }
412
+ updateImageInConfig(id, updates, skipCanvasUpdate = false) {
413
+ const index = this.items.findIndex((i) => i.id === id);
414
+ if (index !== -1) {
415
+ const newItems = [...this.items];
416
+ newItems[index] = { ...newItems[index], ...updates };
417
+ this.updateConfig(newItems, skipCanvasUpdate);
418
+ }
419
+ }
420
+ }
421
+ exports.ImageTool = ImageTool;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.CanvasService = void 0;
21
+ __exportStar(require("./background"), exports);
22
+ __exportStar(require("./dieline"), exports);
23
+ __exportStar(require("./film"), exports);
24
+ __exportStar(require("./feature"), exports);
25
+ __exportStar(require("./image"), exports);
26
+ __exportStar(require("./white-ink"), exports);
27
+ __exportStar(require("./ruler"), exports);
28
+ __exportStar(require("./mirror"), exports);
29
+ __exportStar(require("./units"), exports);
30
+ var CanvasService_1 = require("./CanvasService");
31
+ Object.defineProperty(exports, "CanvasService", { enumerable: true, get: function () { return __importDefault(CanvasService_1).default; } });
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MirrorTool = void 0;
4
+ const core_1 = require("@pooder/core");
5
+ class MirrorTool {
6
+ constructor(options) {
7
+ this.id = "pooder.kit.mirror";
8
+ this.metadata = {
9
+ name: "MirrorTool",
10
+ };
11
+ this.enabled = false;
12
+ if (options) {
13
+ Object.assign(this, options);
14
+ }
15
+ }
16
+ toJSON() {
17
+ return {
18
+ enabled: this.enabled,
19
+ };
20
+ }
21
+ loadFromJSON(json) {
22
+ this.enabled = json.enabled;
23
+ }
24
+ activate(context) {
25
+ this.canvasService = context.services.get("CanvasService");
26
+ if (!this.canvasService) {
27
+ console.warn("CanvasService not found for MirrorTool");
28
+ return;
29
+ }
30
+ const configService = context.services.get("ConfigurationService");
31
+ if (configService) {
32
+ // Load initial config
33
+ this.enabled = configService.get("mirror.enabled", this.enabled);
34
+ // Listen for changes
35
+ configService.onAnyChange((e) => {
36
+ if (e.key === "mirror.enabled") {
37
+ this.applyMirror(e.value);
38
+ }
39
+ });
40
+ }
41
+ // Initialize with current state (if enabled was persisted)
42
+ if (this.enabled) {
43
+ this.applyMirror(true);
44
+ }
45
+ }
46
+ deactivate(context) {
47
+ this.applyMirror(false);
48
+ this.canvasService = undefined;
49
+ }
50
+ contribute() {
51
+ return {
52
+ [core_1.ContributionPointIds.CONFIGURATIONS]: [
53
+ {
54
+ id: "mirror.enabled",
55
+ type: "boolean",
56
+ label: "Enable Mirror",
57
+ default: false,
58
+ },
59
+ ],
60
+ [core_1.ContributionPointIds.COMMANDS]: [
61
+ {
62
+ command: "setMirror",
63
+ title: "Set Mirror",
64
+ handler: (enabled) => {
65
+ this.applyMirror(enabled);
66
+ return true;
67
+ },
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ applyMirror(enabled) {
73
+ if (!this.canvasService)
74
+ return;
75
+ const canvas = this.canvasService.canvas;
76
+ if (!canvas)
77
+ return;
78
+ const width = canvas.width || 800;
79
+ // Fabric.js v6+ uses viewportTransform property
80
+ let vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
81
+ // Create a copy to avoid mutating the reference directly before setting
82
+ vpt = [...vpt];
83
+ // If we are enabling and currently not flipped (scaleX > 0)
84
+ // Or disabling and currently flipped (scaleX < 0)
85
+ const isFlipped = vpt[0] < 0;
86
+ if (enabled && !isFlipped) {
87
+ // Flip scale X
88
+ vpt[0] = -vpt[0]; // Flip scale
89
+ vpt[4] = width - vpt[4]; // Adjust pan X
90
+ canvas.setViewportTransform(vpt);
91
+ canvas.requestRenderAll();
92
+ this.enabled = true;
93
+ }
94
+ else if (!enabled && isFlipped) {
95
+ // Restore
96
+ vpt[0] = -vpt[0]; // Unflip scale
97
+ vpt[4] = width - vpt[4]; // Restore pan X
98
+ canvas.setViewportTransform(vpt);
99
+ canvas.requestRenderAll();
100
+ this.enabled = false;
101
+ }
102
+ }
103
+ }
104
+ exports.MirrorTool = MirrorTool;