verbesserung von benachrichtungen und warnungen an nutzer abgeschlossen. layout editor nun auf gutem stand.
This commit is contained in:
@@ -622,6 +622,8 @@ export function InviteLayoutCustomizerPanel({
|
||||
if (!invite || !activeLayout) {
|
||||
setForm({});
|
||||
setInstructions([]);
|
||||
commitElements(() => [], { silent: true });
|
||||
resetHistory([]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -635,7 +637,7 @@ export function InviteLayoutCustomizerPanel({
|
||||
|
||||
setInstructions(baseInstructions);
|
||||
|
||||
setForm({
|
||||
const newForm: QrLayoutCustomization = {
|
||||
layout_id: activeLayout.id,
|
||||
headline: reuseCustomization ? initialCustomization?.headline ?? eventName : eventName,
|
||||
subtitle: reuseCustomization ? initialCustomization?.subtitle ?? activeLayout.subtitle ?? '' : activeLayout.subtitle ?? '',
|
||||
@@ -652,53 +654,35 @@ export function InviteLayoutCustomizerPanel({
|
||||
badge_color: reuseCustomization ? initialCustomization?.badge_color ?? '#2563EB' : '#2563EB',
|
||||
background_gradient: reuseCustomization ? initialCustomization?.background_gradient ?? activeLayout.preview?.background_gradient ?? null : activeLayout.preview?.background_gradient ?? null,
|
||||
logo_data_url: reuseCustomization ? initialCustomization?.logo_data_url ?? initialCustomization?.logo_url ?? null : null,
|
||||
});
|
||||
mode: initialCustomization?.layout_id === activeLayout.id ? initialCustomization?.mode : 'standard',
|
||||
elements: initialCustomization?.layout_id === activeLayout.id ? initialCustomization?.elements : undefined,
|
||||
};
|
||||
setForm(newForm);
|
||||
setError(null);
|
||||
}, [invite?.id, activeLayout?.id, defaultInstructions, initialCustomization, eventName, inviteUrl, t]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!activeLayout) {
|
||||
const cleared: LayoutElement[] = [];
|
||||
commitElements(() => cleared, { silent: true });
|
||||
resetHistory(cleared);
|
||||
return;
|
||||
}
|
||||
const isCustomizedAdvanced = newForm.mode === 'advanced' && Array.isArray(newForm.elements) && newForm.elements.length > 0;
|
||||
|
||||
const layoutKey = activeLayout.id ?? '__default';
|
||||
const inviteKey = invite?.id ?? null;
|
||||
if (prevInviteRef.current !== inviteKey) {
|
||||
initializedLayoutsRef.current = {};
|
||||
prevInviteRef.current = inviteKey;
|
||||
if (isCustomizedAdvanced) {
|
||||
const initialElements = normalizeElements(payloadToElements(newForm.elements));
|
||||
commitElements(() => initialElements, { silent: true });
|
||||
resetHistory(initialElements);
|
||||
} else {
|
||||
const defaults = buildDefaultElements(activeLayout, newForm, eventName, activeLayoutQrSize);
|
||||
commitElements(() => defaults, { silent: true });
|
||||
resetHistory(defaults);
|
||||
}
|
||||
|
||||
if (!initializedLayoutsRef.current[layoutKey]) {
|
||||
if (initialCustomization?.mode === 'advanced' && Array.isArray(initialCustomization.elements) && initialCustomization.elements.length) {
|
||||
const initialElements = normalizeElements(payloadToElements(initialCustomization.elements));
|
||||
commitElements(() => initialElements, { silent: true });
|
||||
resetHistory(initialElements);
|
||||
} else {
|
||||
const defaults = buildDefaultElements(activeLayout, formStateRef.current, eventName, activeLayoutQrSize);
|
||||
commitElements(() => defaults, { silent: true });
|
||||
resetHistory(defaults);
|
||||
}
|
||||
initializedLayoutsRef.current[layoutKey] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (historyIndexRef.current === -1 && elements.length > 0) {
|
||||
resetHistory(cloneElements(elements));
|
||||
}
|
||||
}, [
|
||||
setActiveElementId(null);
|
||||
}, [
|
||||
activeLayout,
|
||||
invite?.id,
|
||||
initialCustomization,
|
||||
defaultInstructions,
|
||||
eventName,
|
||||
inviteUrl,
|
||||
t,
|
||||
activeLayoutQrSize,
|
||||
initialCustomization?.mode,
|
||||
initialCustomization?.elements,
|
||||
commitElements,
|
||||
resetHistory,
|
||||
elements,
|
||||
cloneElements,
|
||||
eventName,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -756,6 +740,17 @@ export function InviteLayoutCustomizerPanel({
|
||||
hasCustomization: Boolean(initialCustomization?.elements?.length),
|
||||
});
|
||||
|
||||
// Erweiterter Log für Duplikate-Check
|
||||
const idCounts = base.reduce((acc, e) => {
|
||||
acc[e.id] = (acc[e.id] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
const duplicates = Object.entries(idCounts).filter(([_, count]) => count > 1);
|
||||
if (duplicates.length > 0) {
|
||||
console.warn('[Invites][CanvasElements] Duplicates detected in base', { duplicates, baseIds: base.map(e => ({ id: e.id, type: e.type, y: e.y })) });
|
||||
}
|
||||
console.debug('[Invites][CanvasElements] Base IDs overview', base.map(e => ({ id: e.id, type: e.type, y: e.y })));
|
||||
|
||||
const boundContent: Record<string, string | null> = {
|
||||
headline: form.headline ?? eventName,
|
||||
subtitle: form.subtitle ?? '',
|
||||
@@ -783,7 +778,7 @@ export function InviteLayoutCustomizerPanel({
|
||||
};
|
||||
});
|
||||
}, [
|
||||
activeLayout,
|
||||
activeLayout?.id,
|
||||
elements,
|
||||
form.headline,
|
||||
form.subtitle,
|
||||
@@ -794,7 +789,6 @@ export function InviteLayoutCustomizerPanel({
|
||||
eventName,
|
||||
inviteUrl,
|
||||
t,
|
||||
activeLayout,
|
||||
activeLayoutQrSize,
|
||||
]);
|
||||
|
||||
@@ -1254,21 +1248,6 @@ export function InviteLayoutCustomizerPanel({
|
||||
|
||||
function handleLayoutSelect(layout: EventQrInviteLayout) {
|
||||
setSelectedLayoutId(layout.id);
|
||||
updateForm('layout_id', layout.id);
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
accent_color: sanitizeColor(layout.preview?.accent ?? prev.accent_color ?? null) ?? '#6366F1',
|
||||
text_color: sanitizeColor(layout.preview?.text ?? prev.text_color ?? null) ?? '#111827',
|
||||
background_color: sanitizeColor(layout.preview?.background ?? prev.background_color ?? null) ?? '#FFFFFF',
|
||||
secondary_color: '#1F2937',
|
||||
badge_color: '#2563EB',
|
||||
background_gradient: layout.preview?.background_gradient ?? null,
|
||||
}));
|
||||
setInstructions((layout.instructions ?? []).length ? [...(layout.instructions as string[])] : [...defaultInstructions]);
|
||||
const defaults = buildDefaultElements(layout, formStateRef.current, eventName, layout.preview?.qr_size_px ?? activeLayoutQrSize);
|
||||
commitElements(() => defaults, { silent: true });
|
||||
resetHistory(defaults);
|
||||
setActiveElementId(null);
|
||||
}
|
||||
|
||||
function handleInstructionChange(index: number, value: string) {
|
||||
|
||||
@@ -450,7 +450,7 @@ export async function renderFabricLayout(
|
||||
} = options;
|
||||
|
||||
canvas.discardActiveObject();
|
||||
canvas.getObjects().forEach((object) => canvas.remove(object));
|
||||
canvas.clear();
|
||||
|
||||
applyBackground(canvas, backgroundColor, backgroundGradient);
|
||||
|
||||
@@ -461,6 +461,7 @@ export async function renderFabricLayout(
|
||||
readOnly,
|
||||
});
|
||||
|
||||
const abortController = new AbortController();
|
||||
const objectPromises = elements.map((element) =>
|
||||
createFabricObject({
|
||||
element,
|
||||
@@ -471,10 +472,11 @@ export async function renderFabricLayout(
|
||||
qrCodeDataUrl,
|
||||
logoDataUrl,
|
||||
readOnly,
|
||||
}),
|
||||
}, abortController.signal),
|
||||
);
|
||||
|
||||
const fabricObjects = await Promise.all(objectPromises);
|
||||
abortController.abort(); // Abort any pending loads
|
||||
console.debug('[Invites][Fabric] resolved objects', {
|
||||
count: fabricObjects.length,
|
||||
nulls: fabricObjects.filter((obj) => !obj).length,
|
||||
@@ -765,8 +767,9 @@ export async function loadImageObject(
|
||||
element: LayoutElement,
|
||||
baseConfig: FabricObjectWithId,
|
||||
options?: { objectFit?: 'contain' | 'cover'; shadow?: string; padding?: number },
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<fabric.Object | null> {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false;
|
||||
const resolveSafely = (value: fabric.Object | null) => {
|
||||
if (resolved) {
|
||||
@@ -779,7 +782,7 @@ export async function loadImageObject(
|
||||
const isDataUrl = source.startsWith('data:');
|
||||
|
||||
const onImageLoaded = (img?: HTMLImageElement | HTMLCanvasElement | null) => {
|
||||
if (!img) {
|
||||
if (!img || resolved) {
|
||||
console.warn('[Invites][Fabric] image load returned empty', { source });
|
||||
resolveSafely(null);
|
||||
return;
|
||||
@@ -819,14 +822,26 @@ export async function loadImageObject(
|
||||
};
|
||||
|
||||
const onError = (error?: unknown) => {
|
||||
if (resolved) return;
|
||||
console.warn('[Invites][Fabric] failed to load image', source, error);
|
||||
resolveSafely(null);
|
||||
};
|
||||
|
||||
const abortHandler = () => {
|
||||
if (resolved) return;
|
||||
console.debug('[Invites][Fabric] Image load aborted', { source });
|
||||
resolveSafely(null);
|
||||
};
|
||||
|
||||
if (abortSignal) {
|
||||
abortSignal.addEventListener('abort', abortHandler);
|
||||
}
|
||||
|
||||
try {
|
||||
if (isDataUrl) {
|
||||
const imageElement = new Image();
|
||||
imageElement.onload = () => {
|
||||
if (resolved) return;
|
||||
console.debug('[Invites][Fabric] image loaded (data-url)', {
|
||||
source: source.slice(0, 48),
|
||||
width: imageElement.naturalWidth,
|
||||
@@ -840,6 +855,7 @@ export async function loadImageObject(
|
||||
// Use direct Image constructor approach for better compatibility
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
if (resolved) return;
|
||||
console.debug('[Invites][Fabric] image loaded', {
|
||||
source: source.slice(0, 48),
|
||||
width: img.width,
|
||||
@@ -854,7 +870,17 @@ export async function loadImageObject(
|
||||
onError(error);
|
||||
}
|
||||
|
||||
window.setTimeout(() => resolveSafely(null), 3000);
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (resolved) return;
|
||||
resolveSafely(null);
|
||||
}, 3000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
if (abortSignal) {
|
||||
abortSignal.removeEventListener('abort', abortHandler);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user