Enable foldable background presets
This commit is contained in:
@@ -23,6 +23,7 @@ type DesignerCanvasProps = {
|
||||
qrCodeDataUrl: string | null;
|
||||
logoDataUrl: string | null;
|
||||
backgroundImageUrl?: string | null;
|
||||
backgroundImagePanels?: BackgroundImagePanel[];
|
||||
scale?: number;
|
||||
readOnly?: boolean;
|
||||
layoutKey?: string;
|
||||
@@ -38,6 +39,7 @@ export function DesignerCanvas({
|
||||
background,
|
||||
gradient,
|
||||
backgroundImageUrl = null,
|
||||
backgroundImagePanels,
|
||||
accent,
|
||||
text,
|
||||
secondary,
|
||||
@@ -348,6 +350,7 @@ export function DesignerCanvas({
|
||||
background,
|
||||
gradient,
|
||||
backgroundImageUrl,
|
||||
backgroundImagePanels,
|
||||
readOnly,
|
||||
});
|
||||
|
||||
@@ -367,6 +370,7 @@ export function DesignerCanvas({
|
||||
backgroundColor: background,
|
||||
backgroundGradient: gradient,
|
||||
backgroundImageUrl,
|
||||
backgroundImagePanels,
|
||||
readOnly,
|
||||
}).catch((error) => {
|
||||
console.error('[Fabric] Failed to render layout', error);
|
||||
@@ -382,6 +386,7 @@ export function DesignerCanvas({
|
||||
background,
|
||||
gradient,
|
||||
backgroundImageUrl,
|
||||
backgroundImagePanels,
|
||||
readOnly,
|
||||
]);
|
||||
|
||||
@@ -458,9 +463,20 @@ export type FabricRenderOptions = {
|
||||
backgroundColor: string;
|
||||
backgroundGradient: { angle?: number; stops?: string[] } | null;
|
||||
backgroundImageUrl?: string | null;
|
||||
backgroundImagePanels?: BackgroundImagePanel[];
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
export type BackgroundImagePanel = {
|
||||
url: string;
|
||||
centerX: number;
|
||||
centerY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number;
|
||||
mirrored?: boolean;
|
||||
};
|
||||
|
||||
export async function renderFabricLayout(
|
||||
canvas: fabric.Canvas,
|
||||
options: FabricRenderOptions,
|
||||
@@ -476,6 +492,7 @@ export async function renderFabricLayout(
|
||||
backgroundColor,
|
||||
backgroundGradient,
|
||||
backgroundImageUrl,
|
||||
backgroundImagePanels,
|
||||
readOnly,
|
||||
} = options;
|
||||
|
||||
@@ -489,7 +506,7 @@ export async function renderFabricLayout(
|
||||
}
|
||||
canvas.clear();
|
||||
|
||||
await applyBackground(canvas, backgroundColor, backgroundGradient, backgroundImageUrl);
|
||||
await applyBackground(canvas, backgroundColor, backgroundGradient, backgroundImageUrl, backgroundImagePanels);
|
||||
|
||||
console.debug('[Invites][Fabric] render', {
|
||||
elementCount: elements.length,
|
||||
@@ -558,6 +575,7 @@ export async function applyBackground(
|
||||
color: string,
|
||||
gradient: { angle?: number; stops?: string[] } | null,
|
||||
backgroundImageUrl?: string | null,
|
||||
backgroundImagePanels?: BackgroundImagePanel[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (typeof canvas.setBackgroundImage === 'function') {
|
||||
@@ -568,25 +586,47 @@ export async function applyBackground(
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
|
||||
if (backgroundImagePanels?.length) {
|
||||
applyBackgroundFill(canvas, color, gradient);
|
||||
|
||||
const panelImages = await Promise.all(
|
||||
backgroundImagePanels.map(async (panel) => ({
|
||||
panel,
|
||||
element: await loadFabricImage(resolveAssetUrl(panel.url)),
|
||||
})),
|
||||
);
|
||||
|
||||
panelImages.forEach(({ panel, element }) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const needsSwap = Math.abs(panel.rotation) % 180 === 90;
|
||||
const targetWidth = needsSwap ? panel.height : panel.width;
|
||||
const targetHeight = needsSwap ? panel.width : panel.height;
|
||||
const scale = Math.max(targetWidth / (element.width || targetWidth), targetHeight / (element.height || targetHeight));
|
||||
const scaleX = panel.mirrored ? -scale : scale;
|
||||
const scaleY = scale;
|
||||
element.set({
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
left: panel.centerX,
|
||||
top: panel.centerY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
angle: panel.rotation,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
});
|
||||
canvas.add(element);
|
||||
});
|
||||
|
||||
canvas.requestRenderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (backgroundImageUrl) {
|
||||
try {
|
||||
const resolvedUrl = backgroundImageUrl.startsWith('http')
|
||||
? backgroundImageUrl
|
||||
: `${window.location.origin}${backgroundImageUrl.startsWith('/') ? '' : '/'}${backgroundImageUrl}`;
|
||||
const image = await new Promise<fabric.Image | null>((resolve) => {
|
||||
const imgEl = new Image();
|
||||
imgEl.crossOrigin = 'anonymous';
|
||||
const timeoutId = window.setTimeout(() => resolve(null), 3000);
|
||||
imgEl.onload = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
resolve(new fabric.Image(imgEl, { crossOrigin: 'anonymous' }));
|
||||
};
|
||||
imgEl.onerror = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
resolve(null);
|
||||
};
|
||||
imgEl.src = resolvedUrl;
|
||||
});
|
||||
const image = await loadFabricImage(resolveAssetUrl(backgroundImageUrl));
|
||||
if (image) {
|
||||
const scaleX = CANVAS_WIDTH / (image.width || CANVAS_WIDTH);
|
||||
const scaleY = CANVAS_HEIGHT / (image.height || CANVAS_HEIGHT);
|
||||
@@ -617,6 +657,14 @@ export async function applyBackground(
|
||||
console.warn('[Fabric] applyBackground failed', error);
|
||||
}
|
||||
|
||||
applyBackgroundFill(canvas, color, gradient);
|
||||
}
|
||||
|
||||
function applyBackgroundFill(
|
||||
canvas: fabric.Canvas,
|
||||
color: string,
|
||||
gradient: { angle?: number; stops?: string[] } | null,
|
||||
): void {
|
||||
let background: string | fabric.Gradient<'linear'> = color;
|
||||
|
||||
if (gradient?.stops?.length) {
|
||||
@@ -653,6 +701,30 @@ export async function applyBackground(
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFabricImage(url: string): Promise<fabric.Image | null> {
|
||||
return new Promise<fabric.Image | null>((resolve) => {
|
||||
const imgEl = new Image();
|
||||
imgEl.crossOrigin = 'anonymous';
|
||||
const timeoutId = window.setTimeout(() => resolve(null), 3000);
|
||||
imgEl.onload = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
resolve(new fabric.Image(imgEl, { crossOrigin: 'anonymous' }));
|
||||
};
|
||||
imgEl.onerror = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
resolve(null);
|
||||
};
|
||||
imgEl.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function resolveAssetUrl(url: string): string {
|
||||
if (url.startsWith('http')) {
|
||||
return url;
|
||||
}
|
||||
return `${window.location.origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||
}
|
||||
|
||||
export type FabricObjectFactoryContext = {
|
||||
element: LayoutElement;
|
||||
accentColor: string;
|
||||
|
||||
Reference in New Issue
Block a user