merchi_product_editor
Version:
A React component for editing product images using Fabric.js
219 lines (185 loc) • 8.22 kB
text/typescript
import { fabric } from 'fabric';
import { DraftTemplate } from '../types';
/**
* Loads a template image onto a canvas with enhanced error handling and fallbacks
*
* @param fabricCanvas The Fabric.js canvas instance
* @param template The template containing image information
* @param width The canvas width
* @param height The canvas height
* @returns Promise that resolves when the image is loaded
*/
export const loadRegularImagePromise = (
fabricCanvas: fabric.Canvas,
template: DraftTemplate,
width: number,
height: number,
): Promise<void> => {
// Get the canvas ID for tracking if it exists
const canvasId = (fabricCanvas as any).loadingId;
const trackingEnabled = !!canvasId;
return new Promise((resolve) => {
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
resolve();
return;
}
// Make sure template has a file with a viewUrl
if (!template.file || !template.file.viewUrl) {
console.error('Template has no viewUrl:', template);
resolve();
return;
}
const imageUrl = template.file.viewUrl;
// Check if this might be a problematic file format
const isPossiblyUnsupported = !imageUrl.match(/\.(jpg|jpeg|png|gif|svg|webp)$/i);
// If we have a thumbnailUrl or previewUrl, use that instead for unsupported formats
let urlToUse = imageUrl;
if (isPossiblyUnsupported) {
// Try to use alternative URLs if available
if (template.file?.thumbnailUrl) {
urlToUse = template.file.thumbnailUrl;
} else if (template.file?.previewUrl) {
urlToUse = template.file.previewUrl;
}
}
// Add error handling for image loading
fabric.Image.fromURL(
urlToUse,
(img: fabric.Image) => {
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed after image loaded, aborting');
resolve();
return;
}
if (!img || !img.width || !img.height) {
console.error('Failed to load image or image has invalid dimensions:', img);
// Try loading the image with a different approach - create an HTML image first
const imgElement = new Image();
imgElement.crossOrigin = 'anonymous';
imgElement.onload = () => {
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed after alternative image loaded, aborting');
resolve();
return;
}
const fabricImg = new fabric.Image(imgElement);
// Continue with the same scaling and positioning logic
const scale = Math.min(
width / fabricImg.width!,
height / fabricImg.height!
);
fabricImg.scale(scale);
fabricImg.set({
left: (width - fabricImg.width! * scale) / 2,
top: (height - fabricImg.height! * scale) / 2,
selectable: false,
evented: false,
});
// Check again before adding to canvas when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before adding alternative image, aborting');
resolve();
return;
}
fabricCanvas.add(fabricImg);
fabricCanvas.sendToBack(fabricImg);
// Final check before render when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before rendering alternative image, aborting');
resolve();
return;
}
try {
fabricCanvas.renderAll();
} catch (e) {
console.error('Error rendering canvas with alternative image:', e);
}
resolve();
};
imgElement.onerror = () => {
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed after image load error, aborting');
resolve();
return;
}
console.error('Alternative loading also failed. Using placeholder image.');
// Use a placeholder SVG as absolute fallback
const placeholderURL = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22600%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20600%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_1891%20text%20%7Bfill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A40pt%20%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder%22%3E%3Crect%20width%3D%22800%22%20height%3D%22600%22%20fill%3D%22%23EEEEEE%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22285%22%20y%3D%22300%22%3EThumbnail%20Unavailable%3C%2Ftext%3E%3Ctext%20x%3D%22205%22%20y%3D%22350%22%3ETemplate%3A%20' + template.name + '%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E';
fabric.Image.fromURL(placeholderURL, (placeholderImg) => {
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before adding placeholder image, aborting');
resolve();
return;
}
placeholderImg.set({
left: 0,
top: 0,
width: width,
height: height,
selectable: false,
evented: false,
});
fabricCanvas.add(placeholderImg);
// Final check before render when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before rendering placeholder image, aborting');
resolve();
return;
}
try {
fabricCanvas.renderAll();
} catch (e) {
console.error('Error rendering canvas with placeholder image:', e);
}
resolve();
});
};
imgElement.src = urlToUse;
return;
}
// Scale image to fit canvas while maintaining aspect ratio
const 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, // template image is not selectable
evented: false, // template image is not responsive to events
crossOrigin: 'anonymous',
});
// Check if canvas is still valid when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before adding image, aborting');
resolve();
return;
}
// Add the image to the canvas
fabricCanvas.add(img);
// Send the template image to the back
fabricCanvas.sendToBack(img);
// Final check before render when tracking is enabled
if (trackingEnabled && (fabricCanvas as any).loadingId !== canvasId) {
console.warn('Canvas changed before rendering, aborting');
resolve();
return;
}
try {
fabricCanvas.renderAll();
} catch (e) {
console.error('Error rendering canvas:', e);
}
resolve();
},
{ crossOrigin: 'anonymous' }
);
});
};