UNPKG

merchi_product_editor

Version:

A React component for editing product images using Fabric.js

341 lines (340 loc) 17.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { 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; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useProductEditor = exports.ProductEditorProvider = void 0; var react_1 = __importStar(require("react")); var fabric_1 = require("fabric"); var grid_1 = require("../utils/grid"); var job_1 = require("../utils/job"); var ImageHandler_1 = require("../utils/ImageHandler"); var ProductEditorContext = (0, react_1.createContext)(undefined); var ProductEditorProvider = function (_a) { var _b, _c, _d; var children = _a.children, product = _a.product, _e = _a.width, width = _e === void 0 ? 800 : _e, _f = _a.height, height = _f === void 0 ? 600 : _f, job = _a.job, onSave = _a.onSave, onCancel = _a.onCancel, _g = _a.variations, variations = _g === void 0 ? [] : _g, _h = _a.groupVariations, groupVariations = _h === void 0 ? [] : _h; // Combine all variations together to determine the templates to show var allVariations = ((_b = product === null || product === void 0 ? void 0 : product.groupVariationFields) === null || _b === void 0 ? void 0 : _b.length) ? __spreadArray(__spreadArray([], variations, true), groupVariations, true) : __spreadArray([], variations, true); var _j = (0, react_1.useState)((0, job_1.initDraftTemplates)(allVariations, product)), draftTemplates = _j[0], setDraftTemplates = _j[1]; var canvasRef = (0, react_1.useRef)(null); var _k = (0, react_1.useState)(null), canvas = _k[0], setCanvas = _k[1]; var _l = (0, react_1.useState)(new Map()), canvasObjects = _l[0], setCanvasObjects = _l[1]; var _m = (0, react_1.useState)(((_d = (_c = draftTemplates === null || draftTemplates === void 0 ? void 0 : draftTemplates[0]) === null || _c === void 0 ? void 0 : _c.template) === null || _d === void 0 ? void 0 : _d.id) || null), selectedTemplate = _m[0], setSelectedTemplate = _m[1]; var _o = (0, react_1.useState)(false), showGrid = _o[0], setShowGrid = _o[1]; // Initialize canvas objects from variations (0, react_1.useEffect)(function () { if (!canvas) return; var newObjects = new Map(); allVariations.forEach(function (variation) { var _a, _b; var objectData = (0, job_1.buildVariationFieldCanvasObject)(variation); var fieldId = objectData.fieldId; if (!fieldId) return; var fabricObject; if (objectData.canvasObjectType === 'text') { fabricObject = new fabric_1.fabric.Text(objectData.text || '', { fontSize: objectData.fontSize, fontFamily: objectData.fontFamily }); } else if (objectData.canvasObjectType === 'image' && ((_b = (_a = objectData.files) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.viewUrl)) { fabric_1.fabric.Image.fromURL(objectData.files[0].viewUrl, function (img) { if (img) { newObjects.set(fieldId.toString(), img); setCanvasObjects(new Map(newObjects)); } }); return; } else if (objectData.canvasObjectType === 'colour' && objectData.colour) { fabricObject = new fabric_1.fabric.Rect({ fill: objectData.colour, width: 50, height: 50 }); } else { return; } newObjects.set(fieldId.toString(), fabricObject); }); setCanvasObjects(newObjects); }, [canvas, variations, groupVariations]); var handleSave = function () { if (canvas) { var dataUrl = canvas.toDataURL(); // Here you would typically send the dataUrl to your backend console.log('Saving canvas:', dataUrl); onSave(); } }; var loadTemplateImage = function (fabricCanvas, template) { var _a; if (!((_a = template.file) === null || _a === void 0 ? void 0 : _a.viewUrl)) return; // save the existing grid lines var gridLines = (0, grid_1.saveGridState)(fabricCanvas); var hasGrid = gridLines.length > 0; // clear all objects except the grid (0, grid_1.clearCanvasExceptGrid)(fabricCanvas); fabric_1.fabric.Image.fromURL(template.file.viewUrl, function (img) { if (!fabricCanvas) return; // Scale image to fit canvas while maintaining aspect ratio var scale = Math.min(width / img.width, height / img.height); img.scale(scale); // Center the image img.set({ left: (width - img.width * scale) / 2, top: (height - img.height * scale) / 2, selectable: false, evented: false, // template image is not responsive to events }); fabricCanvas.add(img); fabricCanvas.sendToBack(img); // ensure the template is on the bottom // Redraw grid to ensure it's on top if (hasGrid && showGrid) { (0, grid_1.drawGrid)(fabricCanvas, width, height, 20, '#a0a0a0', showGrid); } fabricCanvas.renderAll(); }); }; var _p = (0, react_1.useState)(null), previewImageUrl = _p[0], setPreviewImageUrl = _p[1]; var handleTemplateChange = function (draftTemplate) { if (!canvas) return; if (draftTemplate.id) { setSelectedTemplate(draftTemplate.id); } loadTemplateImage(canvas, draftTemplate); setPreviewImageUrl(null); }; (0, react_1.useEffect)(function () { var init = function () { return __awaiter(void 0, void 0, void 0, function () { var fabricCanvas_1, draftTemplate, cleanupKeyboardEvents_1; var _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!canvasRef.current) return [3 /*break*/, 4]; fabricCanvas_1 = new fabric_1.fabric.Canvas(canvasRef.current, { width: width, height: height, backgroundColor: '#ffffff', }); // fabricCanvas.enableHistory(); // Commented out to avoid error setCanvas(fabricCanvas_1); if (!!!draftTemplates.length) return [3 /*break*/, 3]; draftTemplate = draftTemplates[0]; if (!((_b = (_a = draftTemplate === null || draftTemplate === void 0 ? void 0 : draftTemplate.template) === null || _a === void 0 ? void 0 : _a.file) === null || _b === void 0 ? void 0 : _b.viewUrl)) return [3 /*break*/, 3]; // first load the template image return [4 /*yield*/, loadTemplateImage(fabricCanvas_1, draftTemplate.template)]; case 1: // first load the template image _c.sent(); // then add the variations to the canvas return [4 /*yield*/, (0, job_1.addVariationsToCanvas)(fabricCanvas_1, draftTemplate.variationObjects, draftTemplate.template)]; case 2: // then add the variations to the canvas _c.sent(); _c.label = 3; case 3: // Draw grid after loading the template (0, grid_1.drawGrid)(fabricCanvas_1, width, height, 20, '#a0a0a0', showGrid); cleanupKeyboardEvents_1 = (0, ImageHandler_1.setupKeyboardEvents)(fabricCanvas_1, function (dataUrl) { if (document.activeElement === fabricCanvas_1.upperCanvasEl) { onSave && onSave(); setPreviewImageUrl(null); } }); return [2 /*return*/, function () { cleanupKeyboardEvents_1(); if (fabricCanvas_1) { fabricCanvas_1.dispose(); } }]; case 4: return [2 /*return*/]; } }); }); }; init(); return function () { if (canvas) { canvas.dispose(); } }; }, [product, width, height, onSave]); var _q = (0, react_1.useState)(false), isMobileView = _q[0], setIsMobileView = _q[1]; // Check if we're on a small screen (0, react_1.useEffect)(function () { if (typeof window !== 'undefined') { var updateViewMode_1 = function () { setIsMobileView(window.innerWidth < 480); }; updateViewMode_1(); window.addEventListener('resize', updateViewMode_1); return function () { return window.removeEventListener('resize', updateViewMode_1); }; } }, []); // draw grid when the grid state or canvas size changes (0, react_1.useEffect)(function () { if (canvas) { (0, grid_1.drawGrid)(canvas, width, height, 20, '#a0a0a0', showGrid); } }, [showGrid, width, height, canvas]); // Function to update a canvas object when a variation changes var updateCanvasObject = function (variation) { var objectData = (0, job_1.buildVariationFieldCanvasObject)(variation); var fieldId = objectData.fieldId; if (!fieldId || !canvas) return; var existingObject = canvasObjects.get(fieldId.toString()); if (existingObject) { if (objectData.canvasObjectType === 'text' && existingObject instanceof fabric_1.fabric.Text) { existingObject.set({ text: objectData.text || '', fontSize: objectData.fontSize, fontFamily: objectData.fontFamily, }); } else if (objectData.canvasObjectType === 'colour' && existingObject instanceof fabric_1.fabric.Rect && objectData.colour) { existingObject.set({ fill: objectData.colour, }); } // Add more conditions if needed for other object types canvas.renderAll(); // Re-render the canvas to apply changes } }; // Function to check for changed variations and update canvas objects var updateCanvasFromVariations = function (newVariations, newGroupVariations) { var _a; if (newGroupVariations === void 0) { newGroupVariations = []; } if (!canvas) return; // Combine all variations var newAllVariations = ((_a = product === null || product === void 0 ? void 0 : product.groupVariationFields) === null || _a === void 0 ? void 0 : _a.length) ? __spreadArray(__spreadArray([], newVariations, true), newGroupVariations, true) : __spreadArray([], newVariations, true); // Process each variation to find changes newAllVariations.forEach(function (newVariation) { // Find corresponding old variation to check if it changed var oldVariation = allVariations.find(function (v) { var _a, _b; return ((_a = v.variationField) === null || _a === void 0 ? void 0 : _a.id) === ((_b = newVariation.variationField) === null || _b === void 0 ? void 0 : _b.id); }); // Update the canvas if the variation is new or has changed if (!oldVariation || oldVariation.value !== newVariation.value || JSON.stringify(oldVariation.variationFiles || []) !== JSON.stringify(newVariation.variationFiles || [])) { updateCanvasObject(newVariation); } }); }; return (react_1.default.createElement(ProductEditorContext.Provider, { value: { canvas: canvas, setCanvas: setCanvas, canvasRef: canvasRef, draftTemplates: draftTemplates, selectedTemplate: selectedTemplate, setSelectedTemplate: setSelectedTemplate, showGrid: showGrid, setShowGrid: setShowGrid, product: product, isMobileView: isMobileView, job: job, width: width, height: height, handleUndo: function () { // if (canvas) { // canvas.undo(); // } // History functionality disabled console.log('Undo functionality is disabled'); }, handleRedo: function () { // if (canvas) { // canvas.redo(); // } // History functionality disabled console.log('Redo functionality is disabled'); }, handleTemplateChange: handleTemplateChange, handleSave: handleSave, handleCancel: function () { return onCancel(); }, canvasObjects: canvasObjects, updateCanvasFromVariations: updateCanvasFromVariations, } }, children)); }; exports.ProductEditorProvider = ProductEditorProvider; var useProductEditor = function () { var context = (0, react_1.useContext)(ProductEditorContext); if (context === undefined) { throw new Error('useProductEditor must be used within a ProductEditorProvider'); } return context; }; exports.useProductEditor = useProductEditor;