@merchi/product-editor 0.1.9

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 (123) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/README.md +174 -0
  3. package/dist/components/AlignMenu.d.ts +3 -0
  4. package/dist/components/AlignMenu.js +61 -0
  5. package/dist/components/FloatingToolbar.d.ts +4 -0
  6. package/dist/components/FloatingToolbar.js +97 -0
  7. package/dist/components/FormatButtons.d.ts +2 -0
  8. package/dist/components/FormatButtons.js +22 -0
  9. package/dist/components/ImageNavButton.d.ts +7 -0
  10. package/dist/components/ImageNavButton.js +14 -0
  11. package/dist/components/ImageZoomModal.d.ts +12 -0
  12. package/dist/components/ImageZoomModal.js +129 -0
  13. package/dist/components/LayerPanel.d.ts +4 -0
  14. package/dist/components/LayerPanel.js +223 -0
  15. package/dist/components/LoadingOverlay.d.ts +9 -0
  16. package/dist/components/LoadingOverlay.js +17 -0
  17. package/dist/components/ProductEditor.d.ts +4 -0
  18. package/dist/components/ProductEditor.js +67 -0
  19. package/dist/components/ProductImageGallery.d.ts +6 -0
  20. package/dist/components/ProductImageGallery.js +148 -0
  21. package/dist/components/ProductPreviews.d.ts +7 -0
  22. package/dist/components/ProductPreviews.js +281 -0
  23. package/dist/components/TextToolbar.d.ts +4 -0
  24. package/dist/components/TextToolbar.js +117 -0
  25. package/dist/components/Toolbar.d.ts +3 -0
  26. package/dist/components/Toolbar.js +47 -0
  27. package/dist/config/colorConfig.d.ts +1 -0
  28. package/dist/config/colorConfig.js +10 -0
  29. package/dist/config/fontConfig.d.ts +5 -0
  30. package/dist/config/fontConfig.js +21 -0
  31. package/dist/context/ProductEditorContext.d.ts +60 -0
  32. package/dist/context/ProductEditorContext.js +743 -0
  33. package/dist/hooks/useSwipeNavigate.d.ts +11 -0
  34. package/dist/hooks/useSwipeNavigate.js +35 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.js +30 -0
  37. package/dist/styles/BottomPreviewDisplay.css +65 -0
  38. package/dist/styles/FloatingToolbar.css +41 -0
  39. package/dist/styles/ImageGallery.css +147 -0
  40. package/dist/styles/ImageNavButton.css +35 -0
  41. package/dist/styles/ImageZoomModal.css +101 -0
  42. package/dist/styles/LayerPanel.css +179 -0
  43. package/dist/styles/LoadingOverlay.css +48 -0
  44. package/dist/styles/ProductEditor.css +192 -0
  45. package/dist/styles/TextToolbar.css +327 -0
  46. package/dist/styles/Toolbar.css +125 -0
  47. package/dist/styles/index.css +5 -0
  48. package/dist/styles/textToolbarSelectStyles.d.ts +3 -0
  49. package/dist/styles/textToolbarSelectStyles.js +30 -0
  50. package/dist/types.d.ts +228 -0
  51. package/dist/types.js +17 -0
  52. package/dist/utils/canvasAddText.d.ts +11 -0
  53. package/dist/utils/canvasAddText.js +66 -0
  54. package/dist/utils/canvasUtils.d.ts +37 -0
  55. package/dist/utils/canvasUtils.js +342 -0
  56. package/dist/utils/deviceUtils.d.ts +5 -0
  57. package/dist/utils/deviceUtils.js +33 -0
  58. package/dist/utils/draftTemplateUtils.d.ts +23 -0
  59. package/dist/utils/draftTemplateUtils.js +66 -0
  60. package/dist/utils/grid.d.ts +32 -0
  61. package/dist/utils/grid.js +151 -0
  62. package/dist/utils/imageUtils.d.ts +12 -0
  63. package/dist/utils/imageUtils.js +178 -0
  64. package/dist/utils/job.d.ts +40 -0
  65. package/dist/utils/job.js +115 -0
  66. package/dist/utils/keyboard.d.ts +1 -0
  67. package/dist/utils/keyboard.js +26 -0
  68. package/dist/utils/previewUtils.d.ts +9 -0
  69. package/dist/utils/previewUtils.js +39 -0
  70. package/dist/utils/psdConverter.d.ts +40 -0
  71. package/dist/utils/psdConverter.js +516 -0
  72. package/dist/utils/psdRenderUtils.d.ts +12 -0
  73. package/dist/utils/psdRenderUtils.js +465 -0
  74. package/dist/utils/renderUtils.d.ts +2 -0
  75. package/dist/utils/renderUtils.js +85 -0
  76. package/package.json +66 -0
  77. package/src/components/AlignMenu.tsx +49 -0
  78. package/src/components/FloatingToolbar.tsx +83 -0
  79. package/src/components/FormatButtons.tsx +31 -0
  80. package/src/components/ImageNavButton.tsx +23 -0
  81. package/src/components/ImageZoomModal.tsx +163 -0
  82. package/src/components/LayerPanel.tsx +256 -0
  83. package/src/components/LoadingOverlay.tsx +27 -0
  84. package/src/components/ProductEditor.tsx +104 -0
  85. package/src/components/ProductImageGallery.tsx +181 -0
  86. package/src/components/ProductPreviews.tsx +243 -0
  87. package/src/components/TextToolbar.tsx +164 -0
  88. package/src/components/Toolbar.tsx +86 -0
  89. package/src/config/colorConfig.ts +7 -0
  90. package/src/config/fontConfig.ts +23 -0
  91. package/src/context/ProductEditorContext.tsx +829 -0
  92. package/src/hooks/useSwipeNavigate.ts +40 -0
  93. package/src/index.ts +6 -0
  94. package/src/styles/BottomPreviewDisplay.css +65 -0
  95. package/src/styles/FloatingToolbar.css +41 -0
  96. package/src/styles/ImageGallery.css +147 -0
  97. package/src/styles/ImageNavButton.css +35 -0
  98. package/src/styles/ImageZoomModal.css +101 -0
  99. package/src/styles/LayerPanel.css +179 -0
  100. package/src/styles/LoadingOverlay.css +48 -0
  101. package/src/styles/ProductEditor.css +192 -0
  102. package/src/styles/TextToolbar.css +327 -0
  103. package/src/styles/Toolbar.css +125 -0
  104. package/src/styles/index.css +5 -0
  105. package/src/styles/textToolbarSelectStyles.ts +60 -0
  106. package/src/types/ag-psd/index.d.ts +1 -0
  107. package/src/types/fabric.d.ts +14 -0
  108. package/src/types/lodash/index.d.ts +1 -0
  109. package/src/types/psd.d.ts +53 -0
  110. package/src/types.ts +247 -0
  111. package/src/utils/canvasAddText.ts +72 -0
  112. package/src/utils/canvasUtils.ts +406 -0
  113. package/src/utils/deviceUtils.ts +31 -0
  114. package/src/utils/draftTemplateUtils.ts +70 -0
  115. package/src/utils/grid.ts +154 -0
  116. package/src/utils/imageUtils.ts +218 -0
  117. package/src/utils/job.ts +128 -0
  118. package/src/utils/keyboard.ts +28 -0
  119. package/src/utils/previewUtils.ts +43 -0
  120. package/src/utils/psdConverter.ts +595 -0
  121. package/src/utils/psdRenderUtils.ts +453 -0
  122. package/src/utils/renderUtils.ts +44 -0
  123. package/tsconfig.json +17 -0
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.renderDraftPreviewsWithLayers = void 0;
40
+ var ag_psd_1 = require("ag-psd");
41
+ /**
42
+ * Find a layer by name within a PSD layer structure
43
+ *
44
+ * @param layers The array of PSD layers to search through
45
+ * @param name The name of the layer to find
46
+ * @returns The found layer or null if not found
47
+ */
48
+ function findLayerByName(layers, name) {
49
+ if (!layers)
50
+ return null;
51
+ for (var _i = 0, layers_1 = layers; _i < layers_1.length; _i++) {
52
+ var layer = layers_1[_i];
53
+ if (layer.name === name) {
54
+ return layer;
55
+ }
56
+ if (layer.children) {
57
+ var found = findLayerByName(layer.children, name);
58
+ if (found)
59
+ return found;
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Load an image from a URL
66
+ *
67
+ * @param url URL of the image to load
68
+ * @returns Promise that resolves to an HTMLImageElement
69
+ */
70
+ function loadImage(url) {
71
+ return __awaiter(this, void 0, void 0, function () {
72
+ return __generator(this, function (_a) {
73
+ return [2 /*return*/, new Promise(function (resolve, reject) {
74
+ var img = new Image();
75
+ img.crossOrigin = 'anonymous';
76
+ img.onload = function () { return resolve(img); };
77
+ img.onerror = function (e) { return reject(new Error("Failed to load image from URL: ".concat(url, ", error: ").concat(e))); };
78
+ img.src = url;
79
+ })];
80
+ });
81
+ });
82
+ }
83
+ /**
84
+ * Validates if a data URL represents a valid image
85
+ *
86
+ * @param dataUrl The data URL to validate
87
+ * @returns Promise that resolves to true if valid, false otherwise
88
+ */
89
+ function isValidImageDataUrl(dataUrl) {
90
+ return __awaiter(this, void 0, void 0, function () {
91
+ return __generator(this, function (_a) {
92
+ return [2 /*return*/, new Promise(function (resolve) {
93
+ if (!dataUrl || !dataUrl.startsWith('data:image/')) {
94
+ console.warn('Invalid data URL format');
95
+ resolve(false);
96
+ return;
97
+ }
98
+ var img = new Image();
99
+ img.onload = function () {
100
+ // Check if the image has actual dimensions
101
+ if (img.width > 0 && img.height > 0) {
102
+ resolve(true);
103
+ }
104
+ else {
105
+ console.warn('Image loaded but has zero dimensions');
106
+ resolve(false);
107
+ }
108
+ };
109
+ img.onerror = function () {
110
+ console.warn('Failed to load image from data URL');
111
+ resolve(false);
112
+ };
113
+ img.src = dataUrl;
114
+ })];
115
+ });
116
+ });
117
+ }
118
+ /**
119
+ * Creates a fallback preview image with debugging information
120
+ *
121
+ * @param width Width of the image
122
+ * @param height Height of the image
123
+ * @param draftPreviewId ID of the draft preview
124
+ * @param error Optional error message
125
+ * @returns PNG data URL
126
+ */
127
+ function createFallbackImage(width, height, draftPreviewId, error) {
128
+ var canvas = document.createElement('canvas');
129
+ canvas.width = Math.max(width || 300, 300);
130
+ canvas.height = Math.max(height || 200, 200);
131
+ var ctx = canvas.getContext('2d');
132
+ if (!ctx) {
133
+ // Even more basic fallback if context fails
134
+ var basicCanvas = document.createElement('canvas');
135
+ basicCanvas.width = 300;
136
+ basicCanvas.height = 200;
137
+ var basicCtx = basicCanvas.getContext('2d');
138
+ if (basicCtx) {
139
+ basicCtx.fillStyle = '#ffcccc';
140
+ basicCtx.fillRect(0, 0, 300, 200);
141
+ basicCtx.fillStyle = '#990000';
142
+ basicCtx.font = '16px Arial';
143
+ basicCtx.textAlign = 'center';
144
+ basicCtx.fillText('Preview failed', 150, 100);
145
+ return basicCanvas.toDataURL('image/png');
146
+ }
147
+ return '';
148
+ }
149
+ // Background
150
+ ctx.fillStyle = '#f8f8f8';
151
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
152
+ // Draw a grid pattern
153
+ ctx.strokeStyle = '#e0e0e0';
154
+ ctx.lineWidth = 1;
155
+ var gridSize = 20;
156
+ for (var x = 0; x < canvas.width; x += gridSize) {
157
+ ctx.beginPath();
158
+ ctx.moveTo(x, 0);
159
+ ctx.lineTo(x, canvas.height);
160
+ ctx.stroke();
161
+ }
162
+ for (var y = 0; y < canvas.height; y += gridSize) {
163
+ ctx.beginPath();
164
+ ctx.moveTo(0, y);
165
+ ctx.lineTo(canvas.width, y);
166
+ ctx.stroke();
167
+ }
168
+ // Border
169
+ ctx.strokeStyle = '#cccccc';
170
+ ctx.lineWidth = 2;
171
+ ctx.strokeRect(0, 0, canvas.width, canvas.height);
172
+ // Preview text
173
+ ctx.fillStyle = '#666666';
174
+ ctx.font = 'bold 24px Arial';
175
+ ctx.textAlign = 'center';
176
+ ctx.fillText('Preview', canvas.width / 2, canvas.height / 2 - 15);
177
+ // Preview ID
178
+ if (draftPreviewId !== undefined) {
179
+ ctx.font = '16px Arial';
180
+ ctx.fillText("ID: ".concat(draftPreviewId), canvas.width / 2, canvas.height / 2 + 15);
181
+ }
182
+ // Error message if provided
183
+ if (error) {
184
+ ctx.fillStyle = '#cc0000';
185
+ ctx.font = '14px Arial';
186
+ ctx.fillText(error, canvas.width / 2, canvas.height / 2 + 40);
187
+ }
188
+ return canvas.toDataURL('image/png');
189
+ }
190
+ /**
191
+ * Converts a data URL to a canvas element
192
+ *
193
+ * @param dataUrl The data URL to convert
194
+ * @returns Promise that resolves to a Canvas element
195
+ */
196
+ function dataUrlToCanvas(dataUrl) {
197
+ return __awaiter(this, void 0, void 0, function () {
198
+ return __generator(this, function (_a) {
199
+ switch (_a.label) {
200
+ case 0: return [4 /*yield*/, loadImage(dataUrl)];
201
+ case 1:
202
+ // First load the data URL as an image
203
+ return [2 /*return*/, _a.sent()];
204
+ }
205
+ });
206
+ });
207
+ }
208
+ /**
209
+ * Process mapped previews, replacing layers in PSD files with rendered layers
210
+ * and converting the results to PNG images
211
+ *
212
+ * @param mappedPreviews Array of mapped previews with draftPreview and draftPreviewLayers
213
+ * @returns Promise that resolves to an array of processed previews with PNG data URLs
214
+ */
215
+ function renderDraftPreviewsWithLayers(mappedPreviews) {
216
+ var _a;
217
+ return __awaiter(this, void 0, void 0, function () {
218
+ var results, _i, mappedPreviews_1, mappedPreview, draftPreview, draftPreviewLayers, hasRenderedLayers, simplePngDataUrl, response, arrayBuffer, firstBytes, signature, psd, canvas, ctx, imageData_1, _loop_1, _b, _c, layer, _d, draftPreviewLayers_1, draftPreviewLayer, psdLayer, layerCanvas, left, top_1, width, height, aspectRatio, targetAspectRatio, drawWidth, drawHeight, offsetX, offsetY, error_1, imageData, hasVisiblePixels, i, pngDataUrl, isValid, fallbackPng, error_2, errorMessage, fallbackPng;
219
+ return __generator(this, function (_e) {
220
+ switch (_e.label) {
221
+ case 0:
222
+ results = [];
223
+ _i = 0, mappedPreviews_1 = mappedPreviews;
224
+ _e.label = 1;
225
+ case 1:
226
+ if (!(_i < mappedPreviews_1.length)) return [3 /*break*/, 14];
227
+ mappedPreview = mappedPreviews_1[_i];
228
+ draftPreview = mappedPreview.draftPreview, draftPreviewLayers = mappedPreview.draftPreviewLayers;
229
+ // Skip if there's no file to process
230
+ if (!((_a = draftPreview === null || draftPreview === void 0 ? void 0 : draftPreview.file) === null || _a === void 0 ? void 0 : _a.viewUrl)) {
231
+ console.warn('Skipping preview without a file URL', draftPreview === null || draftPreview === void 0 ? void 0 : draftPreview.id);
232
+ return [3 /*break*/, 13];
233
+ }
234
+ hasRenderedLayers = draftPreviewLayers.some(function (layer) { return layer.renderedLayer !== null; });
235
+ simplePngDataUrl = null;
236
+ _e.label = 2;
237
+ case 2:
238
+ _e.trys.push([2, 12, , 13]);
239
+ return [4 /*yield*/, fetch(draftPreview.file.viewUrl, {
240
+ mode: 'cors',
241
+ credentials: 'same-origin',
242
+ headers: {
243
+ 'Accept': '*/*',
244
+ }
245
+ })];
246
+ case 3:
247
+ response = _e.sent();
248
+ if (!response.ok) {
249
+ throw new Error("Failed to fetch PSD file: ".concat(response.status, " ").concat(response.statusText));
250
+ }
251
+ return [4 /*yield*/, response.arrayBuffer()];
252
+ case 4:
253
+ arrayBuffer = _e.sent();
254
+ firstBytes = new Uint8Array(arrayBuffer, 0, 4);
255
+ signature = String.fromCharCode(firstBytes[0]) +
256
+ String.fromCharCode(firstBytes[1]) +
257
+ String.fromCharCode(firstBytes[2]) +
258
+ String.fromCharCode(firstBytes[3]);
259
+ if (signature !== '8BPS') {
260
+ // Use the simple approach result if we have it
261
+ if (simplePngDataUrl) {
262
+ results.push({
263
+ draftPreviewId: draftPreview.id,
264
+ pngDataUrl: simplePngDataUrl
265
+ });
266
+ return [3 /*break*/, 13];
267
+ }
268
+ throw new Error('Not a PSD file and simple approach failed');
269
+ }
270
+ psd = (0, ag_psd_1.readPsd)(arrayBuffer, {
271
+ skipCompositeImageData: false,
272
+ skipLayerImageData: false,
273
+ skipThumbnail: false
274
+ });
275
+ canvas = document.createElement('canvas');
276
+ canvas.width = psd.width || 500; // Default size if missing
277
+ canvas.height = psd.height || 400;
278
+ ctx = canvas.getContext('2d');
279
+ if (!ctx) {
280
+ throw new Error('Failed to get canvas 2d context');
281
+ }
282
+ // Draw a background to ensure we have something visible
283
+ ctx.fillStyle = 'transparent';
284
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
285
+ // First draw the composite image as a base
286
+ if (psd.imageData) {
287
+ imageData_1 = new ImageData(new Uint8ClampedArray(psd.imageData.data), psd.width, psd.height);
288
+ ctx.putImageData(imageData_1, 0, 0);
289
+ }
290
+ // Now draw base layers that aren't being replaced
291
+ if (psd.children && psd.children.length > 0) {
292
+ _loop_1 = function (layer) {
293
+ // Skip if this layer is hidden or matches one of our replaceable layers
294
+ var shouldReplace = draftPreviewLayers.some(function (dpl) {
295
+ return dpl.layerName === layer.name && dpl.renderedLayer !== null;
296
+ });
297
+ if (!shouldReplace && !layer.hidden && layer.canvas) {
298
+ // Draw the original layer
299
+ ctx.drawImage(layer.canvas, layer.left || 0, layer.top || 0);
300
+ ctx.globalAlpha = 1;
301
+ }
302
+ };
303
+ for (_b = 0, _c = psd.children; _b < _c.length; _b++) {
304
+ layer = _c[_b];
305
+ _loop_1(layer);
306
+ }
307
+ }
308
+ if (!hasRenderedLayers) return [3 /*break*/, 10];
309
+ _d = 0, draftPreviewLayers_1 = draftPreviewLayers;
310
+ _e.label = 5;
311
+ case 5:
312
+ if (!(_d < draftPreviewLayers_1.length)) return [3 /*break*/, 10];
313
+ draftPreviewLayer = draftPreviewLayers_1[_d];
314
+ // Skip if no layer name or no rendered layer
315
+ if (!draftPreviewLayer.layerName || !draftPreviewLayer.renderedLayer) {
316
+ return [3 /*break*/, 9];
317
+ }
318
+ psdLayer = findLayerByName(psd.children, draftPreviewLayer.layerName);
319
+ if (!psdLayer) {
320
+ console.warn("Layer \"".concat(draftPreviewLayer.layerName, "\" not found in PSD"));
321
+ return [3 /*break*/, 9];
322
+ }
323
+ _e.label = 6;
324
+ case 6:
325
+ _e.trys.push([6, 8, , 9]);
326
+ // Validate the rendered layer image URL
327
+ if (!draftPreviewLayer.renderedLayer.draft ||
328
+ !draftPreviewLayer.renderedLayer.draft.startsWith('data:image/')) {
329
+ throw new Error('Invalid image URL format');
330
+ }
331
+ return [4 /*yield*/, dataUrlToCanvas(draftPreviewLayer.renderedLayer.draft)];
332
+ case 7:
333
+ layerCanvas = _e.sent();
334
+ // Validate the canvas dimensions
335
+ if (layerCanvas.width <= 0 || layerCanvas.height <= 0) {
336
+ console.error('Canvas has invalid dimensions');
337
+ throw new Error('Invalid canvas dimensions');
338
+ }
339
+ left = psdLayer.left || 0;
340
+ top_1 = psdLayer.top || 0;
341
+ width = (psdLayer.right || 0) - (psdLayer.left || 0);
342
+ height = (psdLayer.bottom || 0) - (psdLayer.top || 0);
343
+ if (width <= 0 || height <= 0) {
344
+ console.warn("Invalid layer dimensions: ".concat(width, "x").concat(height, ", skipping"));
345
+ return [3 /*break*/, 9];
346
+ }
347
+ aspectRatio = layerCanvas.width / layerCanvas.height;
348
+ targetAspectRatio = width / height;
349
+ drawWidth = void 0, drawHeight = void 0, offsetX = 0, offsetY = 0;
350
+ if (aspectRatio > targetAspectRatio) {
351
+ // Canvas is wider than target area
352
+ drawWidth = width;
353
+ drawHeight = width / aspectRatio;
354
+ offsetY = (height - drawHeight) / 2;
355
+ }
356
+ else {
357
+ // Canvas is taller than target area
358
+ drawHeight = height;
359
+ drawWidth = height * aspectRatio;
360
+ offsetX = (width - drawWidth) / 2;
361
+ }
362
+ // Draw the rendered layer canvas
363
+ // ctx.globalAlpha = psdLayer.opacity !== undefined ? psdLayer.opacity / 255 : 1;
364
+ ctx.restore();
365
+ ctx.drawImage(layerCanvas, left + offsetX, top_1 + offsetY, drawWidth, drawHeight);
366
+ ctx.globalAlpha = 1;
367
+ ctx.save();
368
+ return [3 /*break*/, 9];
369
+ case 8:
370
+ error_1 = _e.sent();
371
+ console.error("Failed to load rendered layer image for \"".concat(draftPreviewLayer.layerName, "\""), error_1);
372
+ // If we fail to load the replacement, draw the original layer as fallback
373
+ if (psdLayer.canvas) {
374
+ ctx.globalAlpha = psdLayer.opacity !== undefined ? psdLayer.opacity / 255 : 1;
375
+ ctx.drawImage(psdLayer.canvas, psdLayer.left || 0, psdLayer.top || 0);
376
+ ctx.globalAlpha = 1;
377
+ }
378
+ return [3 /*break*/, 9];
379
+ case 9:
380
+ _d++;
381
+ return [3 /*break*/, 5];
382
+ case 10:
383
+ imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
384
+ hasVisiblePixels = false;
385
+ for (i = 3; i < imageData.data.length; i += 4) {
386
+ if (imageData.data[i] > 0) {
387
+ hasVisiblePixels = true;
388
+ break;
389
+ }
390
+ }
391
+ if (!hasVisiblePixels) {
392
+ console.warn('WARNING: Canvas appears to be completely transparent!');
393
+ // Use the simple approach result if available
394
+ if (simplePngDataUrl) {
395
+ results.push({
396
+ draftPreviewId: draftPreview.id,
397
+ pngDataUrl: simplePngDataUrl
398
+ });
399
+ return [3 /*break*/, 13];
400
+ }
401
+ // As a last resort, draw a visible placeholder
402
+ ctx.fillStyle = 'rgba(200, 200, 200, 0.5)';
403
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
404
+ ctx.fillStyle = 'rgba(150, 150, 150, 1)';
405
+ ctx.font = '20px Arial';
406
+ ctx.textAlign = 'center';
407
+ ctx.fillText('Preview', canvas.width / 2, canvas.height / 2);
408
+ }
409
+ pngDataUrl = canvas.toDataURL('image/png');
410
+ return [4 /*yield*/, isValidImageDataUrl(pngDataUrl)];
411
+ case 11:
412
+ isValid = _e.sent();
413
+ if (!isValid) {
414
+ console.warn('Generated PNG data URL is not valid!');
415
+ // Use the simple approach as fallback if available
416
+ if (simplePngDataUrl) {
417
+ results.push({
418
+ draftPreviewId: draftPreview.id,
419
+ pngDataUrl: simplePngDataUrl
420
+ });
421
+ }
422
+ else {
423
+ fallbackPng = createFallbackImage(500, 400, draftPreview.id, 'Invalid PNG data');
424
+ results.push({
425
+ draftPreviewId: draftPreview.id,
426
+ pngDataUrl: fallbackPng
427
+ });
428
+ }
429
+ }
430
+ else {
431
+ // Add the result to our array
432
+ results.push({
433
+ draftPreviewId: draftPreview.id,
434
+ pngDataUrl: pngDataUrl
435
+ });
436
+ }
437
+ return [3 /*break*/, 13];
438
+ case 12:
439
+ error_2 = _e.sent();
440
+ console.error('Failed to process PSD file for draft preview:', error_2);
441
+ // Use the simple approach as fallback if available
442
+ if (simplePngDataUrl) {
443
+ results.push({
444
+ draftPreviewId: draftPreview.id,
445
+ pngDataUrl: simplePngDataUrl
446
+ });
447
+ }
448
+ else {
449
+ errorMessage = error_2 instanceof Error ? error_2.message : 'Unknown error';
450
+ fallbackPng = createFallbackImage(500, 400, draftPreview.id, errorMessage);
451
+ results.push({
452
+ draftPreviewId: draftPreview.id,
453
+ pngDataUrl: fallbackPng
454
+ });
455
+ }
456
+ return [3 /*break*/, 13];
457
+ case 13:
458
+ _i++;
459
+ return [3 /*break*/, 1];
460
+ case 14: return [2 /*return*/, results];
461
+ }
462
+ });
463
+ });
464
+ }
465
+ exports.renderDraftPreviewsWithLayers = renderDraftPreviewsWithLayers;
@@ -0,0 +1,2 @@
1
+ import { DraftTemplate, SavedCanvasObject } from "../types";
2
+ export declare const renderEditorOrPreview: (fabricCanvas: fabric.Canvas, template: DraftTemplate, variations: any[], savedObjects: SavedCanvasObject[], width: number, height: number) => Promise<void>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.renderEditorOrPreview = void 0;
40
+ var imageUtils_1 = require("./imageUtils");
41
+ var psdConverter_1 = require("./psdConverter");
42
+ // Simplify loadTemplateImage to just load the template without variations
43
+ var renderEditorOrPreview = function (fabricCanvas, template, variations, savedObjects, width, height) { return __awaiter(void 0, void 0, void 0, function () {
44
+ var file, imageUrl, isPsd, error_1;
45
+ var _a;
46
+ return __generator(this, function (_b) {
47
+ switch (_b.label) {
48
+ case 0:
49
+ // Check if we have a valid image URL
50
+ if (!((_a = template.file) === null || _a === void 0 ? void 0 : _a.viewUrl)) {
51
+ return [2 /*return*/, Promise.resolve()];
52
+ }
53
+ file = template.file;
54
+ imageUrl = file.viewUrl;
55
+ isPsd = imageUrl.toLowerCase().endsWith('.psd')
56
+ || (file.mimetype && file.mimetype.includes('photoshop'));
57
+ if (!isPsd) return [3 /*break*/, 5];
58
+ _b.label = 1;
59
+ case 1:
60
+ _b.trys.push([1, 3, , 5]);
61
+ // Process the PSD file
62
+ return [4 /*yield*/, (0, psdConverter_1.loadPsdOntoCanvas)(fabricCanvas, imageUrl, variations, savedObjects, width, height)];
63
+ case 2:
64
+ // Process the PSD file
65
+ _b.sent();
66
+ return [2 /*return*/, Promise.resolve()];
67
+ case 3:
68
+ error_1 = _b.sent();
69
+ // If PSD processing fails, fall back to our regular image loading
70
+ return [4 /*yield*/, (0, imageUtils_1.loadRegularImagePromise)(fabricCanvas, template, width, height)];
71
+ case 4:
72
+ // If PSD processing fails, fall back to our regular image loading
73
+ _b.sent();
74
+ return [2 /*return*/, Promise.resolve()];
75
+ case 5:
76
+ // For non-PSD files, use the regular image loading approach
77
+ return [4 /*yield*/, (0, imageUtils_1.loadRegularImagePromise)(fabricCanvas, template, width, height)];
78
+ case 6:
79
+ // For non-PSD files, use the regular image loading approach
80
+ _b.sent();
81
+ return [2 /*return*/, Promise.resolve()];
82
+ }
83
+ });
84
+ }); };
85
+ exports.renderEditorOrPreview = renderEditorOrPreview;
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@merchi/product-editor",
3
+ "version": "0.1.9",
4
+ "description": "A React component for editing product images using Fabric.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc && copyfiles -u 2 src/styles/*.css dist/styles/",
9
+ "test": "jest",
10
+ "lint": "eslint src/**/*.{ts,tsx}",
11
+ "prepare": "npm run build"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/merchisdk/merchi_ts.git",
16
+ "directory": "packages/product-editor"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "keywords": [
22
+ "react",
23
+ "fabric.js",
24
+ "product-editor",
25
+ "image-editor"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "peerDependencies": {
30
+ "react": ">=16.8.0",
31
+ "react-dom": ">=16.8.0"
32
+ },
33
+ "dependencies": {
34
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
35
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
36
+ "@fortawesome/react-fontawesome": "^0.2.2",
37
+ "@radix-ui/react-icons": "^1.3.2",
38
+ "@types/react-color": "^3.0.13",
39
+ "@types/react-select": "^5.0.1",
40
+ "ag-psd": "^28.0.1",
41
+ "fabric": "^5.5.1",
42
+ "framer-motion": "^12.6.3",
43
+ "grommet-icons": "^4.12.4",
44
+ "lodash": "^4.17.21",
45
+ "psd.js": "^3.9.1",
46
+ "react-color": "^2.19.3",
47
+ "react-hook-form": "^7.56.1",
48
+ "react-icons": "^5.5.0",
49
+ "react-select": "^5.10.1",
50
+ "uuid": "^11.1.0"
51
+ },
52
+ "devDependencies": {
53
+ "@testing-library/jest-dom": "^5.16.0",
54
+ "@testing-library/react": "^13.0.0",
55
+ "@types/fabric": "^5.3.10",
56
+ "@types/react": "^19.0.0",
57
+ "@types/react-dom": "^19.0.0",
58
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
59
+ "@typescript-eslint/parser": "^5.0.0",
60
+ "copyfiles": "^2.4.1",
61
+ "eslint": "^8.0.0",
62
+ "jest": "^29.0.0",
63
+ "ts-jest": "^29.0.0",
64
+ "typescript": "^4.9.0"
65
+ }
66
+ }
@@ -0,0 +1,49 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { TextAlignLeft, TextAlignCenter, TextAlignRight } from 'grommet-icons';
3
+ import { useProductEditor } from '../context/ProductEditorContext';
4
+ import '../styles/TextToolbar.css';
5
+
6
+ export const AlignMenu: React.FC = () => {
7
+ const { selectedTextObject, updateSelectedText } = useProductEditor();
8
+ const [showMenu, setShowMenu] = useState(false);
9
+ const ref = useRef<HTMLDivElement>(null);
10
+
11
+ useEffect(() => {
12
+ const handleClickOutside = (e: MouseEvent) => {
13
+ if (ref.current && !ref.current.contains(e.target as Node)) {
14
+ setShowMenu(false);
15
+ }
16
+ };
17
+ document.addEventListener('mousedown', handleClickOutside);
18
+ return () => document.removeEventListener('mousedown', handleClickOutside);
19
+ }, []);
20
+
21
+ if (!selectedTextObject || !updateSelectedText) return null;
22
+
23
+
24
+ const handleAlign = (align: 'left' | 'center' | 'right') => {
25
+ updateSelectedText({ textAlign: align });
26
+ setShowMenu(false);
27
+ };
28
+
29
+ return (
30
+ <div className="align-group" ref={ref}>
31
+ <button
32
+ type="button"
33
+ className="toolbar-button align-trigger"
34
+ onClick={() => setShowMenu(prev => !prev)}
35
+ title="Text Align"
36
+ >
37
+ {selectedTextObject.textAlign === 'center' ? <TextAlignCenter /> :
38
+ selectedTextObject.textAlign === 'right' ? <TextAlignRight /> : <TextAlignLeft />}
39
+ </button>
40
+ {showMenu && (
41
+ <div className="align-popover">
42
+ <button type="button" className={`toolbar-button ${selectedTextObject.textAlign === 'left' ? 'active' : ''}`} onClick={() => handleAlign('left')}><TextAlignLeft /></button>
43
+ <button type="button" className={`toolbar-button ${selectedTextObject.textAlign === 'center' ? 'active' : ''}`} onClick={() => handleAlign('center')}><TextAlignCenter /></button>
44
+ <button type="button" className={`toolbar-button ${selectedTextObject.textAlign === 'right' ? 'active' : ''}`} onClick={() => handleAlign('right')}><TextAlignRight /></button>
45
+ </div>
46
+ )}
47
+ </div>
48
+ );
49
+ };