@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.
- package/.turbo/turbo-build.log +1 -0
- package/README.md +174 -0
- package/dist/components/AlignMenu.d.ts +3 -0
- package/dist/components/AlignMenu.js +61 -0
- package/dist/components/FloatingToolbar.d.ts +4 -0
- package/dist/components/FloatingToolbar.js +97 -0
- package/dist/components/FormatButtons.d.ts +2 -0
- package/dist/components/FormatButtons.js +22 -0
- package/dist/components/ImageNavButton.d.ts +7 -0
- package/dist/components/ImageNavButton.js +14 -0
- package/dist/components/ImageZoomModal.d.ts +12 -0
- package/dist/components/ImageZoomModal.js +129 -0
- package/dist/components/LayerPanel.d.ts +4 -0
- package/dist/components/LayerPanel.js +223 -0
- package/dist/components/LoadingOverlay.d.ts +9 -0
- package/dist/components/LoadingOverlay.js +17 -0
- package/dist/components/ProductEditor.d.ts +4 -0
- package/dist/components/ProductEditor.js +67 -0
- package/dist/components/ProductImageGallery.d.ts +6 -0
- package/dist/components/ProductImageGallery.js +148 -0
- package/dist/components/ProductPreviews.d.ts +7 -0
- package/dist/components/ProductPreviews.js +281 -0
- package/dist/components/TextToolbar.d.ts +4 -0
- package/dist/components/TextToolbar.js +117 -0
- package/dist/components/Toolbar.d.ts +3 -0
- package/dist/components/Toolbar.js +47 -0
- package/dist/config/colorConfig.d.ts +1 -0
- package/dist/config/colorConfig.js +10 -0
- package/dist/config/fontConfig.d.ts +5 -0
- package/dist/config/fontConfig.js +21 -0
- package/dist/context/ProductEditorContext.d.ts +60 -0
- package/dist/context/ProductEditorContext.js +743 -0
- package/dist/hooks/useSwipeNavigate.d.ts +11 -0
- package/dist/hooks/useSwipeNavigate.js +35 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +30 -0
- package/dist/styles/BottomPreviewDisplay.css +65 -0
- package/dist/styles/FloatingToolbar.css +41 -0
- package/dist/styles/ImageGallery.css +147 -0
- package/dist/styles/ImageNavButton.css +35 -0
- package/dist/styles/ImageZoomModal.css +101 -0
- package/dist/styles/LayerPanel.css +179 -0
- package/dist/styles/LoadingOverlay.css +48 -0
- package/dist/styles/ProductEditor.css +192 -0
- package/dist/styles/TextToolbar.css +327 -0
- package/dist/styles/Toolbar.css +125 -0
- package/dist/styles/index.css +5 -0
- package/dist/styles/textToolbarSelectStyles.d.ts +3 -0
- package/dist/styles/textToolbarSelectStyles.js +30 -0
- package/dist/types.d.ts +228 -0
- package/dist/types.js +17 -0
- package/dist/utils/canvasAddText.d.ts +11 -0
- package/dist/utils/canvasAddText.js +66 -0
- package/dist/utils/canvasUtils.d.ts +37 -0
- package/dist/utils/canvasUtils.js +342 -0
- package/dist/utils/deviceUtils.d.ts +5 -0
- package/dist/utils/deviceUtils.js +33 -0
- package/dist/utils/draftTemplateUtils.d.ts +23 -0
- package/dist/utils/draftTemplateUtils.js +66 -0
- package/dist/utils/grid.d.ts +32 -0
- package/dist/utils/grid.js +151 -0
- package/dist/utils/imageUtils.d.ts +12 -0
- package/dist/utils/imageUtils.js +178 -0
- package/dist/utils/job.d.ts +40 -0
- package/dist/utils/job.js +115 -0
- package/dist/utils/keyboard.d.ts +1 -0
- package/dist/utils/keyboard.js +26 -0
- package/dist/utils/previewUtils.d.ts +9 -0
- package/dist/utils/previewUtils.js +39 -0
- package/dist/utils/psdConverter.d.ts +40 -0
- package/dist/utils/psdConverter.js +516 -0
- package/dist/utils/psdRenderUtils.d.ts +12 -0
- package/dist/utils/psdRenderUtils.js +465 -0
- package/dist/utils/renderUtils.d.ts +2 -0
- package/dist/utils/renderUtils.js +85 -0
- package/package.json +66 -0
- package/src/components/AlignMenu.tsx +49 -0
- package/src/components/FloatingToolbar.tsx +83 -0
- package/src/components/FormatButtons.tsx +31 -0
- package/src/components/ImageNavButton.tsx +23 -0
- package/src/components/ImageZoomModal.tsx +163 -0
- package/src/components/LayerPanel.tsx +256 -0
- package/src/components/LoadingOverlay.tsx +27 -0
- package/src/components/ProductEditor.tsx +104 -0
- package/src/components/ProductImageGallery.tsx +181 -0
- package/src/components/ProductPreviews.tsx +243 -0
- package/src/components/TextToolbar.tsx +164 -0
- package/src/components/Toolbar.tsx +86 -0
- package/src/config/colorConfig.ts +7 -0
- package/src/config/fontConfig.ts +23 -0
- package/src/context/ProductEditorContext.tsx +829 -0
- package/src/hooks/useSwipeNavigate.ts +40 -0
- package/src/index.ts +6 -0
- package/src/styles/BottomPreviewDisplay.css +65 -0
- package/src/styles/FloatingToolbar.css +41 -0
- package/src/styles/ImageGallery.css +147 -0
- package/src/styles/ImageNavButton.css +35 -0
- package/src/styles/ImageZoomModal.css +101 -0
- package/src/styles/LayerPanel.css +179 -0
- package/src/styles/LoadingOverlay.css +48 -0
- package/src/styles/ProductEditor.css +192 -0
- package/src/styles/TextToolbar.css +327 -0
- package/src/styles/Toolbar.css +125 -0
- package/src/styles/index.css +5 -0
- package/src/styles/textToolbarSelectStyles.ts +60 -0
- package/src/types/ag-psd/index.d.ts +1 -0
- package/src/types/fabric.d.ts +14 -0
- package/src/types/lodash/index.d.ts +1 -0
- package/src/types/psd.d.ts +53 -0
- package/src/types.ts +247 -0
- package/src/utils/canvasAddText.ts +72 -0
- package/src/utils/canvasUtils.ts +406 -0
- package/src/utils/deviceUtils.ts +31 -0
- package/src/utils/draftTemplateUtils.ts +70 -0
- package/src/utils/grid.ts +154 -0
- package/src/utils/imageUtils.ts +218 -0
- package/src/utils/job.ts +128 -0
- package/src/utils/keyboard.ts +28 -0
- package/src/utils/previewUtils.ts +43 -0
- package/src/utils/psdConverter.ts +595 -0
- package/src/utils/psdRenderUtils.ts +453 -0
- package/src/utils/renderUtils.ts +44 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { fabric } from 'fabric';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds an editable text object to the canvas
|
|
5
|
+
* @param canvas - The Fabric.js canvas instance
|
|
6
|
+
* @param width - The width of the canvas
|
|
7
|
+
* @param height - The height of the canvas
|
|
8
|
+
* @param defaultText - Optional default text (defaults to "Text")
|
|
9
|
+
* @param fontSize - Optional font size (defaults to 24)
|
|
10
|
+
* @param fontFamily - Optional font family (defaults to Arial)
|
|
11
|
+
*/
|
|
12
|
+
export const addTextToCanvas = (
|
|
13
|
+
canvas: fabric.Canvas,
|
|
14
|
+
width: number,
|
|
15
|
+
height: number,
|
|
16
|
+
defaultText: string = 'Text',
|
|
17
|
+
fontSize: number = 24,
|
|
18
|
+
fontFamily: string = 'Nunito'
|
|
19
|
+
) => {
|
|
20
|
+
if (!canvas) return null;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Safe check if canvas is still valid
|
|
24
|
+
if (!canvas.getElement() || !canvas.getElement().parentNode) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Create a new text object with minimal options
|
|
29
|
+
const text = new fabric.IText(
|
|
30
|
+
defaultText,
|
|
31
|
+
{
|
|
32
|
+
left: width / 2,
|
|
33
|
+
top: height / 2,
|
|
34
|
+
fontFamily: fontFamily,
|
|
35
|
+
fontSize: fontSize,
|
|
36
|
+
fill: '#000000',
|
|
37
|
+
originX: 'center',
|
|
38
|
+
originY: 'center',
|
|
39
|
+
selectable: true,
|
|
40
|
+
editable: false
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Add the text to the canvas
|
|
45
|
+
canvas.add(text);
|
|
46
|
+
|
|
47
|
+
// Ensure the text is rendered
|
|
48
|
+
canvas.renderAll();
|
|
49
|
+
|
|
50
|
+
// Make the text active - this is critical
|
|
51
|
+
const setActiveTextObject = () => {
|
|
52
|
+
try {
|
|
53
|
+
// Check if canvas is still valid
|
|
54
|
+
if (canvas && canvas.getElement() && canvas.getElement().parentNode) {
|
|
55
|
+
canvas.setActiveObject(text);
|
|
56
|
+
canvas.renderAll();
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('Error setting active text object:', err);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Initial selection
|
|
64
|
+
setActiveTextObject();
|
|
65
|
+
|
|
66
|
+
// Delayed selection to ensure text becomes active
|
|
67
|
+
setTimeout(setActiveTextObject, 50);
|
|
68
|
+
|
|
69
|
+
return text;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error adding text to canvas:', error);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Renders the entire canvas as an image without displaying grid elements
|
|
78
|
+
*
|
|
79
|
+
* @param canvas - The Fabric.js canvas instance
|
|
80
|
+
* @param options - Optional rendering options
|
|
81
|
+
* @returns A promise that resolves to the dataURL of the canvas without grid
|
|
82
|
+
*/
|
|
83
|
+
export const renderCanvasWithoutGrid = (
|
|
84
|
+
canvas: fabric.Canvas,
|
|
85
|
+
options?: {
|
|
86
|
+
format?: string;
|
|
87
|
+
quality?: number;
|
|
88
|
+
multiplier?: number;
|
|
89
|
+
backgroundColor?: string | null;
|
|
90
|
+
}
|
|
91
|
+
): Promise<string | null> => {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
if (!canvas) {
|
|
94
|
+
console.error('Canvas is null or undefined');
|
|
95
|
+
resolve(null);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Get all objects on the canvas
|
|
101
|
+
const objects = canvas.getObjects();
|
|
102
|
+
|
|
103
|
+
// Store original visibility of each object
|
|
104
|
+
const originalVisibilities = objects.map(obj => ({
|
|
105
|
+
object: obj,
|
|
106
|
+
visible: obj.visible
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// Hide grid elements - typically grid elements might have a specific ID or class
|
|
110
|
+
// You may need to adjust this logic based on how grid elements are identified
|
|
111
|
+
objects.forEach(obj => {
|
|
112
|
+
// Hide objects that represent grid elements
|
|
113
|
+
// This assumes grid elements have a property like 'type' or 'id' that identifies them
|
|
114
|
+
if (
|
|
115
|
+
(obj as any).id?.includes('grid') ||
|
|
116
|
+
(obj as any).type === 'grid' ||
|
|
117
|
+
(obj as any).isGridElement ||
|
|
118
|
+
// Add more comprehensive grid detection
|
|
119
|
+
(obj as any).data?.isGrid ||
|
|
120
|
+
(obj as any).name?.includes('grid') ||
|
|
121
|
+
(obj as any).className?.includes('grid') ||
|
|
122
|
+
// Check for common grid-related properties
|
|
123
|
+
(obj as any).strokeDashArray || // Dashed lines are often used for grids
|
|
124
|
+
// Check for line objects with evenly spaced positions (typical grid pattern)
|
|
125
|
+
((obj as any).type === 'line' && ((obj as any).x1 === (obj as any).x2 || (obj as any).y1 === (obj as any).y2)) ||
|
|
126
|
+
// Check for objects tagged with grid-related classes
|
|
127
|
+
(obj as any).classes?.includes('grid') ||
|
|
128
|
+
// Check for objects with certain grid styling
|
|
129
|
+
((obj as any).stroke &&
|
|
130
|
+
((obj as any).strokeWidth === 0.5 || (obj as any).strokeWidth === 1) &&
|
|
131
|
+
((obj as any).stroke === '#ddd' || (obj as any).stroke === '#eee' || (obj as any).stroke === 'rgba(0,0,0,0.1)'))
|
|
132
|
+
) {
|
|
133
|
+
obj.visible = false;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Save the original selection state
|
|
138
|
+
const originalSelectionBorder = canvas.selectionBorderColor;
|
|
139
|
+
const originalSelectionColor = canvas.selectionColor;
|
|
140
|
+
const originalActiveObject = canvas.getActiveObject();
|
|
141
|
+
|
|
142
|
+
// Temporarily disable selection indicators
|
|
143
|
+
canvas.selectionBorderColor = 'transparent';
|
|
144
|
+
canvas.selectionColor = 'transparent';
|
|
145
|
+
canvas.discardActiveObject();
|
|
146
|
+
|
|
147
|
+
// Apply any background color if specified
|
|
148
|
+
const originalBackgroundColor = canvas.backgroundColor;
|
|
149
|
+
if (options?.backgroundColor !== undefined) {
|
|
150
|
+
canvas.backgroundColor = options.backgroundColor || 'transparent';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Render the canvas
|
|
154
|
+
canvas.renderAll();
|
|
155
|
+
|
|
156
|
+
// Get rendering options
|
|
157
|
+
const format = options?.format || 'png';
|
|
158
|
+
const quality = options?.quality || 1;
|
|
159
|
+
const multiplier = options?.multiplier || 1;
|
|
160
|
+
|
|
161
|
+
// Create a data URL from the canvas
|
|
162
|
+
try {
|
|
163
|
+
const dataUrl = canvas.toDataURL({
|
|
164
|
+
format: format,
|
|
165
|
+
quality: quality,
|
|
166
|
+
multiplier: multiplier
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Restore the original state
|
|
170
|
+
objects.forEach((obj, index) => {
|
|
171
|
+
obj.visible = originalVisibilities[index].visible;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Restore selection state
|
|
175
|
+
canvas.selectionBorderColor = originalSelectionBorder;
|
|
176
|
+
canvas.selectionColor = originalSelectionColor;
|
|
177
|
+
if (originalActiveObject) {
|
|
178
|
+
canvas.setActiveObject(originalActiveObject);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Restore background color
|
|
182
|
+
canvas.backgroundColor = originalBackgroundColor;
|
|
183
|
+
|
|
184
|
+
// Render with original state
|
|
185
|
+
canvas.renderAll();
|
|
186
|
+
|
|
187
|
+
resolve(dataUrl);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('Error generating data URL:', error);
|
|
190
|
+
resolve(null);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Error rendering canvas without grid:', error);
|
|
194
|
+
resolve(null);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Renders an image containing only what's visible inside a clipPath on the canvas
|
|
201
|
+
*
|
|
202
|
+
* @param canvas - The Fabric.js canvas instance
|
|
203
|
+
* @param options - Optional rendering options
|
|
204
|
+
* @returns A promise that resolves to the dataURL of the contents inside the clipPath, or null if no clipped object is found
|
|
205
|
+
*/
|
|
206
|
+
export const renderClippedImage = (
|
|
207
|
+
canvas: fabric.Canvas,
|
|
208
|
+
options?: {
|
|
209
|
+
format?: string;
|
|
210
|
+
quality?: number;
|
|
211
|
+
multiplier?: number;
|
|
212
|
+
backgroundColor?: string;
|
|
213
|
+
}
|
|
214
|
+
): Promise<string | null> => {
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
if (!canvas) {
|
|
217
|
+
console.error('Canvas is null or undefined');
|
|
218
|
+
resolve(null);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Find objects with clipPath
|
|
224
|
+
const objects = canvas.getObjects();
|
|
225
|
+
let targetObject: fabric.Object | null = null;
|
|
226
|
+
|
|
227
|
+
targetObject = objects.find((obj: any) => obj.id === 'design-boundary-rect') || null;
|
|
228
|
+
|
|
229
|
+
if (!targetObject) {
|
|
230
|
+
console.warn('No boundary object found');
|
|
231
|
+
resolve(null);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Get bounds of the object with clipPath
|
|
236
|
+
const objBounds = targetObject.getBoundingRect();
|
|
237
|
+
|
|
238
|
+
// Create a new canvas element
|
|
239
|
+
const dpr = window.devicePixelRatio || 1;
|
|
240
|
+
|
|
241
|
+
const tempCanvasEl = document.createElement('canvas');
|
|
242
|
+
tempCanvasEl.width = objBounds.width * dpr;
|
|
243
|
+
tempCanvasEl.height = objBounds.height * dpr;
|
|
244
|
+
|
|
245
|
+
tempCanvasEl.style.width = `${objBounds.width}px`;
|
|
246
|
+
tempCanvasEl.style.height = `${objBounds.height}px`;
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// Explicitly set alpha to true to support transparency
|
|
250
|
+
const tempCtx = tempCanvasEl.getContext('2d', { alpha: true });
|
|
251
|
+
tempCtx && tempCtx.scale(dpr, dpr);
|
|
252
|
+
|
|
253
|
+
if (!tempCtx) {
|
|
254
|
+
console.error('Could not get canvas context');
|
|
255
|
+
resolve(null);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Clear the canvas to ensure transparency
|
|
260
|
+
tempCtx.clearRect(0, 0, tempCanvasEl.width, tempCanvasEl.height);
|
|
261
|
+
|
|
262
|
+
// Set background color if provided, otherwise keep transparent
|
|
263
|
+
if (options?.backgroundColor) {
|
|
264
|
+
tempCtx.fillStyle = options.backgroundColor;
|
|
265
|
+
tempCtx.fillRect(0, 0, tempCanvasEl.width, tempCanvasEl.height);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Save current canvas state
|
|
269
|
+
const originalZoom = canvas.getZoom();
|
|
270
|
+
const originalViewportTransform = canvas.viewportTransform ? [...canvas.viewportTransform] : [1, 0, 0, 1, 0, 0];
|
|
271
|
+
|
|
272
|
+
// Save the original background color of the canvas
|
|
273
|
+
const originalBackgroundColor = canvas.backgroundColor;
|
|
274
|
+
|
|
275
|
+
// Temporarily set canvas background to transparent
|
|
276
|
+
canvas.backgroundColor = 'transparent';
|
|
277
|
+
|
|
278
|
+
// Store original object positions
|
|
279
|
+
const originalPositions = objects.map(obj => ({
|
|
280
|
+
object: obj,
|
|
281
|
+
left: obj.left,
|
|
282
|
+
top: obj.top,
|
|
283
|
+
scaleX: obj.scaleX,
|
|
284
|
+
scaleY: obj.scaleY
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
// Temporarily modify the canvas view to focus on our object
|
|
288
|
+
canvas.setZoom(1);
|
|
289
|
+
canvas.setViewportTransform([1, 0, 0, 1, -objBounds.left, -objBounds.top]);
|
|
290
|
+
|
|
291
|
+
// Temporarily hide all objects except our target and its clipped content
|
|
292
|
+
const originalVisibilities = objects.map(obj => ({ object: obj, visible: obj.visible }));
|
|
293
|
+
objects.forEach(obj => {
|
|
294
|
+
if (!(obj as any).fieldId) {
|
|
295
|
+
obj.visible = false;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Temporarily disable selection borders at canvas level
|
|
300
|
+
const originalSelectionBorder = canvas.selectionBorderColor;
|
|
301
|
+
const originalSelectionColor = canvas.selectionColor;
|
|
302
|
+
canvas.selectionBorderColor = 'transparent';
|
|
303
|
+
canvas.selectionColor = 'transparent';
|
|
304
|
+
|
|
305
|
+
// Temporarily disable selection borders at object level
|
|
306
|
+
const originalObjectStates = objects.map(obj => ({
|
|
307
|
+
object: obj,
|
|
308
|
+
borderColor: obj.borderColor,
|
|
309
|
+
cornerColor: obj.cornerColor,
|
|
310
|
+
cornerStrokeColor: obj.cornerStrokeColor,
|
|
311
|
+
transparentCorners: obj.transparentCorners,
|
|
312
|
+
hasControls: obj.hasControls,
|
|
313
|
+
hasBorders: obj.hasBorders
|
|
314
|
+
}));
|
|
315
|
+
|
|
316
|
+
objects.forEach(obj => {
|
|
317
|
+
obj.borderColor = 'transparent';
|
|
318
|
+
obj.cornerColor = 'transparent';
|
|
319
|
+
obj.cornerStrokeColor = 'transparent';
|
|
320
|
+
obj.transparentCorners = true;
|
|
321
|
+
obj.hasControls = false;
|
|
322
|
+
obj.hasBorders = false;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Render just the isolated object
|
|
326
|
+
canvas.renderAll();
|
|
327
|
+
|
|
328
|
+
// Draw the current canvas view to our temp canvas
|
|
329
|
+
tempCtx.drawImage(
|
|
330
|
+
canvas.getElement(),
|
|
331
|
+
0, 0, objBounds.width * dpr, objBounds.height * dpr,
|
|
332
|
+
0, 0, objBounds.width, objBounds.height
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
// Restore the original object positions
|
|
336
|
+
originalPositions.forEach(item => {
|
|
337
|
+
item.object.left = item.left;
|
|
338
|
+
item.object.top = item.top;
|
|
339
|
+
item.object.scaleX = item.scaleX;
|
|
340
|
+
item.object.scaleY = item.scaleY;
|
|
341
|
+
item.object.setCoords();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Restore the original canvas state
|
|
345
|
+
canvas.setZoom(originalZoom);
|
|
346
|
+
canvas.setViewportTransform(originalViewportTransform);
|
|
347
|
+
canvas.backgroundColor = originalBackgroundColor;
|
|
348
|
+
originalVisibilities.forEach(item => {
|
|
349
|
+
item.object.visible = item.visible;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Restore object-level selection borders
|
|
353
|
+
originalObjectStates.forEach(state => {
|
|
354
|
+
state.object.borderColor = state.borderColor;
|
|
355
|
+
state.object.cornerColor = state.cornerColor;
|
|
356
|
+
state.object.cornerStrokeColor = state.cornerStrokeColor;
|
|
357
|
+
state.object.transparentCorners = state.transparentCorners;
|
|
358
|
+
state.object.hasControls = state.hasControls;
|
|
359
|
+
state.object.hasBorders = state.hasBorders;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Restore canvas-level selection borders
|
|
363
|
+
canvas.selectionBorderColor = originalSelectionBorder;
|
|
364
|
+
canvas.selectionColor = originalSelectionColor;
|
|
365
|
+
canvas.renderAll();
|
|
366
|
+
|
|
367
|
+
// Get the data URL from the temp canvas
|
|
368
|
+
const format = options?.format || 'png';
|
|
369
|
+
const quality = options?.quality || 1;
|
|
370
|
+
|
|
371
|
+
// Create a new canvas for final rendering with proper transparency
|
|
372
|
+
const finalCanvas = document.createElement('canvas');
|
|
373
|
+
finalCanvas.width = tempCanvasEl.width;
|
|
374
|
+
finalCanvas.height = tempCanvasEl.height;
|
|
375
|
+
const finalCtx = finalCanvas.getContext('2d', { alpha: true });
|
|
376
|
+
|
|
377
|
+
finalCtx && finalCtx.scale(1, 1);
|
|
378
|
+
|
|
379
|
+
if (!finalCtx) {
|
|
380
|
+
console.error('Could not get final canvas context');
|
|
381
|
+
resolve(null);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Clear the final canvas to ensure transparency
|
|
386
|
+
finalCtx.clearRect(0, 0, finalCanvas.width, finalCanvas.height);
|
|
387
|
+
|
|
388
|
+
// Draw the temp canvas content to the final canvas
|
|
389
|
+
finalCtx.drawImage(tempCanvasEl, 0, 0);
|
|
390
|
+
|
|
391
|
+
// Try to get the data URL from the final canvas
|
|
392
|
+
try {
|
|
393
|
+
// Ensure PNG format for transparency support
|
|
394
|
+
const dataUrl = finalCanvas.toDataURL(`image/${format === 'jpg' ? 'png' : format}`, quality);
|
|
395
|
+
resolve(dataUrl);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error('Error getting data URL:', error);
|
|
398
|
+
resolve(null);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('Error rendering clipped image:', error);
|
|
403
|
+
resolve(null);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const getDeviceAdjustedDimensions = (defaultWidth = 800, defaultHeight = 600) => {
|
|
2
|
+
if (typeof window === 'undefined') {
|
|
3
|
+
return {
|
|
4
|
+
isMobile: false,
|
|
5
|
+
width: defaultWidth,
|
|
6
|
+
height: defaultHeight
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const isMobile = window.innerWidth < 480;
|
|
11
|
+
|
|
12
|
+
// Use default dimensions for desktop
|
|
13
|
+
if (!isMobile) {
|
|
14
|
+
return {
|
|
15
|
+
isMobile: false,
|
|
16
|
+
width: defaultWidth,
|
|
17
|
+
height: defaultHeight
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Calculate mobile dimensions
|
|
22
|
+
const mobileWidth = Math.min(window.innerWidth * 0.9 - 32, 400);
|
|
23
|
+
const aspectRatio = defaultWidth / defaultHeight;
|
|
24
|
+
const mobileHeight = mobileWidth / aspectRatio;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
isMobile: true,
|
|
28
|
+
width: mobileWidth,
|
|
29
|
+
height: mobileHeight
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DraftPreview, DraftTemplateData, RenderedDraftPreview, DraftPreviewLayer } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compares two arrays of draft templates to determine if they've changed significantly
|
|
5
|
+
* This helps prevent unnecessary re-renders and infinite loops
|
|
6
|
+
*
|
|
7
|
+
* @param oldTemplates The previous draft templates array
|
|
8
|
+
* @param newTemplates The new draft templates array
|
|
9
|
+
* @returns True if templates have changed, false if they're effectively the same
|
|
10
|
+
*/
|
|
11
|
+
export function haveDraftTemplatesChanged(
|
|
12
|
+
oldTemplates: Array<{ template: any; variationObjects: any[] }>,
|
|
13
|
+
newTemplates: Array<{ template: any; variationObjects: any[] }>
|
|
14
|
+
): boolean {
|
|
15
|
+
// Quick length check
|
|
16
|
+
if (!oldTemplates || !newTemplates) return true;
|
|
17
|
+
if (oldTemplates.length !== newTemplates.length) return true;
|
|
18
|
+
|
|
19
|
+
// Check if template IDs are the same and in the same order
|
|
20
|
+
const oldIds = oldTemplates.map(dt => dt.template.id).join(',');
|
|
21
|
+
const newIds = newTemplates.map(dt => dt.template.id).join(',');
|
|
22
|
+
|
|
23
|
+
if (oldIds !== newIds) return true;
|
|
24
|
+
|
|
25
|
+
// Deep check each template for significant changes
|
|
26
|
+
for (let i = 0; i < oldTemplates.length; i++) {
|
|
27
|
+
const oldTemplate = oldTemplates[i];
|
|
28
|
+
const newTemplate = newTemplates[i];
|
|
29
|
+
|
|
30
|
+
// Check for template property changes
|
|
31
|
+
if (oldTemplate.template.id !== newTemplate.template.id) return true;
|
|
32
|
+
if (oldTemplate.template.name !== newTemplate.template.name) return true;
|
|
33
|
+
if (oldTemplate.template.width !== newTemplate.template.width) return true;
|
|
34
|
+
if (oldTemplate.template.height !== newTemplate.template.height) return true;
|
|
35
|
+
|
|
36
|
+
// Check variation objects count
|
|
37
|
+
if (oldTemplate.variationObjects.length !== newTemplate.variationObjects.length) return true;
|
|
38
|
+
|
|
39
|
+
// We could do more detailed checking of variation objects here,
|
|
40
|
+
// but for performance we'll assume if counts are the same and template properties
|
|
41
|
+
// are the same, the variations probably haven't changed enough to warrant a full re-render
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Templates are effectively the same
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function mapPreviewsWithRendered(
|
|
49
|
+
draftTemplates: DraftTemplateData[],
|
|
50
|
+
draftPreviews: DraftPreview[],
|
|
51
|
+
renderedDraftPreviews: RenderedDraftPreview[]
|
|
52
|
+
): { draftPreview: DraftPreview; draftPreviewLayers: { layerName: string | undefined; renderedLayer: RenderedDraftPreview | null }[] }[] {
|
|
53
|
+
return draftPreviews.map((draftPreview: DraftPreview) => {
|
|
54
|
+
// Map each draftTemplate to its associated layers and rendered layers
|
|
55
|
+
const draftPreviewLayers = draftTemplates.flatMap((dTD: DraftTemplateData) => {
|
|
56
|
+
const layers = dTD?.template?.draftPreviewLayers || [];
|
|
57
|
+
return layers
|
|
58
|
+
.filter((dPL: DraftPreviewLayer) => dPL?.draftPreview?.id === draftPreview.id)
|
|
59
|
+
.map(layer => {
|
|
60
|
+
const renderedLayer = renderedDraftPreviews.find(rDR => rDR?.templateId === dTD?.template?.id);
|
|
61
|
+
return { layerName: layer?.layerName, renderedLayer: renderedLayer || null };
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
draftPreview,
|
|
67
|
+
draftPreviewLayers,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { fabric } from 'fabric';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param fabricCanvas
|
|
5
|
+
* @param width
|
|
6
|
+
* @param height
|
|
7
|
+
* @param gridSize
|
|
8
|
+
* @param lineColor
|
|
9
|
+
* @param enabled
|
|
10
|
+
*/
|
|
11
|
+
export const drawGrid = (
|
|
12
|
+
fabricCanvas: fabric.Canvas,
|
|
13
|
+
width: number,
|
|
14
|
+
height: number,
|
|
15
|
+
gridSize = 20,
|
|
16
|
+
lineColor = '#e0e0e0',
|
|
17
|
+
enabled = true
|
|
18
|
+
) => {
|
|
19
|
+
// First, make sure fabricCanvas is not null
|
|
20
|
+
if (!fabricCanvas) {
|
|
21
|
+
console.warn('drawGrid: fabricCanvas is null or undefined');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Make sure the canvas element exists and is valid
|
|
26
|
+
try {
|
|
27
|
+
// This will throw an error if the canvas element is no longer in the DOM
|
|
28
|
+
if (!fabricCanvas.getElement() || !fabricCanvas.getElement().parentNode) {
|
|
29
|
+
console.warn('drawGrid: Canvas element is not in the DOM');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn('drawGrid: Error checking canvas element:', e);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// remove all existing grid lines
|
|
38
|
+
const gridLines = fabricCanvas.getObjects().filter(obj => obj.data?.isGrid);
|
|
39
|
+
gridLines.forEach(line => fabricCanvas.remove(line));
|
|
40
|
+
|
|
41
|
+
if (!enabled) return;
|
|
42
|
+
|
|
43
|
+
// draw vertical lines
|
|
44
|
+
for (let i = 0; i <= width; i += gridSize) {
|
|
45
|
+
const line = new fabric.Line([i, 0, i, height], {
|
|
46
|
+
stroke: lineColor,
|
|
47
|
+
strokeWidth: 0.5,
|
|
48
|
+
opacity: 0.3,
|
|
49
|
+
selectable: false,
|
|
50
|
+
evented: false,
|
|
51
|
+
data: { isGrid: true }
|
|
52
|
+
});
|
|
53
|
+
fabricCanvas.add(line);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// draw horizontal lines
|
|
57
|
+
for (let i = 0; i <= height; i += gridSize) {
|
|
58
|
+
const line = new fabric.Line([0, i, width, i], {
|
|
59
|
+
stroke: lineColor,
|
|
60
|
+
strokeWidth: 0.5,
|
|
61
|
+
opacity: 0.3,
|
|
62
|
+
selectable: false,
|
|
63
|
+
evented: false,
|
|
64
|
+
data: { isGrid: true }
|
|
65
|
+
});
|
|
66
|
+
fabricCanvas.add(line);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
fabricCanvas.renderAll();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error('drawGrid: Error rendering grid:', e);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* save the grid state on the canvas
|
|
78
|
+
* @param fabricCanvas
|
|
79
|
+
*/
|
|
80
|
+
export const saveGridState = (fabricCanvas: fabric.Canvas) => {
|
|
81
|
+
if (!fabricCanvas) {
|
|
82
|
+
console.warn('saveGridState: fabricCanvas is null or undefined');
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const objs = fabricCanvas.getObjects().filter(obj => obj);
|
|
88
|
+
return objs;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.warn('saveGridState: Error getting grid objects:', e);
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* bring grid lines to front of the canvas
|
|
97
|
+
* @param fabricCanvas
|
|
98
|
+
* @param gridLines
|
|
99
|
+
*/
|
|
100
|
+
export const bringGridToFront = (fabricCanvas: fabric.Canvas, gridLines: fabric.Object[]) => {
|
|
101
|
+
if (!fabricCanvas) {
|
|
102
|
+
console.warn('bringGridToFront: fabricCanvas is null or undefined');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (gridLines && gridLines.length > 0) {
|
|
107
|
+
try {
|
|
108
|
+
gridLines.forEach(line => fabricCanvas.bringToFront(line));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.warn('bringGridToFront: Error bringing grid to front:', e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* send grid lines to back of the canvas
|
|
117
|
+
* @param fabricCanvas
|
|
118
|
+
* @param gridLines
|
|
119
|
+
*/
|
|
120
|
+
export const sendGridToBack = (fabricCanvas: fabric.Canvas, gridLines: fabric.Object[]) => {
|
|
121
|
+
if (!fabricCanvas) {
|
|
122
|
+
console.warn('sendGridToBack: fabricCanvas is null or undefined');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (gridLines && gridLines.length > 0) {
|
|
127
|
+
try {
|
|
128
|
+
gridLines.forEach(line => fabricCanvas.sendToBack(line));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn('sendGridToBack: Error sending grid to back:', e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* clear all objects except the grid on the canvas
|
|
137
|
+
* @param fabricCanvas
|
|
138
|
+
*/
|
|
139
|
+
export const clearCanvasExceptGrid = (fabricCanvas: fabric.Canvas) => {
|
|
140
|
+
if (!fabricCanvas) {
|
|
141
|
+
console.warn('clearCanvasExceptGrid: fabricCanvas is null or undefined');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
fabricCanvas.getObjects().forEach(obj => {
|
|
147
|
+
if (!obj.data?.isGrid) {
|
|
148
|
+
fabricCanvas.remove(obj);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.warn('clearCanvasExceptGrid: Error clearing canvas:', e);
|
|
153
|
+
}
|
|
154
|
+
};
|