@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,829 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { fabric } from 'fabric';
|
|
3
|
+
import { Product, Job, DraftTemplate, DraftTemplateData, DraftPreview, SavedCanvasObject, RenderedDraftPreview } from '../types';
|
|
4
|
+
import { drawGrid } from '../utils/grid';
|
|
5
|
+
import { initDraftTemplates } from '../utils/job';
|
|
6
|
+
import { renderEditorOrPreview } from '../utils/renderUtils';
|
|
7
|
+
import { setupKeyboardEvents } from '../utils/keyboard';
|
|
8
|
+
import { haveDraftTemplatesChanged } from '../utils/draftTemplateUtils';
|
|
9
|
+
import { setNewDraftPreviews } from '../utils/previewUtils';
|
|
10
|
+
import { debounce } from 'lodash';
|
|
11
|
+
import { renderClippedImage, renderCanvasWithoutGrid } from '../utils/canvasUtils';
|
|
12
|
+
import { FontOption, defaultFontOptions } from '../config/fontConfig';
|
|
13
|
+
import { defaultPalette } from '../config/colorConfig';
|
|
14
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
15
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
16
|
+
import { getDeviceAdjustedDimensions } from '../utils/deviceUtils';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
interface ProductEditorContextType {
|
|
20
|
+
// States
|
|
21
|
+
canvas: fabric.Canvas | null;
|
|
22
|
+
draftPreviews: DraftPreview[];
|
|
23
|
+
draftTemplates: DraftTemplateData[]; // Updated to use the new type
|
|
24
|
+
isCanvasLoading: boolean;
|
|
25
|
+
isMobileView: boolean;
|
|
26
|
+
loadingPreviews: boolean;
|
|
27
|
+
renderedDraftPreviews: RenderedDraftPreview[];
|
|
28
|
+
savedObjects: SavedCanvasObject[];
|
|
29
|
+
selectedTemplate: number | null;
|
|
30
|
+
selectedTextObject: fabric.IText | null;
|
|
31
|
+
showGrid: boolean;
|
|
32
|
+
showPreview: boolean;
|
|
33
|
+
|
|
34
|
+
// State Setters
|
|
35
|
+
setCanvas: (canvas: fabric.Canvas) => void;
|
|
36
|
+
setSelectedTemplate: (templateId: number) => void;
|
|
37
|
+
setShowGrid: (show: boolean) => void;
|
|
38
|
+
|
|
39
|
+
// Functions
|
|
40
|
+
handleCancel: () => void;
|
|
41
|
+
handleSave: () => void;
|
|
42
|
+
handleTemplateChange: (draftTemplate: DraftTemplate) => void;
|
|
43
|
+
recordVariationFieldObjectsOnCanvas: () => void;
|
|
44
|
+
togglePreview: () => void;
|
|
45
|
+
updateSelectedText: (props: Partial<fabric.IText>) => void;
|
|
46
|
+
fontOptions: FontOption[];
|
|
47
|
+
colorPalette: (string | null)[][];
|
|
48
|
+
showLayerPanel: boolean;
|
|
49
|
+
toggleLayerPanel: () => void;
|
|
50
|
+
selectedObjectId: string | null;
|
|
51
|
+
setSelectedObjectId: (id: string | null) => void;
|
|
52
|
+
selectObject: (obj: fabric.Object | null) => void;
|
|
53
|
+
deleteObject: (obj: fabric.Object) => void;
|
|
54
|
+
|
|
55
|
+
// Props
|
|
56
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
57
|
+
groupIndex: number;
|
|
58
|
+
height: number;
|
|
59
|
+
inputName: string;
|
|
60
|
+
job: Job;
|
|
61
|
+
product: Product;
|
|
62
|
+
width: number;
|
|
63
|
+
hookForm: UseFormReturn<any> | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ProductEditorContext = createContext<ProductEditorContextType | undefined>(undefined);
|
|
67
|
+
|
|
68
|
+
interface ProductEditorProviderProps {
|
|
69
|
+
children: React.ReactNode;
|
|
70
|
+
groupIndex?: number;
|
|
71
|
+
product: Product;
|
|
72
|
+
width?: number;
|
|
73
|
+
height?: number;
|
|
74
|
+
job: Job;
|
|
75
|
+
onSave: () => void;
|
|
76
|
+
onCancel: () => void;
|
|
77
|
+
hookForm?: UseFormReturn<any> | null; // Type the form methods prop
|
|
78
|
+
fontOptions?: FontOption[];
|
|
79
|
+
colorPalette?: (string | null)[][];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const ProductEditorProvider: React.FC<ProductEditorProviderProps> = ({
|
|
83
|
+
children,
|
|
84
|
+
groupIndex = 0,
|
|
85
|
+
product,
|
|
86
|
+
width: initialWidth = 800, // Rename prop
|
|
87
|
+
height: initialHeight = 600, // Rename prop
|
|
88
|
+
job,
|
|
89
|
+
onSave,
|
|
90
|
+
onCancel,
|
|
91
|
+
hookForm = null, // Initialize with null
|
|
92
|
+
fontOptions = defaultFontOptions,
|
|
93
|
+
colorPalette = defaultPalette,
|
|
94
|
+
}) => {
|
|
95
|
+
|
|
96
|
+
const inputName = `ownDrafts[0].images[${groupIndex || 0}]`;
|
|
97
|
+
|
|
98
|
+
// Create refs to store the latest values to prevent excessive re-renders
|
|
99
|
+
const allVariationsRef = useRef<any[]>([]);
|
|
100
|
+
const productRef = useRef(product);
|
|
101
|
+
|
|
102
|
+
// Update product ref when it changes
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
productRef.current = product;
|
|
105
|
+
}, [product]);
|
|
106
|
+
|
|
107
|
+
// Determine dimensions and mobile status once at initialization
|
|
108
|
+
const deviceSettings = useMemo(() =>
|
|
109
|
+
getDeviceAdjustedDimensions(initialWidth, initialHeight),
|
|
110
|
+
[initialWidth, initialHeight]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Use dimensions from device settings
|
|
114
|
+
const canvasWidth = deviceSettings.width;
|
|
115
|
+
const canvasHeight = deviceSettings.height;
|
|
116
|
+
const isMobileView = deviceSettings.isMobile;
|
|
117
|
+
|
|
118
|
+
const [
|
|
119
|
+
draftTemplates,
|
|
120
|
+
setDraftTemplates
|
|
121
|
+
] = useState<DraftTemplateData[]>([]);
|
|
122
|
+
const [draftPreviews, setDraftPreviews] = useState<DraftPreview[]>([]);
|
|
123
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
124
|
+
const [canvas, setCanvas] = useState<fabric.Canvas | null>(null);
|
|
125
|
+
const [selectedTemplate, setSelectedTemplate] = useState<number | null>(
|
|
126
|
+
draftTemplates?.[0]?.template?.id || null
|
|
127
|
+
);
|
|
128
|
+
const [showGrid, setShowGrid] = useState(true);
|
|
129
|
+
const [showPreview, setShowPreview] = useState(true);
|
|
130
|
+
const [isCanvasLoading, setIsCanvasLoading] = useState(true);
|
|
131
|
+
const [selectedTextObject, setSelectedTextObject] = useState<fabric.IText | null>(null);
|
|
132
|
+
|
|
133
|
+
const [showLayerPanel, setShowLayerPanel] = useState(false);
|
|
134
|
+
const [selectedObjectId, setSelectedObjectId] = useState<string | null>(null);
|
|
135
|
+
|
|
136
|
+
// Add a new state to track saved objects
|
|
137
|
+
const [savedObjects, setSavedObjects] = useState<SavedCanvasObject[]>([]);
|
|
138
|
+
|
|
139
|
+
// Add this ref to track initial mounting
|
|
140
|
+
const initialMountRef = useRef(false);
|
|
141
|
+
|
|
142
|
+
// Initialize savedObjects from localStorage on component mount
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
// Only reset on initial mount
|
|
145
|
+
if (!initialMountRef.current) {
|
|
146
|
+
initialMountRef.current = true;
|
|
147
|
+
// Reset savedObjects on page reload instead of loading from localStorage
|
|
148
|
+
setSavedObjects([]);
|
|
149
|
+
}
|
|
150
|
+
}, [product]);
|
|
151
|
+
|
|
152
|
+
// Function to record the current state of variation field objects
|
|
153
|
+
const recordVariationFieldObjectsOnCanvas = useCallback(() => {
|
|
154
|
+
if (!canvas || selectedTemplate === null) return;
|
|
155
|
+
// Create a fresh array instead of appending to existing
|
|
156
|
+
const updatedSavedObjects: SavedCanvasObject[] = [];
|
|
157
|
+
let hasChanges = false;
|
|
158
|
+
|
|
159
|
+
// Get all objects from the canvas
|
|
160
|
+
const objects = canvas.getObjects();
|
|
161
|
+
|
|
162
|
+
// Filter for objects that have a fieldId property (variation field objects)
|
|
163
|
+
objects.forEach((obj: any) => {
|
|
164
|
+
// Get fieldId from either direct property or from variationField
|
|
165
|
+
|
|
166
|
+
const { fieldId } = obj;
|
|
167
|
+
|
|
168
|
+
if (fieldId) {
|
|
169
|
+
// Also save a serialized version for the savedObjects state
|
|
170
|
+
const updatedObject = {
|
|
171
|
+
...obj,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (obj instanceof fabric.Image) {
|
|
175
|
+
Object.assign(updatedObject, {
|
|
176
|
+
width: obj.width,
|
|
177
|
+
height: obj.height,
|
|
178
|
+
src: obj.getSrc(),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
updatedSavedObjects.push(updatedObject);
|
|
183
|
+
hasChanges = true;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Only update state if we found objects with fieldIds
|
|
188
|
+
if (hasChanges) {
|
|
189
|
+
// Update the savedObjects state with the new complete array
|
|
190
|
+
setSavedObjects(updatedSavedObjects);
|
|
191
|
+
}
|
|
192
|
+
}, [canvas, selectedTemplate]); // Remove savedObjects from the dependencies
|
|
193
|
+
|
|
194
|
+
// Function to toggle preview visibility
|
|
195
|
+
const togglePreview = () => {
|
|
196
|
+
setShowPreview(prev => !prev);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Function to toggle layer panel visibility
|
|
200
|
+
const toggleLayerPanel = () => {
|
|
201
|
+
setShowLayerPanel(prev => !prev);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Function to update selected text object properties
|
|
205
|
+
const updateSelectedText = (props: Partial<fabric.IText>) => {
|
|
206
|
+
if (selectedTextObject && canvas && selectedTemplate !== null) {
|
|
207
|
+
selectedTextObject.set(props);
|
|
208
|
+
canvas.requestRenderAll();
|
|
209
|
+
|
|
210
|
+
// Record the state changes after updating text properties
|
|
211
|
+
// This ensures color changes and other styling updates are saved
|
|
212
|
+
setTimeout(() => {
|
|
213
|
+
if (canvas && selectedTemplate !== null) {
|
|
214
|
+
recordVariationFieldObjectsOnCanvas();
|
|
215
|
+
}
|
|
216
|
+
}, 50);
|
|
217
|
+
} else {
|
|
218
|
+
console.warn('Cannot update text - missing canvas, text object, or template');
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const handleSave = () => {
|
|
223
|
+
if (canvas) {
|
|
224
|
+
const dataUrl = canvas.toDataURL();
|
|
225
|
+
onSave();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Handle template change with improved state tracking
|
|
230
|
+
const handleTemplateChange = async (draftTemplate: DraftTemplate) => {
|
|
231
|
+
if (!canvas) return;
|
|
232
|
+
|
|
233
|
+
// Record the current state of variation field objects
|
|
234
|
+
recordVariationFieldObjectsOnCanvas();
|
|
235
|
+
|
|
236
|
+
// Check if this is already the selected template - prevent reloading the same template
|
|
237
|
+
if (draftTemplate.id && selectedTemplate === draftTemplate.id) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Update the selected template
|
|
242
|
+
if (draftTemplate.id) {
|
|
243
|
+
setSelectedTemplate(draftTemplate.id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Create a clean canvas reference for tracking
|
|
247
|
+
const currentCanvas = canvas;
|
|
248
|
+
|
|
249
|
+
// Create a new canvas before disposing the old one
|
|
250
|
+
if (canvasRef.current) {
|
|
251
|
+
// Dispose the old canvas properly
|
|
252
|
+
try {
|
|
253
|
+
currentCanvas.dispose();
|
|
254
|
+
|
|
255
|
+
// Remove any lingering canvas containers to prevent duplicates
|
|
256
|
+
if (canvasRef.current.parentElement) {
|
|
257
|
+
const containers = canvasRef.current.parentElement.querySelectorAll('.canvas-container');
|
|
258
|
+
if (containers.length > 1) {
|
|
259
|
+
// Keep only the first container
|
|
260
|
+
for (let i = 1; i < containers.length; i++) {
|
|
261
|
+
containers[i].remove();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} catch (e) {
|
|
266
|
+
console.error('Error during canvas cleanup:', e);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const newCanvas = new fabric.Canvas(canvasRef.current, {
|
|
270
|
+
width: canvasWidth,
|
|
271
|
+
height: canvasHeight,
|
|
272
|
+
backgroundColor: '#ffffff',
|
|
273
|
+
// not to bring the selected object to the top visually
|
|
274
|
+
preserveObjectStacking: true
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Now load the template image and add variations
|
|
278
|
+
const templateData = draftTemplates.find(dt => dt.template.id === draftTemplate.id);
|
|
279
|
+
|
|
280
|
+
await renderEditorOrPreview(
|
|
281
|
+
newCanvas,
|
|
282
|
+
(templateData as any).template,
|
|
283
|
+
(templateData as any).variationObjects,
|
|
284
|
+
savedObjects,
|
|
285
|
+
canvasHeight,
|
|
286
|
+
canvasWidth,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Update the state with the new canvas
|
|
290
|
+
setCanvas(newCanvas);
|
|
291
|
+
|
|
292
|
+
// Since selectedTemplate was just updated, we need a slight delay to ensure
|
|
293
|
+
// grid is drawn and loading is set to false
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
// Draw grid if needed
|
|
296
|
+
if (showGrid) {
|
|
297
|
+
try {
|
|
298
|
+
if (newCanvas.getElement() && newCanvas.getElement().parentNode) {
|
|
299
|
+
drawGrid(newCanvas, canvasWidth, canvasHeight, 20, '#a0a0a0', showGrid);
|
|
300
|
+
}
|
|
301
|
+
} catch (e) {
|
|
302
|
+
console.error('Error drawing grid after template change:', e);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
setIsCanvasLoading(false);
|
|
306
|
+
}, 50);
|
|
307
|
+
|
|
308
|
+
// Re-setup keyboard events for the new canvas
|
|
309
|
+
setupKeyboardEvents(newCanvas, (dataUrl: any) => {
|
|
310
|
+
if (document.activeElement === newCanvas.upperCanvasEl) {
|
|
311
|
+
onSave && onSave();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Add cleanup event handlers that properly use the callback function
|
|
318
|
+
const createEventHandler = useCallback((callback: Function) => {
|
|
319
|
+
return () => {
|
|
320
|
+
if (callback) callback();
|
|
321
|
+
};
|
|
322
|
+
}, []);
|
|
323
|
+
|
|
324
|
+
// Replace the direct watch with a debounced version
|
|
325
|
+
const debouncedWatch = useMemo(() => {
|
|
326
|
+
return debounce(async (watchValues: { variationsGroups?: any; variations?: any }) => {
|
|
327
|
+
// Start loading
|
|
328
|
+
setIsCanvasLoading(true);
|
|
329
|
+
|
|
330
|
+
// Record the current state of variation field objects before changes
|
|
331
|
+
if (canvas) {
|
|
332
|
+
recordVariationFieldObjectsOnCanvas();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Extract the variations from the watch values
|
|
336
|
+
const variationsGroups = watchValues.variationsGroups;
|
|
337
|
+
const variations = watchValues.variations;
|
|
338
|
+
|
|
339
|
+
// Process the latest variations to update allVariationsRef
|
|
340
|
+
const newAllVariations = variationsGroups?.[groupIndex]?.variations
|
|
341
|
+
? [...variationsGroups[groupIndex].variations]
|
|
342
|
+
: variations || [];
|
|
343
|
+
|
|
344
|
+
// Store in ref to avoid triggering effects
|
|
345
|
+
allVariationsRef.current = newAllVariations;
|
|
346
|
+
|
|
347
|
+
const newDraftTemplates = initDraftTemplates(newAllVariations, productRef.current);
|
|
348
|
+
|
|
349
|
+
// Check if the currently selected template ID still exists in the new list
|
|
350
|
+
const currentSelectedIdStillExists = newDraftTemplates.some(
|
|
351
|
+
dt => dt.template.id === selectedTemplate);
|
|
352
|
+
|
|
353
|
+
// Store the canvas instance for this effect run
|
|
354
|
+
let fabricCanvasInstance = canvas;
|
|
355
|
+
|
|
356
|
+
// Only update if they've actually changed
|
|
357
|
+
if (haveDraftTemplatesChanged(draftTemplates, newDraftTemplates)) {
|
|
358
|
+
setDraftTemplates(newDraftTemplates);
|
|
359
|
+
setDraftPreviews(setNewDraftPreviews(newDraftTemplates));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Determine the template to load
|
|
363
|
+
let templateToLoad: DraftTemplate | undefined = newDraftTemplates[0]?.template;
|
|
364
|
+
|
|
365
|
+
if (currentSelectedIdStillExists && selectedTemplate) {
|
|
366
|
+
const previouslySelected = newDraftTemplates.find(dt => dt.template.id === selectedTemplate);
|
|
367
|
+
templateToLoad = previouslySelected?.template || templateToLoad;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
setSelectedTemplate(templateToLoad?.id || null);
|
|
371
|
+
|
|
372
|
+
// Handle canvas setup
|
|
373
|
+
if (fabricCanvasInstance) {
|
|
374
|
+
// if we have a canvas we need to clear all the old templates
|
|
375
|
+
fabricCanvasInstance.clear();
|
|
376
|
+
fabricCanvasInstance.setBackgroundColor(
|
|
377
|
+
'#ffffff',
|
|
378
|
+
() => fabricCanvasInstance?.renderAll()
|
|
379
|
+
);
|
|
380
|
+
} else if (canvasRef.current) {
|
|
381
|
+
fabricCanvasInstance = new fabric.Canvas(canvasRef.current, {
|
|
382
|
+
width: canvasWidth,
|
|
383
|
+
height: canvasHeight,
|
|
384
|
+
backgroundColor: '#ffffff',
|
|
385
|
+
preserveObjectStacking: true
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Update state immediately to ensure it's available for event handlers
|
|
389
|
+
setCanvas(fabricCanvasInstance);
|
|
390
|
+
} else {
|
|
391
|
+
console.error('No canvas ref available');
|
|
392
|
+
setIsCanvasLoading(false);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (templateToLoad && templateToLoad.id) {
|
|
397
|
+
const templateData = newDraftTemplates.find(
|
|
398
|
+
dt => dt.template.id === (templateToLoad as any).id);
|
|
399
|
+
|
|
400
|
+
// Render the template onto the canvas
|
|
401
|
+
await renderEditorOrPreview(
|
|
402
|
+
fabricCanvasInstance,
|
|
403
|
+
templateData?.template || {},
|
|
404
|
+
templateData?.variationObjects || [],
|
|
405
|
+
savedObjects,
|
|
406
|
+
canvasHeight,
|
|
407
|
+
canvasWidth,
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
if (fabricCanvasInstance.getElement() && fabricCanvasInstance.getElement().parentNode) {
|
|
411
|
+
drawGrid(fabricCanvasInstance, canvasWidth, canvasHeight, 20, '#a0a0a0', showGrid);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
setIsCanvasLoading(false);
|
|
415
|
+
} else {
|
|
416
|
+
setIsCanvasLoading(false);
|
|
417
|
+
}
|
|
418
|
+
}, 500);
|
|
419
|
+
}, [
|
|
420
|
+
groupIndex,
|
|
421
|
+
draftTemplates,
|
|
422
|
+
canvasRef,
|
|
423
|
+
showGrid,
|
|
424
|
+
canvasWidth,
|
|
425
|
+
canvasHeight,
|
|
426
|
+
savedObjects,
|
|
427
|
+
canvas,
|
|
428
|
+
selectedTemplate,
|
|
429
|
+
recordVariationFieldObjectsOnCanvas,
|
|
430
|
+
setSelectedTemplate
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
// Set up the debounced watch subscription
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
if (!hookForm) return;
|
|
436
|
+
|
|
437
|
+
// Subscribe to form changes
|
|
438
|
+
const subscription = hookForm.watch((value: any) => {
|
|
439
|
+
debouncedWatch({
|
|
440
|
+
variationsGroups: value.variationsGroups,
|
|
441
|
+
variations: value.variations,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Clean up subscription
|
|
446
|
+
return () => subscription.unsubscribe();
|
|
447
|
+
}, [hookForm, debouncedWatch]);
|
|
448
|
+
|
|
449
|
+
// This effect is used to trigger the debounced watch when the selected template is not set
|
|
450
|
+
// This helps with things like add group.
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (!selectedTemplate) {
|
|
453
|
+
debouncedWatch({
|
|
454
|
+
variationsGroups: job.variationsGroups,
|
|
455
|
+
variations: job.variations,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}, [groupIndex]);
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
const [loadingPreviews, setLoadingPreviews] = useState(false);
|
|
462
|
+
const [renderedDraftPreviews, setRenderedDraftPreviews] = useState<RenderedDraftPreview[]>([]);
|
|
463
|
+
|
|
464
|
+
// Extract event handler setup to a separate function to avoid recreating in debounced watch
|
|
465
|
+
const setupEventHandlers = (fabricCanvasInstance: fabric.Canvas, selectedTemplate: number | null) => {
|
|
466
|
+
if (!fabricCanvasInstance) return () => { };
|
|
467
|
+
|
|
468
|
+
// Add a flag to prevent recursive calls
|
|
469
|
+
let isRendering = false;
|
|
470
|
+
|
|
471
|
+
// Define the record changes function with canvas and template closure
|
|
472
|
+
const recordChanges = () => {
|
|
473
|
+
// Use the passed instance and template ID instead of state variables
|
|
474
|
+
if (fabricCanvasInstance) {
|
|
475
|
+
const objects = fabricCanvasInstance.getObjects();
|
|
476
|
+
|
|
477
|
+
const updatedSavedObjects: SavedCanvasObject[] = [];
|
|
478
|
+
let hasChanges = false;
|
|
479
|
+
|
|
480
|
+
objects.forEach((obj: any) => {
|
|
481
|
+
const { fieldId } = obj;
|
|
482
|
+
if (fieldId) {
|
|
483
|
+
const updatedObject = {
|
|
484
|
+
...obj,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
if (obj instanceof fabric.Image) {
|
|
488
|
+
Object.assign(updatedObject, {
|
|
489
|
+
width: obj.width,
|
|
490
|
+
height: obj.height,
|
|
491
|
+
src: obj.getSrc(),
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
updatedSavedObjects.push(updatedObject);
|
|
496
|
+
hasChanges = true;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (hasChanges) {
|
|
501
|
+
setSavedObjects(updatedSavedObjects);
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
console.warn('Cannot record changes - missing canvas or templateId');
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// Create a bound version for event handling
|
|
509
|
+
const boundRecordChanges = createEventHandler(recordChanges);
|
|
510
|
+
|
|
511
|
+
// Add Text Toolbar Event Listener
|
|
512
|
+
const handleSelection = (e: fabric.IEvent) => {
|
|
513
|
+
const activeObject = fabricCanvasInstance.getActiveObject();
|
|
514
|
+
// Check if the active object is an IText instance
|
|
515
|
+
|
|
516
|
+
// Ensure the selected object has an ID
|
|
517
|
+
if (activeObject && !(activeObject as any).id) {
|
|
518
|
+
(activeObject as any).id = uuidv4();
|
|
519
|
+
}
|
|
520
|
+
setSelectedObjectId((activeObject as any)?.id || null);
|
|
521
|
+
|
|
522
|
+
if (activeObject instanceof fabric.IText) {
|
|
523
|
+
setSelectedTextObject(activeObject);
|
|
524
|
+
} else {
|
|
525
|
+
setSelectedTextObject(null);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const handleSelectionCleared = (e: fabric.IEvent) => {
|
|
530
|
+
setSelectedObjectId(null); // Clear selection in context
|
|
531
|
+
setSelectedTextObject(null);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const updateRenderedDraftPreviews = async () => {
|
|
535
|
+
const templateId = selectedTemplate;
|
|
536
|
+
const renderedImage = await renderClippedImage(fabricCanvasInstance, { format: 'png' });
|
|
537
|
+
const fullCanvasImage = await renderCanvasWithoutGrid(fabricCanvasInstance, { format: 'png' });
|
|
538
|
+
const activeTemplateIds = draftTemplates.map(dt => dt.template.id);
|
|
539
|
+
|
|
540
|
+
if (renderedImage && templateId) {
|
|
541
|
+
const previews = [...renderedDraftPreviews.filter(rdp => activeTemplateIds.includes(rdp.templateId))];
|
|
542
|
+
const existingPreviewIndex = previews.findIndex(preview => preview.templateId === templateId);
|
|
543
|
+
if (existingPreviewIndex !== -1) {
|
|
544
|
+
// Update existing preview
|
|
545
|
+
previews[existingPreviewIndex] = {
|
|
546
|
+
templateId,
|
|
547
|
+
draft: renderedImage,
|
|
548
|
+
canvasPreview: fullCanvasImage || '',
|
|
549
|
+
};
|
|
550
|
+
} else {
|
|
551
|
+
// Add new preview
|
|
552
|
+
previews.push({
|
|
553
|
+
templateId,
|
|
554
|
+
draft: renderedImage,
|
|
555
|
+
canvasPreview: fullCanvasImage || '',
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Save the rendered draft previews and previews to local storage
|
|
560
|
+
const productDraftTemplate = localStorage.getItem(`productDraftTemplate-${product.id}`);
|
|
561
|
+
const storedData = productDraftTemplate ? JSON.parse(productDraftTemplate) : [];
|
|
562
|
+
storedData[groupIndex] = {
|
|
563
|
+
groupIndex,
|
|
564
|
+
productId: product.id,
|
|
565
|
+
templateData: [...previews],
|
|
566
|
+
previews: [...previews],
|
|
567
|
+
}
|
|
568
|
+
localStorage.setItem(`productDraftTemplate-${product.id}`, JSON.stringify(storedData));
|
|
569
|
+
setRenderedDraftPreviews(previews);
|
|
570
|
+
|
|
571
|
+
setLoadingPreviews(false);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// Add after:render event listener to call renderClippedImage
|
|
576
|
+
const handleAfterRender = async () => {
|
|
577
|
+
// Prevent recursive calls
|
|
578
|
+
if (isRendering) return;
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
isRendering = true;
|
|
582
|
+
setLoadingPreviews(true);
|
|
583
|
+
await updateRenderedDraftPreviews();
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error('Error rendering clipped image after render:', error);
|
|
586
|
+
} finally {
|
|
587
|
+
isRendering = false;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// Add unique ID to objects if they don't have one
|
|
592
|
+
const ensureObjectIds = (canvasInstance: fabric.Canvas) => {
|
|
593
|
+
canvasInstance.getObjects().forEach(obj => {
|
|
594
|
+
if (!(obj as any).id) {
|
|
595
|
+
(obj as any).id = uuidv4();
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
// Ensure initial objects have IDs
|
|
600
|
+
if (fabricCanvasInstance) {
|
|
601
|
+
ensureObjectIds(fabricCanvasInstance);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Attach listeners
|
|
605
|
+
fabricCanvasInstance?.on('selection:created', handleSelection);
|
|
606
|
+
fabricCanvasInstance?.on('selection:updated', handleSelection);
|
|
607
|
+
fabricCanvasInstance?.on('selection:cleared', handleSelectionCleared);
|
|
608
|
+
// Also update IDs when new objects are added
|
|
609
|
+
fabricCanvasInstance?.on('object:added', (e) => {
|
|
610
|
+
if (e.target && !(e.target as any).id) {
|
|
611
|
+
(e.target as any).id = uuidv4();
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// setup keyboard events
|
|
616
|
+
const cleanupKeyboardEvents = setupKeyboardEvents(fabricCanvasInstance, () => {
|
|
617
|
+
if (document.activeElement === fabricCanvasInstance.upperCanvasEl) {
|
|
618
|
+
onSave && onSave();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
fabricCanvasInstance.on('selection:created', handleSelection);
|
|
623
|
+
fabricCanvasInstance.on('selection:updated', handleSelection);
|
|
624
|
+
fabricCanvasInstance.on('selection:cleared', handleSelectionCleared);
|
|
625
|
+
fabricCanvasInstance.on('after:render', handleAfterRender);
|
|
626
|
+
|
|
627
|
+
// Add event listeners for object modifications
|
|
628
|
+
fabricCanvasInstance.on('object:modified', boundRecordChanges);
|
|
629
|
+
fabricCanvasInstance.on('object:moved', boundRecordChanges);
|
|
630
|
+
fabricCanvasInstance.on('object:scaled', boundRecordChanges);
|
|
631
|
+
fabricCanvasInstance.on('object:rotated', boundRecordChanges);
|
|
632
|
+
fabricCanvasInstance.on('text:changed', boundRecordChanges);
|
|
633
|
+
|
|
634
|
+
// Add handlers for more specific events that might be triggered by color changes
|
|
635
|
+
fabricCanvasInstance.on('object:changed', boundRecordChanges);
|
|
636
|
+
fabricCanvasInstance.on('canvas:updated', boundRecordChanges);
|
|
637
|
+
|
|
638
|
+
// Return cleanup function
|
|
639
|
+
return () => {
|
|
640
|
+
fabricCanvasInstance.off('selection:created', handleSelection);
|
|
641
|
+
fabricCanvasInstance.off('selection:updated', handleSelection);
|
|
642
|
+
fabricCanvasInstance.off('selection:cleared', handleSelectionCleared);
|
|
643
|
+
fabricCanvasInstance.off('after:render', handleAfterRender);
|
|
644
|
+
|
|
645
|
+
fabricCanvasInstance.off('object:modified', boundRecordChanges);
|
|
646
|
+
fabricCanvasInstance.off('object:moved', boundRecordChanges);
|
|
647
|
+
fabricCanvasInstance.off('object:scaled', boundRecordChanges);
|
|
648
|
+
fabricCanvasInstance.off('object:rotated', boundRecordChanges);
|
|
649
|
+
fabricCanvasInstance.off('text:changed', boundRecordChanges);
|
|
650
|
+
fabricCanvasInstance.off('object:changed', boundRecordChanges);
|
|
651
|
+
fabricCanvasInstance.off('canvas:updated', boundRecordChanges);
|
|
652
|
+
|
|
653
|
+
cleanupKeyboardEvents();
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// Move setupEventHandlers call to a useEffect hook
|
|
658
|
+
useEffect(() => {
|
|
659
|
+
if (canvas) {
|
|
660
|
+
try {
|
|
661
|
+
if (canvas.getElement() && canvas.getElement().parentNode) {
|
|
662
|
+
drawGrid(canvas, canvasWidth, canvasHeight, 20, '#a0a0a0', showGrid);
|
|
663
|
+
canvas.renderAll();
|
|
664
|
+
}
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.error('Error drawing/clearing grid:', error);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Setup event handlers
|
|
670
|
+
const cleanupEventHandlers = setupEventHandlers(canvas, selectedTemplate);
|
|
671
|
+
return () => {
|
|
672
|
+
cleanupEventHandlers();
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}, [canvas, selectedTemplate, showGrid, canvasWidth, canvasHeight]);
|
|
676
|
+
|
|
677
|
+
const selectObject = (obj: fabric.Object | null) => {
|
|
678
|
+
if (canvas) {
|
|
679
|
+
if (obj) {
|
|
680
|
+
if (!(obj as any).id) {
|
|
681
|
+
(obj as any).id = uuidv4();
|
|
682
|
+
}
|
|
683
|
+
canvas.setActiveObject(obj);
|
|
684
|
+
setSelectedObjectId((obj as any).id);
|
|
685
|
+
} else {
|
|
686
|
+
canvas.discardActiveObject();
|
|
687
|
+
setSelectedObjectId(null);
|
|
688
|
+
}
|
|
689
|
+
canvas.requestRenderAll();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
const deleteObject = useCallback((obj: fabric.Object) => {
|
|
694
|
+
if (!canvas || !obj) return;
|
|
695
|
+
|
|
696
|
+
try {
|
|
697
|
+
setIsCanvasLoading(true);
|
|
698
|
+
|
|
699
|
+
// remove the object from the canvas
|
|
700
|
+
canvas.remove(obj);
|
|
701
|
+
canvas.renderAll();
|
|
702
|
+
|
|
703
|
+
const fieldId = (obj as any)?.fieldId;
|
|
704
|
+
const fileId = (obj as any)?.fileId;
|
|
705
|
+
|
|
706
|
+
if (fieldId) {
|
|
707
|
+
const currentTemplateId = selectedTemplate;
|
|
708
|
+
|
|
709
|
+
// update draftTemplates
|
|
710
|
+
const updatedDraftTemplates = draftTemplates.map(dt => {
|
|
711
|
+
if (dt.template.id === currentTemplateId) {
|
|
712
|
+
return {
|
|
713
|
+
...dt,
|
|
714
|
+
variationObjects: dt.variationObjects.filter((vo: any) => vo.fieldId !== fieldId)
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return dt;
|
|
718
|
+
});
|
|
719
|
+
setDraftTemplates(updatedDraftTemplates);
|
|
720
|
+
|
|
721
|
+
// update the form state
|
|
722
|
+
if (hookForm) {
|
|
723
|
+
const hasVariationsGroups = 'variationsGroups' in hookForm.getValues();
|
|
724
|
+
const variationsField = hasVariationsGroups ?
|
|
725
|
+
`variationsGroups.${groupIndex}.variations` : 'variations';
|
|
726
|
+
const values = hookForm.getValues(variationsField) || [];
|
|
727
|
+
|
|
728
|
+
const updatedValues = values.map((variation: any) => {
|
|
729
|
+
if (variation?.variationField?.id === fieldId) {
|
|
730
|
+
// file type variation and has fileId
|
|
731
|
+
// only delete the specific file
|
|
732
|
+
if (variation.variationFiles?.length > 0 && fileId) {
|
|
733
|
+
const updatedFiles = variation.variationFiles.filter((file: any) => file.id !== fileId);
|
|
734
|
+
return {
|
|
735
|
+
...variation,
|
|
736
|
+
value: updatedFiles.length === 0 ? null : variation.value,
|
|
737
|
+
variationFiles: updatedFiles
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
// non file type or no fileId
|
|
741
|
+
// clear the whole field
|
|
742
|
+
else if (!fileId) {
|
|
743
|
+
return {
|
|
744
|
+
...variation,
|
|
745
|
+
value: null,
|
|
746
|
+
variationFiles: variation.variationFiles ? [] : undefined
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return variation;
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
hookForm.setValue(variationsField, updatedValues);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// clear the selection state
|
|
758
|
+
setSelectedObjectId(null);
|
|
759
|
+
setSelectedTextObject(null);
|
|
760
|
+
setIsCanvasLoading(false);
|
|
761
|
+
|
|
762
|
+
onSave && onSave();
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error('Error in deleteObject:', error);
|
|
765
|
+
setIsCanvasLoading(false);
|
|
766
|
+
}
|
|
767
|
+
}, [canvas, draftTemplates, selectedTemplate, hookForm, groupIndex, onSave]);
|
|
768
|
+
|
|
769
|
+
return (
|
|
770
|
+
<ProductEditorContext.Provider
|
|
771
|
+
value={{
|
|
772
|
+
// States
|
|
773
|
+
canvas,
|
|
774
|
+
draftPreviews,
|
|
775
|
+
draftTemplates,
|
|
776
|
+
isCanvasLoading,
|
|
777
|
+
isMobileView,
|
|
778
|
+
loadingPreviews,
|
|
779
|
+
renderedDraftPreviews,
|
|
780
|
+
savedObjects,
|
|
781
|
+
selectedTemplate,
|
|
782
|
+
selectedTextObject,
|
|
783
|
+
showGrid,
|
|
784
|
+
showPreview,
|
|
785
|
+
|
|
786
|
+
// State Setters
|
|
787
|
+
setCanvas,
|
|
788
|
+
setSelectedTemplate,
|
|
789
|
+
setShowGrid,
|
|
790
|
+
|
|
791
|
+
// Functions
|
|
792
|
+
handleCancel: () => onCancel(),
|
|
793
|
+
handleSave,
|
|
794
|
+
handleTemplateChange,
|
|
795
|
+
recordVariationFieldObjectsOnCanvas,
|
|
796
|
+
togglePreview,
|
|
797
|
+
updateSelectedText,
|
|
798
|
+
fontOptions,
|
|
799
|
+
colorPalette,
|
|
800
|
+
showLayerPanel,
|
|
801
|
+
toggleLayerPanel,
|
|
802
|
+
selectedObjectId,
|
|
803
|
+
setSelectedObjectId,
|
|
804
|
+
selectObject,
|
|
805
|
+
deleteObject,
|
|
806
|
+
|
|
807
|
+
// Props
|
|
808
|
+
canvasRef,
|
|
809
|
+
groupIndex,
|
|
810
|
+
height: canvasHeight,
|
|
811
|
+
inputName,
|
|
812
|
+
job,
|
|
813
|
+
product,
|
|
814
|
+
width: canvasWidth,
|
|
815
|
+
hookForm,
|
|
816
|
+
}}
|
|
817
|
+
>
|
|
818
|
+
{children}
|
|
819
|
+
</ProductEditorContext.Provider>
|
|
820
|
+
);
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
export const useProductEditor = () => {
|
|
824
|
+
const context = useContext(ProductEditorContext);
|
|
825
|
+
if (context === undefined) {
|
|
826
|
+
throw new Error('useProductEditor must be used within a ProductEditorProvider');
|
|
827
|
+
}
|
|
828
|
+
return context;
|
|
829
|
+
};
|