UNPKG

merchi_product_editor

Version:

A React component for editing product images using Fabric.js

466 lines (465 loc) 22.6 kB
"use strict"; 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 }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderDraftPreviewsWithLayers = void 0; var ag_psd_1 = require("ag-psd"); /** * Find a layer by name within a PSD layer structure * * @param layers The array of PSD layers to search through * @param name The name of the layer to find * @returns The found layer or null if not found */ function findLayerByName(layers, name) { if (!layers) return null; for (var _i = 0, layers_1 = layers; _i < layers_1.length; _i++) { var layer = layers_1[_i]; if (layer.name === name) { return layer; } if (layer.children) { var found = findLayerByName(layer.children, name); if (found) return found; } } return null; } /** * Load an image from a URL * * @param url URL of the image to load * @returns Promise that resolves to an HTMLImageElement */ function loadImage(url) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { var img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function () { return resolve(img); }; img.onerror = function (e) { return reject(new Error("Failed to load image from URL: ".concat(url, ", error: ").concat(e))); }; img.src = url; })]; }); }); } /** * Validates if a data URL represents a valid image * * @param dataUrl The data URL to validate * @returns Promise that resolves to true if valid, false otherwise */ function isValidImageDataUrl(dataUrl) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve) { if (!dataUrl || !dataUrl.startsWith('data:image/')) { console.warn('Invalid data URL format'); resolve(false); return; } var img = new Image(); img.onload = function () { // Check if the image has actual dimensions if (img.width > 0 && img.height > 0) { resolve(true); } else { console.warn('Image loaded but has zero dimensions'); resolve(false); } }; img.onerror = function () { console.warn('Failed to load image from data URL'); resolve(false); }; img.src = dataUrl; })]; }); }); } /** * Creates a fallback preview image with debugging information * * @param width Width of the image * @param height Height of the image * @param draftPreviewId ID of the draft preview * @param error Optional error message * @returns PNG data URL */ function createFallbackImage(width, height, draftPreviewId, error) { var canvas = document.createElement('canvas'); canvas.width = Math.max(width || 300, 300); canvas.height = Math.max(height || 200, 200); var ctx = canvas.getContext('2d'); if (!ctx) { // Even more basic fallback if context fails var basicCanvas = document.createElement('canvas'); basicCanvas.width = 300; basicCanvas.height = 200; var basicCtx = basicCanvas.getContext('2d'); if (basicCtx) { basicCtx.fillStyle = '#ffcccc'; basicCtx.fillRect(0, 0, 300, 200); basicCtx.fillStyle = '#990000'; basicCtx.font = '16px Arial'; basicCtx.textAlign = 'center'; basicCtx.fillText('Preview failed', 150, 100); return basicCanvas.toDataURL('image/png'); } return ''; } // Background ctx.fillStyle = '#f8f8f8'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw a grid pattern ctx.strokeStyle = '#e0e0e0'; ctx.lineWidth = 1; var gridSize = 20; for (var x = 0; x < canvas.width; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (var y = 0; y < canvas.height; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } // Border ctx.strokeStyle = '#cccccc'; ctx.lineWidth = 2; ctx.strokeRect(0, 0, canvas.width, canvas.height); // Preview text ctx.fillStyle = '#666666'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.fillText('Preview', canvas.width / 2, canvas.height / 2 - 15); // Preview ID if (draftPreviewId !== undefined) { ctx.font = '16px Arial'; ctx.fillText("ID: ".concat(draftPreviewId), canvas.width / 2, canvas.height / 2 + 15); } // Error message if provided if (error) { ctx.fillStyle = '#cc0000'; ctx.font = '14px Arial'; ctx.fillText(error, canvas.width / 2, canvas.height / 2 + 40); } return canvas.toDataURL('image/png'); } /** * Converts a data URL to a canvas element * * @param dataUrl The data URL to convert * @returns Promise that resolves to a Canvas element */ function dataUrlToCanvas(dataUrl) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, loadImage(dataUrl)]; case 1: // First load the data URL as an image return [2 /*return*/, _a.sent()]; } }); }); } /** * Process mapped previews, replacing layers in PSD files with rendered layers * and converting the results to PNG images * * @param mappedPreviews Array of mapped previews with draftPreview and draftPreviewLayers * @returns Promise that resolves to an array of processed previews with PNG data URLs */ function renderDraftPreviewsWithLayers(mappedPreviews) { var _a; return __awaiter(this, void 0, void 0, function () { 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; return __generator(this, function (_e) { switch (_e.label) { case 0: results = []; _i = 0, mappedPreviews_1 = mappedPreviews; _e.label = 1; case 1: if (!(_i < mappedPreviews_1.length)) return [3 /*break*/, 14]; mappedPreview = mappedPreviews_1[_i]; draftPreview = mappedPreview.draftPreview, draftPreviewLayers = mappedPreview.draftPreviewLayers; // Skip if there's no file to process if (!((_a = draftPreview === null || draftPreview === void 0 ? void 0 : draftPreview.file) === null || _a === void 0 ? void 0 : _a.viewUrl)) { console.warn('Skipping preview without a file URL', draftPreview === null || draftPreview === void 0 ? void 0 : draftPreview.id); return [3 /*break*/, 13]; } hasRenderedLayers = draftPreviewLayers.some(function (layer) { return layer.renderedLayer !== null; }); simplePngDataUrl = null; _e.label = 2; case 2: _e.trys.push([2, 12, , 13]); return [4 /*yield*/, fetch(draftPreview.file.viewUrl, { mode: 'cors', credentials: 'same-origin', headers: { 'Accept': '*/*', } })]; case 3: response = _e.sent(); if (!response.ok) { throw new Error("Failed to fetch PSD file: ".concat(response.status, " ").concat(response.statusText)); } return [4 /*yield*/, response.arrayBuffer()]; case 4: arrayBuffer = _e.sent(); firstBytes = new Uint8Array(arrayBuffer, 0, 4); signature = String.fromCharCode(firstBytes[0]) + String.fromCharCode(firstBytes[1]) + String.fromCharCode(firstBytes[2]) + String.fromCharCode(firstBytes[3]); if (signature !== '8BPS') { // Use the simple approach result if we have it if (simplePngDataUrl) { results.push({ draftPreviewId: draftPreview.id, pngDataUrl: simplePngDataUrl }); return [3 /*break*/, 13]; } throw new Error('Not a PSD file and simple approach failed'); } psd = (0, ag_psd_1.readPsd)(arrayBuffer, { skipCompositeImageData: false, skipLayerImageData: false, skipThumbnail: false }); canvas = document.createElement('canvas'); canvas.width = psd.width || 500; // Default size if missing canvas.height = psd.height || 400; ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get canvas 2d context'); } // Draw a background to ensure we have something visible ctx.fillStyle = 'transparent'; ctx.fillRect(0, 0, canvas.width, canvas.height); // First draw the composite image as a base if (psd.imageData) { imageData_1 = new ImageData(new Uint8ClampedArray(psd.imageData.data), psd.width, psd.height); ctx.putImageData(imageData_1, 0, 0); } // Now draw base layers that aren't being replaced if (psd.children && psd.children.length > 0) { _loop_1 = function (layer) { // Skip if this layer is hidden or matches one of our replaceable layers var shouldReplace = draftPreviewLayers.some(function (dpl) { return dpl.layerName === layer.name && dpl.renderedLayer !== null; }); if (!shouldReplace && !layer.hidden && layer.canvas) { // Draw the original layer ctx.drawImage(layer.canvas, layer.left || 0, layer.top || 0); ctx.globalAlpha = 1; } }; for (_b = 0, _c = psd.children; _b < _c.length; _b++) { layer = _c[_b]; _loop_1(layer); } } if (!hasRenderedLayers) return [3 /*break*/, 10]; _d = 0, draftPreviewLayers_1 = draftPreviewLayers; _e.label = 5; case 5: if (!(_d < draftPreviewLayers_1.length)) return [3 /*break*/, 10]; draftPreviewLayer = draftPreviewLayers_1[_d]; // Skip if no layer name or no rendered layer if (!draftPreviewLayer.layerName || !draftPreviewLayer.renderedLayer) { return [3 /*break*/, 9]; } psdLayer = findLayerByName(psd.children, draftPreviewLayer.layerName); if (!psdLayer) { console.warn("Layer \"".concat(draftPreviewLayer.layerName, "\" not found in PSD")); return [3 /*break*/, 9]; } _e.label = 6; case 6: _e.trys.push([6, 8, , 9]); // Validate the rendered layer image URL if (!draftPreviewLayer.renderedLayer.draft || !draftPreviewLayer.renderedLayer.draft.startsWith('data:image/')) { throw new Error('Invalid image URL format'); } return [4 /*yield*/, dataUrlToCanvas(draftPreviewLayer.renderedLayer.draft)]; case 7: layerCanvas = _e.sent(); // Validate the canvas dimensions if (layerCanvas.width <= 0 || layerCanvas.height <= 0) { console.error('Canvas has invalid dimensions'); throw new Error('Invalid canvas dimensions'); } left = psdLayer.left || 0; top_1 = psdLayer.top || 0; width = (psdLayer.right || 0) - (psdLayer.left || 0); height = (psdLayer.bottom || 0) - (psdLayer.top || 0); if (width <= 0 || height <= 0) { console.warn("Invalid layer dimensions: ".concat(width, "x").concat(height, ", skipping")); return [3 /*break*/, 9]; } aspectRatio = layerCanvas.width / layerCanvas.height; targetAspectRatio = width / height; drawWidth = void 0, drawHeight = void 0, offsetX = 0, offsetY = 0; if (aspectRatio > targetAspectRatio) { // Canvas is wider than target area drawWidth = width; drawHeight = width / aspectRatio; offsetY = (height - drawHeight) / 2; } else { // Canvas is taller than target area drawHeight = height; drawWidth = height * aspectRatio; offsetX = (width - drawWidth) / 2; } // Draw the rendered layer canvas // ctx.globalAlpha = psdLayer.opacity !== undefined ? psdLayer.opacity / 255 : 1; ctx.restore(); ctx.drawImage(layerCanvas, left + offsetX, top_1 + offsetY, drawWidth, drawHeight); ctx.globalAlpha = 1; ctx.save(); return [3 /*break*/, 9]; case 8: error_1 = _e.sent(); console.error("Failed to load rendered layer image for \"".concat(draftPreviewLayer.layerName, "\""), error_1); // If we fail to load the replacement, draw the original layer as fallback if (psdLayer.canvas) { ctx.globalAlpha = psdLayer.opacity !== undefined ? psdLayer.opacity / 255 : 1; ctx.drawImage(psdLayer.canvas, psdLayer.left || 0, psdLayer.top || 0); ctx.globalAlpha = 1; } return [3 /*break*/, 9]; case 9: _d++; return [3 /*break*/, 5]; case 10: imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); hasVisiblePixels = false; for (i = 3; i < imageData.data.length; i += 4) { if (imageData.data[i] > 0) { hasVisiblePixels = true; break; } } if (!hasVisiblePixels) { console.warn('WARNING: Canvas appears to be completely transparent!'); // Use the simple approach result if available if (simplePngDataUrl) { results.push({ draftPreviewId: draftPreview.id, pngDataUrl: simplePngDataUrl }); return [3 /*break*/, 13]; } // As a last resort, draw a visible placeholder ctx.fillStyle = 'rgba(200, 200, 200, 0.5)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(150, 150, 150, 1)'; ctx.font = '20px Arial'; ctx.textAlign = 'center'; ctx.fillText('Preview', canvas.width / 2, canvas.height / 2); } pngDataUrl = canvas.toDataURL('image/png'); return [4 /*yield*/, isValidImageDataUrl(pngDataUrl)]; case 11: isValid = _e.sent(); if (!isValid) { console.warn('Generated PNG data URL is not valid!'); // Use the simple approach as fallback if available if (simplePngDataUrl) { results.push({ draftPreviewId: draftPreview.id, pngDataUrl: simplePngDataUrl }); } else { fallbackPng = createFallbackImage(500, 400, draftPreview.id, 'Invalid PNG data'); results.push({ draftPreviewId: draftPreview.id, pngDataUrl: fallbackPng }); } } else { // Add the result to our array results.push({ draftPreviewId: draftPreview.id, pngDataUrl: pngDataUrl }); } return [3 /*break*/, 13]; case 12: error_2 = _e.sent(); console.error('Failed to process PSD file for draft preview:', error_2); // Use the simple approach as fallback if available if (simplePngDataUrl) { results.push({ draftPreviewId: draftPreview.id, pngDataUrl: simplePngDataUrl }); } else { errorMessage = error_2 instanceof Error ? error_2.message : 'Unknown error'; fallbackPng = createFallbackImage(500, 400, draftPreview.id, errorMessage); results.push({ draftPreviewId: draftPreview.id, pngDataUrl: fallbackPng }); } return [3 /*break*/, 13]; case 13: _i++; return [3 /*break*/, 1]; case 14: return [2 /*return*/, results]; } }); }); } exports.renderDraftPreviewsWithLayers = renderDraftPreviewsWithLayers;