Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 130 additions & 1 deletion src/components/modals/DenoiseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri

const containerRef = useRef<HTMLDivElement>(null);
const lastMousePos = useRef({ x: 0, y: 0 });
const [isTouchDragging, setIsTouchDragging] = useState(false);
const touchState = useRef({
initialPinchDist: 0,
initialZoom: 1,
initialPanX: 0,
initialPanY: 0,
lastCenterX: 0,
lastCenterY: 0,
lastTouchX: 0,
lastTouchY: 0,
isPinching: false,
isPanning: false,
isSliding: false,
});

useEffect(() => {
if (!isDragging && !isResizingSlider) return;
Expand Down Expand Up @@ -107,9 +121,119 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri
setPan({ x: newPanX, y: newPanY });
};

const handleTouchStart = (e: React.TouchEvent) => {
if (e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
touchState.current = {
initialPinchDist: Math.hypot(dx, dy),
initialZoom: zoom,
initialPanX: pan.x,
initialPanY: pan.y,
lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2,
lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2,
lastTouchX: 0,
lastTouchY: 0,
isPinching: true,
isPanning: false,
isSliding: false,
};
} else if (e.touches.length === 1) {
const target = e.target as HTMLElement;
const isSlider = target.closest('[data-slider]');
if (isSlider) {
e.preventDefault();
touchState.current = {
...touchState.current,
isPinching: false,
isPanning: false,
isSliding: true,
};
setIsResizingSlider(true);
const rect = containerRef.current?.getBoundingClientRect();
if (rect) {
const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width));
setSliderPosition((x / rect.width) * 100);
}
} else {
e.preventDefault();
touchState.current = {
...touchState.current,
lastTouchX: e.touches[0].clientX,
lastTouchY: e.touches[0].clientY,
isPinching: false,
isPanning: true,
isSliding: false,
};
setIsTouchDragging(true);
}
}
};

const handleTouchMove = (e: React.TouchEvent) => {
if (touchState.current.isPinching && e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const dist = Math.hypot(dx, dy);
const scale = dist / touchState.current.initialPinchDist;
const newZoom = Math.min(Math.max(0.5, touchState.current.initialZoom * scale), 4);

const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
const cx = centerX - touchState.current.lastCenterX;
const cy = centerY - touchState.current.lastCenterY;

setZoom(newZoom);
setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy }));
touchState.current.lastCenterX = centerX;
touchState.current.lastCenterY = centerY;
} else if (touchState.current.isPanning && e.touches.length === 1) {
e.preventDefault();
const dx = e.touches[0].clientX - touchState.current.lastTouchX;
const dy = e.touches[0].clientY - touchState.current.lastTouchY;
setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy }));
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
} else if (touchState.current.isSliding && e.touches.length === 1) {
e.preventDefault();
const rect = containerRef.current?.getBoundingClientRect();
if (rect) {
const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width));
setSliderPosition((x / rect.width) * 100);
}
}
};

const handleTouchEnd = (e: React.TouchEvent) => {
if (e.touches.length === 0) {
touchState.current.isPinching = false;
touchState.current.isPanning = false;
touchState.current.isSliding = false;
setIsTouchDragging(false);
setIsResizingSlider(false);
} else if (e.touches.length === 1) {
touchState.current.isPinching = false;
const target = e.target as HTMLElement;
const isSlider = target.closest('[data-slider]');
if (isSlider) {
touchState.current.isSliding = true;
touchState.current.isPanning = false;
setIsResizingSlider(true);
} else {
touchState.current.isPanning = true;
touchState.current.isSliding = false;
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
setIsTouchDragging(true);
}
}
};

const imageTransformStyle = {
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
transition: isDragging || isResizingSlider ? 'none' : 'transform 0.1s ease-out',
transition: isDragging || isResizingSlider || isTouchDragging ? 'none' : 'transform 0.1s ease-out',
transformOrigin: 'center center',
};

