fixed layout canvas including elements

This commit is contained in:
Codex Agent
2025-10-31 23:20:52 +01:00
parent eb0c31c90b
commit 81cdee428e
3 changed files with 230 additions and 219 deletions

View File

@@ -45,7 +45,7 @@ import {
normalizeElements,
payloadToElements,
} from './invite-layout/schema';
import { CanvasScaleControl, DesignerCanvas } from './invite-layout/DesignerCanvas';
import { DesignerCanvas } from './invite-layout/DesignerCanvas';
import { CANVAS_HEIGHT, CANVAS_WIDTH } from './invite-layout/schema';
import {
generatePdfBytes,
@@ -181,9 +181,6 @@ type InviteLayoutCustomizerPanelProps = {
};
const MAX_INSTRUCTIONS = 5;
const MIN_CANVAS_SCALE = 0.15;
const MAX_CANVAS_SCALE = 0.85;
const SCALE_EPSILON = 0.005;
export function InviteLayoutCustomizerPanel({
invite,
@@ -215,8 +212,6 @@ export function InviteLayoutCustomizerPanel({
const [printBusy, setPrintBusy] = React.useState(false);
const [elements, setElements] = React.useState<LayoutElement[]>([]);
const [activeElementId, setActiveElementId] = React.useState<string | null>(null);
const [canvasScale, setCanvasScale] = React.useState(0.45);
const [autoScaleEnabled, setAutoScaleEnabled] = React.useState(true);
const [showFloatingActions, setShowFloatingActions] = React.useState(false);
const actionsSentinelRef = React.useRef<HTMLDivElement | null>(null);
const historyRef = React.useRef<LayoutElement[][]>([]);
@@ -345,26 +340,6 @@ export function InviteLayoutCustomizerPanel({
const prevFormRef = React.useRef(form);
const initializedLayoutsRef = React.useRef<Record<string, boolean>>({});
const prevInviteRef = React.useRef<number | string | null>(null);
const clampScale = React.useCallback(
(value: number) => Math.min(MAX_CANVAS_SCALE, Math.max(MIN_CANVAS_SCALE, value)),
[],
);
const updateAutoScale = React.useCallback(() => {
if (!autoScaleEnabled) {
return;
}
const viewport = designerViewportRef.current;
const availableWidth = viewport?.clientWidth ?? 0;
const widthScale = availableWidth > 0 ? availableWidth / CANVAS_WIDTH : NaN;
const heightBudget = Math.max(window.innerHeight * 0.75 - 48, 200);
const heightScale = heightBudget / CANVAS_HEIGHT;
const resolvedWidthScale = Number.isFinite(widthScale) ? widthScale : Number.POSITIVE_INFINITY;
const candidate = Math.min(resolvedWidthScale, heightScale, MAX_CANVAS_SCALE);
const targetScale = clampScale(Number.isFinite(candidate) ? candidate : canvasScale);
setCanvasScale((prev) => (Math.abs(prev - targetScale) > SCALE_EPSILON ? targetScale : prev));
}, [autoScaleEnabled, canvasScale, clampScale]);
const activeLayout = React.useMemo(() => {
if (!availableLayouts.length) {
return null;
@@ -431,9 +406,7 @@ export function InviteLayoutCustomizerPanel({
resetHistory(defaults);
setActiveElementId(null);
initializedLayoutsRef.current[activeLayout.id] = true;
setAutoScaleEnabled(true);
updateAutoScale();
}, [activeLayout, eventName, activeLayoutQrSize, commitElements, elements, elementsAreEqual, resetHistory, updateAutoScale]);
}, [activeLayout, eventName, activeLayoutQrSize, commitElements, elements, elementsAreEqual, resetHistory]);
React.useEffect(() => {
if (!invite) {
@@ -454,7 +427,6 @@ export function InviteLayoutCustomizerPanel({
}
return layouts[0]?.id;
});
setAutoScaleEnabled(true);
}, [invite?.id, initialCustomization?.layout_id]);
React.useEffect(() => {
@@ -541,27 +513,6 @@ export function InviteLayoutCustomizerPanel({
});
}, [availableLayouts, initialCustomization?.layout_id]);
React.useEffect(() => {
updateAutoScale();
}, [updateAutoScale]);
React.useEffect(() => {
setAutoScaleEnabled(true);
updateAutoScale();
}, [activeLayout?.id, updateAutoScale]);
React.useEffect(() => {
const viewport = designerViewportRef.current;
if (!viewport || typeof ResizeObserver === 'undefined') {
return;
}
const observer = new ResizeObserver(() => updateAutoScale());
observer.observe(viewport);
return () => observer.disconnect();
}, [updateAutoScale]);
React.useEffect(() => {
if (!invite || !activeLayout) {
setForm({});
@@ -1751,36 +1702,27 @@ export function InviteLayoutCustomizerPanel({
<div ref={actionsSentinelRef} className="h-1 w-full" />
</form>
<div className="flex flex-col gap-4 rounded-2xl border border-[var(--tenant-border-strong)] bg-[var(--tenant-surface)] p-5 shadow-sm transition-colors">
<div className="flex flex-wrap items-center justify-between gap-3">
<CanvasScaleControl
scale={canvasScale}
onChange={(value) => {
setAutoScaleEnabled(false);
setCanvasScale(clampScale(value));
}}
/>
<div className="flex flex-wrap gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={handleUndo}
disabled={!canUndo}
>
<Undo2 className="mr-1 h-4 w-4" />
{t('invites.customizer.actions.undo', 'Rückgängig')}
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRedo}
disabled={!canRedo}
>
<Redo2 className="mr-1 h-4 w-4" />
{t('invites.customizer.actions.redo', 'Wiederholen')}
</Button>
</div>
<div className="flex flex-wrap items-center justify-end gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={handleUndo}
disabled={!canUndo}
>
<Undo2 className="mr-1 h-4 w-4" />
{t('invites.customizer.actions.undo', 'Rückgängig')}
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRedo}
disabled={!canRedo}
>
<Redo2 className="mr-1 h-4 w-4" />
{t('invites.customizer.actions.redo', 'Wiederholen')}
</Button>
</div>
<div className="flex justify-center">
@@ -1802,7 +1744,6 @@ export function InviteLayoutCustomizerPanel({
badge={form.badge_color ?? form.accent_color ?? '#2563EB'}
qrCodeDataUrl={qrCodeDataUrl}
logoDataUrl={form.logo_data_url ?? form.logo_url ?? null}
scale={canvasScale}
layoutKey={`designer:${invite?.id ?? 'unknown'}:${activeLayout?.id ?? 'default'}`}
/>
</div>