merchi_product_editor
Version:
A React component for editing product images using Fabric.js
343 lines (342 loc) • 16.3 kB
JavaScript
;
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.renderClippedImage = exports.renderCanvasWithoutGrid = exports.addTextToCanvas = void 0;
var fabric_1 = require("fabric");
/**
* Adds an editable text object to the canvas
* @param canvas - The Fabric.js canvas instance
* @param width - The width of the canvas
* @param height - The height of the canvas
* @param defaultText - Optional default text (defaults to "Text")
* @param fontSize - Optional font size (defaults to 24)
* @param fontFamily - Optional font family (defaults to Arial)
*/
var addTextToCanvas = function (canvas, width, height, defaultText, fontSize, fontFamily) {
if (defaultText === void 0) { defaultText = 'Text'; }
if (fontSize === void 0) { fontSize = 24; }
if (fontFamily === void 0) { fontFamily = 'Nunito'; }
if (!canvas)
return null;
try {
// Safe check if canvas is still valid
if (!canvas.getElement() || !canvas.getElement().parentNode) {
return null;
}
// Create a new text object with minimal options
var text_1 = new fabric_1.fabric.IText(defaultText, {
left: width / 2,
top: height / 2,
fontFamily: fontFamily,
fontSize: fontSize,
fill: '#000000',
originX: 'center',
originY: 'center',
selectable: true,
editable: false
});
// Add the text to the canvas
canvas.add(text_1);
// Ensure the text is rendered
canvas.renderAll();
// Make the text active - this is critical
var setActiveTextObject = function () {
try {
// Check if canvas is still valid
if (canvas && canvas.getElement() && canvas.getElement().parentNode) {
canvas.setActiveObject(text_1);
canvas.renderAll();
}
}
catch (err) {
console.error('Error setting active text object:', err);
}
};
// Initial selection
setActiveTextObject();
// Delayed selection to ensure text becomes active
setTimeout(setActiveTextObject, 50);
return text_1;
}
catch (error) {
console.error('Error adding text to canvas:', error);
return null;
}
};
exports.addTextToCanvas = addTextToCanvas;
/**
* Renders the entire canvas as an image without displaying grid elements
*
* @param canvas - The Fabric.js canvas instance
* @param options - Optional rendering options
* @returns A promise that resolves to the dataURL of the canvas without grid
*/
var renderCanvasWithoutGrid = function (canvas, options) {
return new Promise(function (resolve) {
if (!canvas) {
console.error('Canvas is null or undefined');
resolve(null);
return;
}
try {
// Get all objects on the canvas
var objects = canvas.getObjects();
// Store original visibility of each object
var originalVisibilities_1 = objects.map(function (obj) { return ({
object: obj,
visible: obj.visible
}); });
// Hide grid elements - typically grid elements might have a specific ID or class
// You may need to adjust this logic based on how grid elements are identified
objects.forEach(function (obj) {
var _a, _b, _c, _d, _e;
// Hide objects that represent grid elements
// This assumes grid elements have a property like 'type' or 'id' that identifies them
if (((_a = obj.id) === null || _a === void 0 ? void 0 : _a.includes('grid')) ||
obj.type === 'grid' ||
obj.isGridElement ||
(
// Add more comprehensive grid detection
(_b = obj.data) === null || _b === void 0 ? void 0 : _b.isGrid) ||
((_c = obj.name) === null || _c === void 0 ? void 0 : _c.includes('grid')) ||
((_d = obj.className) === null || _d === void 0 ? void 0 : _d.includes('grid')) ||
// Check for common grid-related properties
obj.strokeDashArray || // Dashed lines are often used for grids
// Check for line objects with evenly spaced positions (typical grid pattern)
(obj.type === 'line' && (obj.x1 === obj.x2 || obj.y1 === obj.y2)) ||
(
// Check for objects tagged with grid-related classes
(_e = obj.classes) === null || _e === void 0 ? void 0 : _e.includes('grid')) ||
// Check for objects with certain grid styling
(obj.stroke &&
(obj.strokeWidth === 0.5 || obj.strokeWidth === 1) &&
(obj.stroke === '#ddd' || obj.stroke === '#eee' || obj.stroke === 'rgba(0,0,0,0.1)'))) {
obj.visible = false;
}
});
// Save the original selection state
var originalSelectionBorder = canvas.selectionBorderColor;
var originalSelectionColor = canvas.selectionColor;
var originalActiveObject = canvas.getActiveObject();
// Temporarily disable selection indicators
canvas.selectionBorderColor = 'transparent';
canvas.selectionColor = 'transparent';
canvas.discardActiveObject();
// Apply any background color if specified
var originalBackgroundColor = canvas.backgroundColor;
if ((options === null || options === void 0 ? void 0 : options.backgroundColor) !== undefined) {
canvas.backgroundColor = options.backgroundColor || 'transparent';
}
// Render the canvas
canvas.renderAll();
// Get rendering options
var format = (options === null || options === void 0 ? void 0 : options.format) || 'png';
var quality = (options === null || options === void 0 ? void 0 : options.quality) || 1;
var multiplier = (options === null || options === void 0 ? void 0 : options.multiplier) || 1;
// Create a data URL from the canvas
try {
var dataUrl = canvas.toDataURL({
format: format,
quality: quality,
multiplier: multiplier
});
// Restore the original state
objects.forEach(function (obj, index) {
obj.visible = originalVisibilities_1[index].visible;
});
// Restore selection state
canvas.selectionBorderColor = originalSelectionBorder;
canvas.selectionColor = originalSelectionColor;
if (originalActiveObject) {
canvas.setActiveObject(originalActiveObject);
}
// Restore background color
canvas.backgroundColor = originalBackgroundColor;
// Render with original state
canvas.renderAll();
resolve(dataUrl);
}
catch (error) {
console.error('Error generating data URL:', error);
resolve(null);
}
}
catch (error) {
console.error('Error rendering canvas without grid:', error);
resolve(null);
}
});
};
exports.renderCanvasWithoutGrid = renderCanvasWithoutGrid;
/**
* Renders an image containing only what's visible inside a clipPath on the canvas
*
* @param canvas - The Fabric.js canvas instance
* @param options - Optional rendering options
* @returns A promise that resolves to the dataURL of the contents inside the clipPath, or null if no clipped object is found
*/
var renderClippedImage = function (canvas, options) {
return new Promise(function (resolve) {
if (!canvas) {
console.error('Canvas is null or undefined');
resolve(null);
return;
}
try {
// Find objects with clipPath
var objects = canvas.getObjects();
var targetObject = null;
targetObject = objects.find(function (obj) { return obj.id === 'design-boundary-rect'; }) || null;
if (!targetObject) {
console.warn('No boundary object found');
resolve(null);
return;
}
// Get bounds of the object with clipPath
var objBounds = targetObject.getBoundingRect();
// Create a new canvas element
var dpr = window.devicePixelRatio || 1;
var tempCanvasEl = document.createElement('canvas');
tempCanvasEl.width = objBounds.width * dpr;
tempCanvasEl.height = objBounds.height * dpr;
tempCanvasEl.style.width = "".concat(objBounds.width, "px");
tempCanvasEl.style.height = "".concat(objBounds.height, "px");
// Explicitly set alpha to true to support transparency
var tempCtx = tempCanvasEl.getContext('2d', { alpha: true });
tempCtx && tempCtx.scale(dpr, dpr);
if (!tempCtx) {
console.error('Could not get canvas context');
resolve(null);
return;
}
// Clear the canvas to ensure transparency
tempCtx.clearRect(0, 0, tempCanvasEl.width, tempCanvasEl.height);
// Set background color if provided, otherwise keep transparent
if (options === null || options === void 0 ? void 0 : options.backgroundColor) {
tempCtx.fillStyle = options.backgroundColor;
tempCtx.fillRect(0, 0, tempCanvasEl.width, tempCanvasEl.height);
}
// Save current canvas state
var originalZoom = canvas.getZoom();
var originalViewportTransform = canvas.viewportTransform ? __spreadArray([], canvas.viewportTransform, true) : [1, 0, 0, 1, 0, 0];
// Save the original background color of the canvas
var originalBackgroundColor = canvas.backgroundColor;
// Temporarily set canvas background to transparent
canvas.backgroundColor = 'transparent';
// Store original object positions
var originalPositions = objects.map(function (obj) { return ({
object: obj,
left: obj.left,
top: obj.top,
scaleX: obj.scaleX,
scaleY: obj.scaleY
}); });
// Temporarily modify the canvas view to focus on our object
canvas.setZoom(1);
canvas.setViewportTransform([1, 0, 0, 1, -objBounds.left, -objBounds.top]);
// Temporarily hide all objects except our target and its clipped content
var originalVisibilities = objects.map(function (obj) { return ({ object: obj, visible: obj.visible }); });
objects.forEach(function (obj) {
if (!obj.fieldId) {
obj.visible = false;
}
});
// Temporarily disable selection borders at canvas level
var originalSelectionBorder = canvas.selectionBorderColor;
var originalSelectionColor = canvas.selectionColor;
canvas.selectionBorderColor = 'transparent';
canvas.selectionColor = 'transparent';
// Temporarily disable selection borders at object level
var originalObjectStates = objects.map(function (obj) { return ({
object: obj,
borderColor: obj.borderColor,
cornerColor: obj.cornerColor,
cornerStrokeColor: obj.cornerStrokeColor,
transparentCorners: obj.transparentCorners,
hasControls: obj.hasControls,
hasBorders: obj.hasBorders
}); });
objects.forEach(function (obj) {
obj.borderColor = 'transparent';
obj.cornerColor = 'transparent';
obj.cornerStrokeColor = 'transparent';
obj.transparentCorners = true;
obj.hasControls = false;
obj.hasBorders = false;
});
// Render just the isolated object
canvas.renderAll();
// Draw the current canvas view to our temp canvas
tempCtx.drawImage(canvas.getElement(), 0, 0, objBounds.width * dpr, objBounds.height * dpr, 0, 0, objBounds.width, objBounds.height);
// Restore the original object positions
originalPositions.forEach(function (item) {
item.object.left = item.left;
item.object.top = item.top;
item.object.scaleX = item.scaleX;
item.object.scaleY = item.scaleY;
item.object.setCoords();
});
// Restore the original canvas state
canvas.setZoom(originalZoom);
canvas.setViewportTransform(originalViewportTransform);
canvas.backgroundColor = originalBackgroundColor;
originalVisibilities.forEach(function (item) {
item.object.visible = item.visible;
});
// Restore object-level selection borders
originalObjectStates.forEach(function (state) {
state.object.borderColor = state.borderColor;
state.object.cornerColor = state.cornerColor;
state.object.cornerStrokeColor = state.cornerStrokeColor;
state.object.transparentCorners = state.transparentCorners;
state.object.hasControls = state.hasControls;
state.object.hasBorders = state.hasBorders;
});
// Restore canvas-level selection borders
canvas.selectionBorderColor = originalSelectionBorder;
canvas.selectionColor = originalSelectionColor;
canvas.renderAll();
// Get the data URL from the temp canvas
var format = (options === null || options === void 0 ? void 0 : options.format) || 'png';
var quality = (options === null || options === void 0 ? void 0 : options.quality) || 1;
// Create a new canvas for final rendering with proper transparency
var finalCanvas = document.createElement('canvas');
finalCanvas.width = tempCanvasEl.width;
finalCanvas.height = tempCanvasEl.height;
var finalCtx = finalCanvas.getContext('2d', { alpha: true });
finalCtx && finalCtx.scale(1, 1);
if (!finalCtx) {
console.error('Could not get final canvas context');
resolve(null);
return;
}
// Clear the final canvas to ensure transparency
finalCtx.clearRect(0, 0, finalCanvas.width, finalCanvas.height);
// Draw the temp canvas content to the final canvas
finalCtx.drawImage(tempCanvasEl, 0, 0);
// Try to get the data URL from the final canvas
try {
// Ensure PNG format for transparency support
var dataUrl = finalCanvas.toDataURL("image/".concat(format === 'jpg' ? 'png' : format), quality);
resolve(dataUrl);
}
catch (error) {
console.error('Error getting data URL:', error);
resolve(null);
}
}
catch (error) {
console.error('Error rendering clipped image:', error);
resolve(null);
}
});
};
exports.renderClippedImage = renderClippedImage;