Expand Down Expand Up @@ -143,8 +267,12 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri
<div
ref={containerRef}
className="flex-1 relative overflow-hidden cursor-grab active:cursor-grabbing select-none"
style={{ touchAction: 'none' as any }}
onMouseDown={handleMouseDown}
onWheel={handleWheel}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className="absolute inset-0 flex items-center justify-center overflow-hidden pointer-events-none">
<div className="origin-center" style={imageTransformStyle}>
Expand Down Expand Up @@ -177,6 +305,7 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri
className="absolute top-0 bottom-0 w-0.5 bg-white cursor-col-resize z-10 shadow-[0_0_8px_rgba(0,0,0,0.8)]"
style={{ left: `${sliderPosition}%` }}
onMouseDown={handleSliderMouseDown}
data-slider
>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-white rounded-full shadow-lg flex items-center justify-center gap-0.5">
<div className="w-0.5 h-3 bg-black/40 rounded-full"></div>
Expand Down
91 changes: 90 additions & 1 deletion src/components/modals/LensCorrectionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@
const [isDragging, setIsDragging] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const lastMousePos = useRef({ x: 0, y: 0 });
const [isTouchDragging, setIsTouchDragging] = useState(false);
const touchState = useRef({
initialPinchDist: 0,
initialZoom: 1,
initialPanX: 0,
initialPanY: 0,
lastCenterX: 0,
lastCenterY: 0,
lastTouchX: 0,
lastTouchY: 0,
isPinching: false,
isDragging: false,
});

const [modeBubbleStyle, setModeBubbleStyle] = useState({});
const isModeInitialAnimation = useRef(true);
Expand Down Expand Up @@ -227,6 +240,78 @@
setPan({ x: newPanX, y: newPanY });
};

const handleTouchStart = (e: React.TouchEvent) => {
if (e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
touchState.current = {
initialPinchDist: Math.hypot(dx, dy),
initialZoom: zoom,
initialPanX: pan.x,
initialPanY: pan.y,
lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2,
lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2,
lastTouchX: 0,
lastTouchY: 0,
isPinching: true,
isDragging: false,
};
} else if (e.touches.length === 1) {
e.preventDefault();
touchState.current = {
...touchState.current,
lastTouchX: e.touches[0].clientX,
lastTouchY: e.touches[0].clientY,
isPinching: false,
isDragging: true,
};
setIsTouchDragging(true);
}
};

const handleTouchMove = (e: React.TouchEvent) => {
if (touchState.current.isPinching && e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const dist = Math.hypot(dx, dy);
const scale = dist / touchState.current.initialPinchDist;
const newZoom = Math.min(Math.max(0.1, touchState.current.initialZoom * scale), 8);

const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
const cx = centerX - touchState.current.lastCenterX;
const cy = centerY - touchState.current.lastCenterY;

setZoom(newZoom);
setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy }));
touchState.current.lastCenterX = centerX;
touchState.current.lastCenterY = centerY;
} else if (touchState.current.isDragging && e.touches.length === 1) {
e.preventDefault();
const dx = e.touches[0].clientX - touchState.current.lastTouchX;
const dy = e.touches[0].clientY - touchState.current.lastTouchY;
setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy }));
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
}
};

const handleTouchEnd = (e: React.TouchEvent) => {
if (e.touches.length === 0) {
touchState.current.isPinching = false;
touchState.current.isDragging = false;
setIsTouchDragging(false);
} else if (e.touches.length === 1) {
touchState.current.isPinching = false;
touchState.current.isDragging = true;
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
setIsTouchDragging(true);
}
};

const handleResetZoom = (e?: React.MouseEvent) => {
e?.stopPropagation();
setZoom(1);
Expand Down Expand Up @@ -533,7 +618,7 @@

const imageTransformStyle = {
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
transition: isDragging ? 'none' : 'transform 0.1s ease-out',
transition: isDragging || isTouchDragging ? 'none' : 'transform 0.1s ease-out',
transformOrigin: 'center center',
};

Expand All @@ -550,7 +635,7 @@
}));
}, [myLenses, t]);

