|
|
|
|
@@ -1,9 +1,11 @@
|
|
|
|
|
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
|
|
|
import { createPortal } from 'react-dom';
|
|
|
|
|
import { LabelTemplate, TemplateElement } from '../types';
|
|
|
|
|
import { mmToPx, MM_TO_PX, shouldRenderElement } from '../utils';
|
|
|
|
|
import { useIsNarrowScreen } from '../hooks/useIsMobile';
|
|
|
|
|
import { CanvasLabelImage } from './CanvasLabelImage';
|
|
|
|
|
import { ElementFloatPreview } from './ElementFloatPreview';
|
|
|
|
|
import { ElementFloatPreview, renderElementFloatPreview } from './ElementFloatPreview';
|
|
|
|
|
import { renderLabelToDataUrl } from '../labelRenderer';
|
|
|
|
|
import { useAppDialog } from './AppDialog';
|
|
|
|
|
import {
|
|
|
|
|
ZoomIn,
|
|
|
|
|
@@ -76,6 +78,9 @@ function applyCornerResize(
|
|
|
|
|
|
|
|
|
|
const MARQUEE_MIN_PX = 4;
|
|
|
|
|
const DRAG_START_THRESHOLD_PX = 4;
|
|
|
|
|
/** 松手前极短时间内的微小位移视为抖动,提交上一稳定位置 */
|
|
|
|
|
const DRAG_RELEASE_JITTER_PX = 4;
|
|
|
|
|
const DRAG_RELEASE_JITTER_MS = 48;
|
|
|
|
|
const MODERATE_ZOOM_MAX = 2.0;
|
|
|
|
|
|
|
|
|
|
function blurActiveField() {
|
|
|
|
|
@@ -219,7 +224,21 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
>(null);
|
|
|
|
|
const dragRafRef = useRef<number | null>(null);
|
|
|
|
|
const pendingDragElementsRef = useRef<TemplateElement[] | null>(null);
|
|
|
|
|
const pendingDragDeltaRef = useRef({ x: 0, y: 0 });
|
|
|
|
|
type DragCommitSnapshot = {
|
|
|
|
|
elements: TemplateElement[];
|
|
|
|
|
delta: { x: number; y: number };
|
|
|
|
|
time: number;
|
|
|
|
|
};
|
|
|
|
|
const dragCommitHistoryRef = useRef<DragCommitSnapshot | null>(null);
|
|
|
|
|
const dragCommitHistoryPrevRef = useRef<DragCommitSnapshot | null>(null);
|
|
|
|
|
const dragFloatLayerRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const canvasWrapperRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const interactionPortalRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const dragFloatImgRefs = useRef<Map<string, HTMLImageElement>>(new Map());
|
|
|
|
|
const dragFloatUrlsRef = useRef<Record<string, string>>({});
|
|
|
|
|
const dragTransformLoopRef = useRef<number | null>(null);
|
|
|
|
|
const [dragFloatElements, setDragFloatElements] = useState<TemplateElement[]>([]);
|
|
|
|
|
const interactionFloatPaintRef = useRef(0);
|
|
|
|
|
const interactionFloatTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
|
|
|
|
|
|
@@ -487,8 +506,18 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
if (dragFloatLayerRef.current) {
|
|
|
|
|
dragFloatLayerRef.current.style.transform = '';
|
|
|
|
|
}
|
|
|
|
|
if (dragTransformLoopRef.current !== null) {
|
|
|
|
|
cancelAnimationFrame(dragTransformLoopRef.current);
|
|
|
|
|
dragTransformLoopRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
setInteractionSnapshot(null);
|
|
|
|
|
setInteractionFloatElements(null);
|
|
|
|
|
setDragFloatElements([]);
|
|
|
|
|
dragFloatImgRefs.current.clear();
|
|
|
|
|
dragFloatUrlsRef.current = {};
|
|
|
|
|
pendingDragDeltaRef.current = { x: 0, y: 0 };
|
|
|
|
|
dragCommitHistoryRef.current = null;
|
|
|
|
|
dragCommitHistoryPrevRef.current = null;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const cancelActiveInteraction = useCallback(() => {
|
|
|
|
|
@@ -712,11 +741,45 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
if (!dragState) return;
|
|
|
|
|
dragMovedRef.current = false;
|
|
|
|
|
|
|
|
|
|
const syncInteractionPortalLayout = () => {
|
|
|
|
|
const wrapper = canvasWrapperRef.current;
|
|
|
|
|
const portal = interactionPortalRef.current;
|
|
|
|
|
if (wrapper && portal) {
|
|
|
|
|
const rect = wrapper.getBoundingClientRect();
|
|
|
|
|
portal.style.left = `${rect.left}px`;
|
|
|
|
|
portal.style.top = `${rect.top}px`;
|
|
|
|
|
portal.style.width = `${rect.width}px`;
|
|
|
|
|
portal.style.height = `${rect.height}px`;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const runDragTransformLoop = () => {
|
|
|
|
|
const tick = () => {
|
|
|
|
|
syncInteractionPortalLayout();
|
|
|
|
|
const layer = dragFloatLayerRef.current;
|
|
|
|
|
if (layer) {
|
|
|
|
|
const { x, y } = pendingDragDeltaRef.current;
|
|
|
|
|
layer.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
|
|
|
}
|
|
|
|
|
dragTransformLoopRef.current = requestAnimationFrame(tick);
|
|
|
|
|
};
|
|
|
|
|
dragTransformLoopRef.current = requestAnimationFrame(tick);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (dragState.type === 'drag') {
|
|
|
|
|
runDragTransformLoop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handlePointerMove = (e: PointerEvent) => {
|
|
|
|
|
if (multiTouchActiveRef.current) return;
|
|
|
|
|
|
|
|
|
|
const deltaX = e.clientX - dragState.startX;
|
|
|
|
|
const deltaY = e.clientY - dragState.startY;
|
|
|
|
|
|
|
|
|
|
if (dragState.type === 'drag') {
|
|
|
|
|
pendingDragDeltaRef.current = { x: deltaX, y: deltaY };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pastDragThreshold =
|
|
|
|
|
Math.abs(deltaX) > DRAG_START_THRESHOLD_PX || Math.abs(deltaY) > DRAG_START_THRESHOLD_PX;
|
|
|
|
|
|
|
|
|
|
@@ -744,18 +807,12 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
pendingDragElementsRef.current = updated;
|
|
|
|
|
if (dragFloatLayerRef.current) {
|
|
|
|
|
dragFloatLayerRef.current.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
|
|
|
}
|
|
|
|
|
if (dragRafRef.current === null) {
|
|
|
|
|
dragRafRef.current = requestAnimationFrame(() => {
|
|
|
|
|
dragRafRef.current = null;
|
|
|
|
|
const next = pendingDragElementsRef.current;
|
|
|
|
|
if (next) {
|
|
|
|
|
localElementsRef.current = next;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
dragCommitHistoryPrevRef.current = dragCommitHistoryRef.current;
|
|
|
|
|
dragCommitHistoryRef.current = {
|
|
|
|
|
elements: updated,
|
|
|
|
|
delta: { x: deltaX, y: deltaY },
|
|
|
|
|
time: performance.now(),
|
|
|
|
|
};
|
|
|
|
|
} else if (dragState.type === 'resize') {
|
|
|
|
|
const elem = template.elements.find((el) => el.id === dragState.elementId);
|
|
|
|
|
if (!elem || elem.locked) return;
|
|
|
|
|
@@ -821,6 +878,23 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resolveDragCommitElements = (): TemplateElement[] | null => {
|
|
|
|
|
if (dragState.type !== 'drag') {
|
|
|
|
|
return pendingDragElementsRef.current ?? localElementsRef.current;
|
|
|
|
|
}
|
|
|
|
|
const cur = dragCommitHistoryRef.current;
|
|
|
|
|
const prev = dragCommitHistoryPrevRef.current;
|
|
|
|
|
if (cur && prev) {
|
|
|
|
|
const micro = Math.hypot(cur.delta.x - prev.delta.x, cur.delta.y - prev.delta.y);
|
|
|
|
|
const dt = cur.time - prev.time;
|
|
|
|
|
if (micro > 0 && micro <= DRAG_RELEASE_JITTER_PX && dt <= DRAG_RELEASE_JITTER_MS) {
|
|
|
|
|
return prev.elements;
|
|
|
|
|
}
|
|
|
|
|
return cur.elements;
|
|
|
|
|
}
|
|
|
|
|
return cur?.elements ?? pendingDragElementsRef.current ?? localElementsRef.current;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePointerUp = () => {
|
|
|
|
|
if (multiTouchActiveRef.current) {
|
|
|
|
|
cancelActiveInteraction();
|
|
|
|
|
@@ -830,10 +904,11 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
cancelAnimationFrame(dragRafRef.current);
|
|
|
|
|
dragRafRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
if (pendingDragElementsRef.current) {
|
|
|
|
|
localElementsRef.current = pendingDragElementsRef.current;
|
|
|
|
|
pendingDragElementsRef.current = null;
|
|
|
|
|
const commitElements = resolveDragCommitElements();
|
|
|
|
|
if (commitElements) {
|
|
|
|
|
localElementsRef.current = commitElements;
|
|
|
|
|
}
|
|
|
|
|
pendingDragElementsRef.current = null;
|
|
|
|
|
if (localElementsRef.current) {
|
|
|
|
|
const shouldCommit =
|
|
|
|
|
dragState.type === 'resize' || dragMovedRef.current;
|
|
|
|
|
@@ -850,6 +925,10 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
window.addEventListener('pointerup', handlePointerUp);
|
|
|
|
|
window.addEventListener('pointercancel', handlePointerUp);
|
|
|
|
|
return () => {
|
|
|
|
|
if (dragTransformLoopRef.current !== null) {
|
|
|
|
|
cancelAnimationFrame(dragTransformLoopRef.current);
|
|
|
|
|
dragTransformLoopRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
window.removeEventListener('pointermove', handlePointerMove);
|
|
|
|
|
window.removeEventListener('pointerup', handlePointerUp);
|
|
|
|
|
window.removeEventListener('pointercancel', handlePointerUp);
|
|
|
|
|
@@ -954,9 +1033,22 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
if (element.locked) return;
|
|
|
|
|
|
|
|
|
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
|
|
|
const groupStartPositions = buildGroupStartPositions(element);
|
|
|
|
|
const moveIds = new Set(Object.keys(groupStartPositions));
|
|
|
|
|
const floating = template.elements.filter(
|
|
|
|
|
(el) =>
|
|
|
|
|
moveIds.has(el.id) &&
|
|
|
|
|
shouldRenderElement(el, null, 0, previewDefaults, 'design')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setInteractionSnapshot(template.elements);
|
|
|
|
|
setInteractionFloatElements(null);
|
|
|
|
|
interactionFloatPaintRef.current = 0;
|
|
|
|
|
dragFloatUrlsRef.current = {};
|
|
|
|
|
dragFloatImgRefs.current.clear();
|
|
|
|
|
dragCommitHistoryRef.current = null;
|
|
|
|
|
dragCommitHistoryPrevRef.current = null;
|
|
|
|
|
setDragFloatElements(floating);
|
|
|
|
|
setDragState({
|
|
|
|
|
elementId: element.id,
|
|
|
|
|
type: actionType,
|
|
|
|
|
@@ -966,8 +1058,34 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
startElemY: element.y,
|
|
|
|
|
startElemW: element.width,
|
|
|
|
|
startElemH: element.height,
|
|
|
|
|
groupStartPositions: buildGroupStartPositions(element),
|
|
|
|
|
groupStartPositions,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (actionType === 'drag' && floating.length > 0) {
|
|
|
|
|
void Promise.all(
|
|
|
|
|
floating.map(async (el) => {
|
|
|
|
|
try {
|
|
|
|
|
const url = await renderElementFloatPreview(
|
|
|
|
|
el,
|
|
|
|
|
canvasRenderScale,
|
|
|
|
|
previewDefaults
|
|
|
|
|
);
|
|
|
|
|
dragFloatUrlsRef.current[el.id] = url;
|
|
|
|
|
const img = dragFloatImgRefs.current.get(el.id);
|
|
|
|
|
if (img) {
|
|
|
|
|
img.src = url;
|
|
|
|
|
if (img.complete) {
|
|
|
|
|
img.style.opacity = '1';
|
|
|
|
|
const placeholder = img.previousElementSibling as HTMLElement | null;
|
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Drag float preview error:', err);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleResizeStart = (
|
|
|
|
|
@@ -1052,23 +1170,40 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
filterVisibleElements,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const dragFloatElements = useMemo(() => {
|
|
|
|
|
if (dragState?.type !== 'drag' || !interactionSnapshot || !dragMovingIdSet) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const moving = interactionSnapshot.filter((el) => dragMovingIdSet.has(el.id));
|
|
|
|
|
return moving.length > 0 ? filterVisibleElements(moving) : null;
|
|
|
|
|
}, [dragState?.type, interactionSnapshot, dragMovingIdSet, filterVisibleElements]);
|
|
|
|
|
|
|
|
|
|
const resizeFloatElements = useMemo(() => {
|
|
|
|
|
if (dragState?.type !== 'resize' || !interactionFloatElements?.length) return null;
|
|
|
|
|
return filterVisibleElements(interactionFloatElements);
|
|
|
|
|
}, [dragState?.type, interactionFloatElements, filterVisibleElements]);
|
|
|
|
|
|
|
|
|
|
const interactionFloatCanvasElements = dragFloatElements ?? resizeFloatElements;
|
|
|
|
|
const allowInteractionOverflow = dragState !== null;
|
|
|
|
|
|
|
|
|
|
const hideSelectionChrome = suppressSelectionChrome || dragState !== null;
|
|
|
|
|
const syncInteractionPortalLayout = useCallback(() => {
|
|
|
|
|
const wrapper = canvasWrapperRef.current;
|
|
|
|
|
const portal = interactionPortalRef.current;
|
|
|
|
|
if (!wrapper || !portal) return;
|
|
|
|
|
const rect = wrapper.getBoundingClientRect();
|
|
|
|
|
portal.style.left = `${rect.left}px`;
|
|
|
|
|
portal.style.top = `${rect.top}px`;
|
|
|
|
|
portal.style.width = `${rect.width}px`;
|
|
|
|
|
portal.style.height = `${rect.height}px`;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!allowInteractionOverflow) return;
|
|
|
|
|
syncInteractionPortalLayout();
|
|
|
|
|
window.addEventListener('scroll', syncInteractionPortalLayout, true);
|
|
|
|
|
window.addEventListener('resize', syncInteractionPortalLayout);
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('scroll', syncInteractionPortalLayout, true);
|
|
|
|
|
window.removeEventListener('resize', syncInteractionPortalLayout);
|
|
|
|
|
};
|
|
|
|
|
}, [allowInteractionOverflow, syncInteractionPortalLayout]);
|
|
|
|
|
|
|
|
|
|
const hideSelectionChrome = suppressSelectionChrome || dragState?.type === 'drag';
|
|
|
|
|
const resizingPreviewElement =
|
|
|
|
|
dragState?.type === 'resize'
|
|
|
|
|
? interactionFloatElements?.find((el) => el.id === dragState.elementId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const overlayElements = filterVisibleElements(template.elements);
|
|
|
|
|
|
|
|
|
|
@@ -1152,7 +1287,103 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const showInteractionFloatPortal =
|
|
|
|
|
typeof document !== 'undefined' &&
|
|
|
|
|
((dragState?.type === 'drag' && dragFloatElements.length > 0) ||
|
|
|
|
|
(resizeFloatElements && resizeFloatElements.length > 0));
|
|
|
|
|
|
|
|
|
|
const interactionFloatPortal = showInteractionFloatPortal
|
|
|
|
|
? createPortal(
|
|
|
|
|
<div
|
|
|
|
|
ref={interactionPortalRef}
|
|
|
|
|
className="pointer-events-none"
|
|
|
|
|
style={{
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
left: 0,
|
|
|
|
|
top: 0,
|
|
|
|
|
width: 0,
|
|
|
|
|
height: 0,
|
|
|
|
|
overflow: 'visible',
|
|
|
|
|
zIndex: 10000,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{dragState?.type === 'drag' && dragFloatElements.length > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
ref={dragFloatLayerRef}
|
|
|
|
|
className="absolute inset-0 pointer-events-none"
|
|
|
|
|
style={{
|
|
|
|
|
overflow: 'visible',
|
|
|
|
|
willChange: 'transform',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{dragFloatElements.map((el) => (
|
|
|
|
|
<div
|
|
|
|
|
key={el.id}
|
|
|
|
|
className="absolute pointer-events-none"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${mmToPx(el.x, displayZoom)}px`,
|
|
|
|
|
top: `${mmToPx(el.y, displayZoom)}px`,
|
|
|
|
|
width: `${mmToPx(el.width, displayZoom)}px`,
|
|
|
|
|
height: `${mmToPx(el.height, displayZoom)}px`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
data-drag-float-placeholder={el.id}
|
|
|
|
|
className="absolute inset-0 bg-white border border-[#31a8ff]/40 shadow-[0_1px_4px_rgba(0,0,0,0.12)]"
|
|
|
|
|
/>
|
|
|
|
|
<img
|
|
|
|
|
ref={(node) => {
|
|
|
|
|
if (node) {
|
|
|
|
|
dragFloatImgRefs.current.set(el.id, node);
|
|
|
|
|
const url = dragFloatUrlsRef.current[el.id];
|
|
|
|
|
if (url) {
|
|
|
|
|
node.src = url;
|
|
|
|
|
if (node.complete) {
|
|
|
|
|
node.style.opacity = '1';
|
|
|
|
|
const placeholder = node.previousElementSibling as HTMLElement | null;
|
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
dragFloatImgRefs.current.delete(el.id);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
alt=""
|
|
|
|
|
className="absolute inset-0 w-full h-full pointer-events-none select-none opacity-0"
|
|
|
|
|
onLoad={(e) => {
|
|
|
|
|
const img = e.currentTarget;
|
|
|
|
|
img.style.opacity = '1';
|
|
|
|
|
const placeholder = img.previousElementSibling as HTMLElement | null;
|
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{resizeFloatElements && resizeFloatElements.length > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
className="absolute inset-0 pointer-events-none"
|
|
|
|
|
style={{ overflow: 'visible' }}
|
|
|
|
|
>
|
|
|
|
|
{resizeFloatElements.map((el) => (
|
|
|
|
|
<ElementFloatPreview
|
|
|
|
|
key={el.id}
|
|
|
|
|
element={el}
|
|
|
|
|
renderScale={canvasRenderScale}
|
|
|
|
|
displayZoom={displayZoom}
|
|
|
|
|
variableDefaults={previewDefaults}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>,
|
|
|
|
|
document.body
|
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div
|
|
|
|
|
className={
|
|
|
|
|
isMobileLayout
|
|
|
|
|
@@ -1189,6 +1420,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
ref={canvasWrapperRef}
|
|
|
|
|
className="relative shrink-0 grow-0"
|
|
|
|
|
style={{
|
|
|
|
|
width: `${canvasDisplayW}px`,
|
|
|
|
|
@@ -1252,17 +1484,22 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
dragState?.type === 'drag' && !!dragMovingIdSet?.has(element.id);
|
|
|
|
|
const isBeingResized =
|
|
|
|
|
dragState?.type === 'resize' && dragState.elementId === element.id;
|
|
|
|
|
if (isBeingDragged || isBeingResized) {
|
|
|
|
|
if (isBeingDragged) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const overlayElement =
|
|
|
|
|
isBeingResized && resizingPreviewElement ? resizingPreviewElement : element;
|
|
|
|
|
|
|
|
|
|
const showSelectionRing = isSelected && !hideSelectionChrome;
|
|
|
|
|
const showResizeHandles =
|
|
|
|
|
showSelectionRing && selectedElementIds.length === 1;
|
|
|
|
|
const elemX = mmToPx(element.x, displayZoom);
|
|
|
|
|
const elemY = mmToPx(element.y, displayZoom);
|
|
|
|
|
const elemW = mmToPx(element.width, displayZoom);
|
|
|
|
|
const elemH = mmToPx(element.height, displayZoom);
|
|
|
|
|
showSelectionRing &&
|
|
|
|
|
selectedElementIds.length === 1 &&
|
|
|
|
|
dragState?.type !== 'resize';
|
|
|
|
|
const elemX = mmToPx(overlayElement.x, displayZoom);
|
|
|
|
|
const elemY = mmToPx(overlayElement.y, displayZoom);
|
|
|
|
|
const elemW = mmToPx(overlayElement.width, displayZoom);
|
|
|
|
|
const elemH = mmToPx(overlayElement.height, displayZoom);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
@@ -1282,12 +1519,21 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
top: `${elemY}px`,
|
|
|
|
|
width: `${elemW}px`,
|
|
|
|
|
height: `${elemH}px`,
|
|
|
|
|
zIndex: isSelected ? index + 1000 : index + 10,
|
|
|
|
|
transform: element.rotation ? `rotate(${element.rotation}deg)` : undefined,
|
|
|
|
|
zIndex: isBeingResized
|
|
|
|
|
? index + 2000
|
|
|
|
|
: isSelected
|
|
|
|
|
? index + 1000
|
|
|
|
|
: index + 10,
|
|
|
|
|
transform: overlayElement.rotation
|
|
|
|
|
? `rotate(${overlayElement.rotation}deg)`
|
|
|
|
|
: undefined,
|
|
|
|
|
transformOrigin: 'center center',
|
|
|
|
|
pointerEvents: isBeingResized ? 'none' : undefined,
|
|
|
|
|
}}
|
|
|
|
|
onPointerDown={
|
|
|
|
|
isLocked ? undefined : (e) => handleElementPointerDown(e, element, 'drag')
|
|
|
|
|
isLocked || isBeingResized
|
|
|
|
|
? undefined
|
|
|
|
|
: (e) => handleElementPointerDown(e, element, 'drag')
|
|
|
|
|
}
|
|
|
|
|
onClick={isLocked ? undefined : (e) => handleElementClick(e, element)}
|
|
|
|
|
>
|
|
|
|
|
@@ -1336,28 +1582,6 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{interactionFloatCanvasElements && interactionFloatCanvasElements.length > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
ref={dragFloatLayerRef}
|
|
|
|
|
className="absolute top-0 left-0 pointer-events-none z-[10]"
|
|
|
|
|
style={{
|
|
|
|
|
width: `${canvasDisplayW}px`,
|
|
|
|
|
height: `${canvasDisplayH}px`,
|
|
|
|
|
overflow: 'visible',
|
|
|
|
|
willChange: dragState?.type === 'drag' ? 'transform' : undefined,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{interactionFloatCanvasElements.map((el) => (
|
|
|
|
|
<ElementFloatPreview
|
|
|
|
|
key={el.id}
|
|
|
|
|
element={el}
|
|
|
|
|
renderScale={canvasRenderScale}
|
|
|
|
|
displayZoom={displayZoom}
|
|
|
|
|
variableDefaults={previewDefaults}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -1420,5 +1644,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{interactionFloatPortal}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|