fix: stabilize enhance preview compare
This commit is contained in:
parent
82dda143eb
commit
8a722ed849
@ -160,13 +160,6 @@ function clampNumber(value: number, min: number, max: number) {
|
|||||||
return Math.min(max, Math.max(min, value));
|
return Math.min(max, Math.max(min, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEnhanceCompareControlTarget(target: EventTarget | null) {
|
|
||||||
return (
|
|
||||||
target instanceof HTMLElement &&
|
|
||||||
Boolean(target.closest("[data-enhance-pro-compare-control='true']"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fitDimensions(width: number, height: number, maxOutputEdge: number) {
|
function fitDimensions(width: number, height: number, maxOutputEdge: number) {
|
||||||
const safeWidth = Math.max(1, Math.round(width || 1024));
|
const safeWidth = Math.max(1, Math.round(width || 1024));
|
||||||
const safeHeight = Math.max(1, Math.round(height || 1024));
|
const safeHeight = Math.max(1, Math.round(height || 1024));
|
||||||
@ -403,16 +396,8 @@ export function EnhanceProWorkspace({
|
|||||||
const [settingsOpen, setSettingsOpen] = useState(true);
|
const [settingsOpen, setSettingsOpen] = useState(true);
|
||||||
const [adjustmentsOpen, setAdjustmentsOpen] = useState(true);
|
const [adjustmentsOpen, setAdjustmentsOpen] = useState(true);
|
||||||
const [faceOpen, setFaceOpen] = useState(true);
|
const [faceOpen, setFaceOpen] = useState(true);
|
||||||
const [previewOffset, setPreviewOffset] = useState({ x: 0, y: 0 });
|
|
||||||
const [comparePosition, setComparePosition] = useState(50);
|
const [comparePosition, setComparePosition] = useState(50);
|
||||||
const [isPreviewDragging, setIsPreviewDragging] = useState(false);
|
const compareDragPointerIdRef = useRef<number | null>(null);
|
||||||
const previewDragStateRef = useRef<{
|
|
||||||
pointerId: number;
|
|
||||||
startX: number;
|
|
||||||
startY: number;
|
|
||||||
originX: number;
|
|
||||||
originY: number;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const { data: configPayload, isLoading } = useSWR<{
|
const { data: configPayload, isLoading } = useSWR<{
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
@ -523,12 +508,6 @@ export function EnhanceProWorkspace({
|
|||||||
setLastErrorMessage("");
|
setLastErrorMessage("");
|
||||||
}, [sourceImageUrl]);
|
}, [sourceImageUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPreviewOffset({ x: 0, y: 0 });
|
|
||||||
setIsPreviewDragging(false);
|
|
||||||
previewDragStateRef.current = null;
|
|
||||||
}, [activePreviewUrl]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setComparePosition(50);
|
setComparePosition(50);
|
||||||
}, [selectedResultIndex]);
|
}, [selectedResultIndex]);
|
||||||
@ -682,67 +661,43 @@ export function EnhanceProWorkspace({
|
|||||||
return items;
|
return items;
|
||||||
}, [generatedImages, sourceImageUrl]);
|
}, [generatedImages, sourceImageUrl]);
|
||||||
|
|
||||||
const handlePreviewPointerDown = (
|
const updateComparePositionFromPointer = (
|
||||||
event: ReactPointerEvent<HTMLDivElement>
|
event: ReactPointerEvent<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
if (!activePreviewUrl || event.button !== 0) {
|
const rect = event.currentTarget.getBoundingClientRect();
|
||||||
return;
|
if (rect.width <= 0) return;
|
||||||
}
|
const nextPosition = ((event.clientX - rect.left) / rect.width) * 100;
|
||||||
if (isEnhanceCompareControlTarget(event.target)) {
|
setComparePosition(clampNumber(nextPosition, 0, 100));
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
previewDragStateRef.current = {
|
const handleComparePointerDown = (
|
||||||
pointerId: event.pointerId,
|
event: ReactPointerEvent<HTMLDivElement>
|
||||||
startX: event.clientX,
|
) => {
|
||||||
startY: event.clientY,
|
if (event.button !== 0) return;
|
||||||
originX: previewOffset.x,
|
event.preventDefault();
|
||||||
originY: previewOffset.y,
|
event.stopPropagation();
|
||||||
};
|
compareDragPointerIdRef.current = event.pointerId;
|
||||||
setIsPreviewDragging(true);
|
|
||||||
event.currentTarget.setPointerCapture(event.pointerId);
|
event.currentTarget.setPointerCapture(event.pointerId);
|
||||||
|
updateComparePositionFromPointer(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePreviewPointerMove = (
|
const handleComparePointerMove = (
|
||||||
event: ReactPointerEvent<HTMLDivElement>
|
event: ReactPointerEvent<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
const dragState = previewDragStateRef.current;
|
if (compareDragPointerIdRef.current !== event.pointerId) return;
|
||||||
if (!dragState || dragState.pointerId !== event.pointerId) {
|
event.preventDefault();
|
||||||
return;
|
event.stopPropagation();
|
||||||
}
|
updateComparePositionFromPointer(event);
|
||||||
|
|
||||||
const maxOffsetX = Math.max(48, event.currentTarget.clientWidth * 0.18);
|
|
||||||
const maxOffsetY = Math.max(48, event.currentTarget.clientHeight * 0.18);
|
|
||||||
|
|
||||||
setPreviewOffset({
|
|
||||||
x: clampNumber(
|
|
||||||
dragState.originX + event.clientX - dragState.startX,
|
|
||||||
-maxOffsetX,
|
|
||||||
maxOffsetX
|
|
||||||
),
|
|
||||||
y: clampNumber(
|
|
||||||
dragState.originY + event.clientY - dragState.startY,
|
|
||||||
-maxOffsetY,
|
|
||||||
maxOffsetY
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const finishPreviewDrag = (event?: ReactPointerEvent<HTMLDivElement>) => {
|
const finishCompareDrag = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||||
const dragState = previewDragStateRef.current;
|
if (compareDragPointerIdRef.current !== event.pointerId) return;
|
||||||
if (!dragState) {
|
event.preventDefault();
|
||||||
return;
|
event.stopPropagation();
|
||||||
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||||
|
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||||
}
|
}
|
||||||
|
compareDragPointerIdRef.current = null;
|
||||||
if (
|
|
||||||
event &&
|
|
||||||
event.currentTarget.hasPointerCapture(dragState.pointerId)
|
|
||||||
) {
|
|
||||||
event.currentTarget.releasePointerCapture(dragState.pointerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
previewDragStateRef.current = null;
|
|
||||||
setIsPreviewDragging(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -751,24 +706,9 @@ export function EnhanceProWorkspace({
|
|||||||
<div className="pointer-events-none absolute inset-0 bg-[#ffffff]" />
|
<div className="pointer-events-none absolute inset-0 bg-[#ffffff]" />
|
||||||
<div className="pattern-grid-lg pointer-events-none absolute inset-0 opacity-[0.025] [mask-image:radial-gradient(circle_at_center,black_38%,transparent_82%)]" />
|
<div className="pattern-grid-lg pointer-events-none absolute inset-0 opacity-[0.025] [mask-image:radial-gradient(circle_at_center,black_38%,transparent_82%)]" />
|
||||||
{activePreviewUrl ? (
|
{activePreviewUrl ? (
|
||||||
<div
|
<div className="relative flex h-full w-full min-w-0 select-none items-center justify-center overflow-hidden">
|
||||||
className={cn(
|
|
||||||
"relative flex h-full w-full min-w-0 touch-none select-none items-center justify-center overflow-hidden",
|
|
||||||
isPreviewDragging ? "cursor-grabbing" : "cursor-grab"
|
|
||||||
)}
|
|
||||||
onDoubleClick={() => setPreviewOffset({ x: 0, y: 0 })}
|
|
||||||
onPointerCancel={finishPreviewDrag}
|
|
||||||
onPointerDown={handlePreviewPointerDown}
|
|
||||||
onPointerMove={handlePreviewPointerMove}
|
|
||||||
onPointerUp={finishPreviewDrag}
|
|
||||||
>
|
|
||||||
<div className="relative flex h-full w-full min-w-0 items-center justify-center overflow-hidden px-4 py-4">
|
<div className="relative flex h-full w-full min-w-0 items-center justify-center overflow-hidden px-4 py-4">
|
||||||
<div
|
<div className="relative h-full w-full max-w-[min(1320px,calc(100vw-30rem))] overflow-hidden">
|
||||||
className="relative h-full w-full max-w-[min(1320px,calc(100vw-30rem))] overflow-hidden"
|
|
||||||
style={{
|
|
||||||
transform: `translate(${previewOffset.x}px, ${previewOffset.y}px)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
alt={activePreviewLabel}
|
alt={activePreviewLabel}
|
||||||
className="pointer-events-none absolute inset-0 h-full w-full object-contain transition-transform duration-75"
|
className="pointer-events-none absolute inset-0 h-full w-full object-contain transition-transform duration-75"
|
||||||
@ -792,6 +732,25 @@ export function EnhanceProWorkspace({
|
|||||||
className="pointer-events-none absolute inset-y-0 w-px bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.32),0_0_18px_rgba(0,0,0,0.35)]"
|
className="pointer-events-none absolute inset-y-0 w-px bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.32),0_0_18px_rgba(0,0,0,0.35)]"
|
||||||
style={{ left: `${comparePosition}%` }}
|
style={{ left: `${comparePosition}%` }}
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className="pointer-events-auto absolute inset-0 cursor-col-resize touch-none"
|
||||||
|
data-enhance-pro-compare-control="true"
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onPointerCancel={finishCompareDrag}
|
||||||
|
onPointerDown={handleComparePointerDown}
|
||||||
|
onPointerMove={handleComparePointerMove}
|
||||||
|
onPointerUp={finishCompareDrag}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute inset-y-0 w-12 -translate-x-1/2"
|
||||||
|
style={{ left: `${comparePosition}%` }}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.25),0_0_18px_rgba(0,0,0,0.3)]" />
|
||||||
|
<div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 flex h-9 w-9 items-center justify-center rounded-full border border-white/80 bg-black/70 text-white shadow-[0_12px_24px_rgba(0,0,0,0.28)] backdrop-blur-sm">
|
||||||
|
<SlidersHorizontal className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{showComparisonPreview ? (
|
{showComparisonPreview ? (
|
||||||
@ -802,38 +761,6 @@ export function EnhanceProWorkspace({
|
|||||||
<div className="pointer-events-none absolute top-3 right-3 rounded-full bg-black/65 px-2.5 py-1 font-medium text-[11px] text-white backdrop-blur-sm">
|
<div className="pointer-events-none absolute top-3 right-3 rounded-full bg-black/65 px-2.5 py-1 font-medium text-[11px] text-white backdrop-blur-sm">
|
||||||
增强后
|
增强后
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="pointer-events-auto absolute right-6 bottom-5 left-6 h-8 cursor-col-resize rounded-full"
|
|
||||||
data-enhance-pro-compare-control="true"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
|
||||||
onPointerMove={(event) => event.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div className="-translate-y-1/2 pointer-events-none absolute top-1/2 right-0 left-0 h-1 rounded-full bg-white/55 shadow-[0_0_0_1px_rgba(0,0,0,0.2)]">
|
|
||||||
<div
|
|
||||||
className="h-full rounded-full bg-[#111111]"
|
|
||||||
style={{ width: `${comparePosition}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute top-1/2 h-5 w-5 rounded-full border-2 border-white bg-[#111111] shadow-[0_8px_18px_rgba(0,0,0,0.28)]"
|
|
||||||
style={{ left: `${comparePosition}%` }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
aria-label="原图和增强结果对比滑块"
|
|
||||||
className="absolute inset-0 h-full w-full cursor-col-resize opacity-0"
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
onChange={(event) =>
|
|
||||||
setComparePosition(Number(event.target.value) || 0)
|
|
||||||
}
|
|
||||||
onInput={(event) =>
|
|
||||||
setComparePosition(Number(event.currentTarget.value) || 0)
|
|
||||||
}
|
|
||||||
type="range"
|
|
||||||
value={comparePosition}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -843,7 +770,7 @@ export function EnhanceProWorkspace({
|
|||||||
<span>{activePreviewLabel}</span>
|
<span>{activePreviewLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="pointer-events-none absolute top-6 right-8 hidden items-center gap-2 rounded-full border border-[#e5e5e5] bg-white/96 px-3 py-1.5 text-[#666666] text-xs shadow-[0_0_0_1px_rgba(0,0,0,0.03),0_10px_24px_rgba(0,0,0,0.04)] lg:inline-flex">
|
<div className="pointer-events-none absolute top-6 right-8 hidden items-center gap-2 rounded-full border border-[#e5e5e5] bg-white/96 px-3 py-1.5 text-[#666666] text-xs shadow-[0_0_0_1px_rgba(0,0,0,0.03),0_10px_24px_rgba(0,0,0,0.04)] lg:inline-flex">
|
||||||
双击回正
|
图片固定显示
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user