const autoDetectButtonContent = () => {

Check warning on line 638 in src/components/modals/LensCorrectionModal.tsx

View workflow job for this annotation

GitHub Actions / Frontend Lint & Format

'autoDetectButtonContent' is assigned a value but never used. Allowed unused vars must match /^_/u
switch (detectionStatus) {
case 'detecting':
return (
Expand Down Expand Up @@ -875,8 +960,12 @@
<div
ref={containerRef}
className="flex-1 relative overflow-hidden cursor-grab active:cursor-grabbing select-none"
style={{ touchAction: 'none' as any }}
onMouseDown={handleMouseDown}
onWheel={handleWheel}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div
className="absolute inset-0 opacity-20 pointer-events-none"
Expand Down
91 changes: 90 additions & 1 deletion src/components/modals/NegativeConversionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ export default function NegativeConversionModal({

const containerRef = useRef<HTMLDivElement>(null);
const lastMousePos = useRef({ x: 0, y: 0 });
const [isTouchDragging, setIsTouchDragging] = useState(false);
const touchState = useRef({
initialPinchDist: 0,
initialZoom: 1,
initialPanX: 0,
initialPanY: 0,
lastCenterX: 0,
lastCenterY: 0,
lastTouchX: 0,
lastTouchY: 0,
isPinching: false,
isDragging: false,
});
const [originalUrl, setOriginalUrl] = useState<string | null>(null);
const selectedImagePath = targetPaths.length > 0 ? targetPaths[0] : null;

Expand Down Expand Up @@ -191,9 +204,81 @@ export default function NegativeConversionModal({
}
};

const handleTouchStart = (e: React.TouchEvent) => {
if (e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
touchState.current = {
initialPinchDist: Math.hypot(dx, dy),
initialZoom: zoom,
initialPanX: pan.x,
initialPanY: pan.y,
lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2,
lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2,
lastTouchX: 0,
lastTouchY: 0,
isPinching: true,
isDragging: false,
};
} else if (e.touches.length === 1) {
e.preventDefault();
touchState.current = {
...touchState.current,
lastTouchX: e.touches[0].clientX,
lastTouchY: e.touches[0].clientY,
isPinching: false,
isDragging: true,
};
setIsTouchDragging(true);
}
};

const handleTouchMove = (e: React.TouchEvent) => {
if (touchState.current.isPinching && e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const dist = Math.hypot(dx, dy);
const scale = dist / touchState.current.initialPinchDist;
const newZoom = Math.min(Math.max(0.1, touchState.current.initialZoom * scale), 8);

const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
const cx = centerX - touchState.current.lastCenterX;
const cy = centerY - touchState.current.lastCenterY;

setZoom(newZoom);
setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy }));
touchState.current.lastCenterX = centerX;
touchState.current.lastCenterY = centerY;
} else if (touchState.current.isDragging && e.touches.length === 1) {
e.preventDefault();
const dx = e.touches[0].clientX - touchState.current.lastTouchX;
const dy = e.touches[0].clientY - touchState.current.lastTouchY;
setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy }));
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
}
};

const handleTouchEnd = (e: React.TouchEvent) => {
if (e.touches.length === 0) {
touchState.current.isPinching = false;
touchState.current.isDragging = false;
setIsTouchDragging(false);
} else if (e.touches.length === 1) {
touchState.current.isPinching = false;
touchState.current.isDragging = true;
touchState.current.lastTouchX = e.touches[0].clientX;
touchState.current.lastTouchY = e.touches[0].clientY;
setIsTouchDragging(true);
}
};

const imageTransformStyle = {
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
transition: isDragging ? 'none' : 'transform 0.1s ease-out',
transition: isDragging || isTouchDragging ? 'none' : 'transform 0.1s ease-out',
transformOrigin: 'center center',
};

Expand Down Expand Up @@ -326,8 +411,12 @@ export default function NegativeConversionModal({
<div
ref={containerRef}
className="flex-1 relative overflow-hidden cursor-grab active:cursor-grabbing select-none"
style={{ touchAction: 'none' as any }}
onMouseDown={handleMouseDown}
onWheel={handleWheel}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div
className="absolute inset-0 opacity-20 pointer-events-none"
Expand Down
Loading
Loading