image-converter-client/app/routes/CropSelector.tsx

165 lines
5.2 KiB
TypeScript
Raw Normal View History

2024-11-07 19:19:02 +00:00
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
2024-11-02 22:38:33 +00:00
import DragableBox from "./DragableBox";
2024-11-07 19:19:02 +00:00
import { Coordinates } from "~/types";
2024-11-02 22:38:33 +00:00
2024-11-07 19:19:02 +00:00
type ContainerDimensions = {
height: number;
width: number;
2024-11-03 17:34:21 +00:00
}
2024-11-02 22:38:33 +00:00
export default function CropSelector() {
2024-11-03 17:34:21 +00:00
const [firstDragable, setFirstDragable] = useState<Coordinates>({
2024-11-02 22:38:33 +00:00
x: 0,
y: 0
2024-11-03 21:39:34 +00:00
});
2024-11-03 17:34:21 +00:00
const [secondDragable, setSecondDragable] = useState<Coordinates>({
2024-11-03 21:39:34 +00:00
x: 100,
y: 100
});
const [selectorPosition, setSelectorPosition] = useState<Coordinates>({
2024-11-02 22:38:33 +00:00
x: 0,
y: 0
2024-11-03 21:39:34 +00:00
});
2024-11-07 19:19:02 +00:00
const [containerSize, setContainerSize] = useState<ContainerDimensions>({
height: 0,
width: 0
});
const [userInputs, setUserInputs] = useState<ContainerDimensions>({
height: 100,
width: 100
});
2024-11-02 22:38:33 +00:00
2024-11-03 21:39:34 +00:00
const containerRef = useRef<HTMLDivElement>(null);
2024-11-03 20:56:34 +00:00
2024-11-02 22:38:33 +00:00
useEffect(() => {
2024-11-03 21:39:34 +00:00
setSelectorPosition({
x: Math.min(firstDragable.x, secondDragable.x),
y: Math.min(firstDragable.y, secondDragable.y)
});
}, [firstDragable, secondDragable]);
2024-11-03 20:56:34 +00:00
2024-11-07 19:19:02 +00:00
useEffect(() => {
if (!containerRef.current) return;
const resizeObserver = new ResizeObserver(entries => {
const entry = entries[0];
setContainerSize({
width: entry.contentRect.width,
height: entry.contentRect.height
});
});
resizeObserver.observe(containerRef.current);
return () => resizeObserver.disconnect();
}, [containerRef]);
2024-11-03 21:39:34 +00:00
const selectorWidth = Math.abs(secondDragable.x - firstDragable.x);
const selectorHeight = Math.abs(secondDragable.y - firstDragable.y);
2024-11-03 20:56:34 +00:00
2024-11-03 21:39:34 +00:00
const handleSelectorDrag = (newPosition: Coordinates) => {
const deltaX = newPosition.x - selectorPosition.x;
const deltaY = newPosition.y - selectorPosition.y;
2024-11-02 22:38:33 +00:00
2024-11-07 19:19:02 +00:00
const newFirstX = Math.min(firstDragable.x, secondDragable.x) + deltaX;
const newFirstY = Math.min(firstDragable.y, secondDragable.y) + deltaY;
const newSecondX = Math.max(firstDragable.x, secondDragable.x) + deltaX;
const newSecondY = Math.max(firstDragable.y, secondDragable.y) + deltaY;
2024-11-03 17:34:21 +00:00
2024-11-07 19:19:02 +00:00
// Ensure the selector stays within container bounds
if (newFirstX >= 0 && newSecondX <= containerSize.width &&
newFirstY >= 0 && newSecondY <= containerSize.height) {
setFirstDragable({
x: newFirstX,
y: newFirstY
});
setSecondDragable({
x: newSecondX,
y: newSecondY
});
}
};
const handleDimensionChange = (
dimension: 'width' | 'height',
event: React.ChangeEvent<HTMLInputElement>
) => {
const value = Math.max(0, parseInt(event.target.value) || 0);
const maxValue = dimension === 'width' ? containerSize.width : containerSize.height;
const clampedValue = Math.min(value, maxValue);
setUserInputs(prev => ({
...prev,
[dimension]: clampedValue
}));
// Update selector dimensions
if (dimension === 'width') {
const newX = Math.min(firstDragable.x, containerSize.width - clampedValue);
setFirstDragable(prev => ({ ...prev, x: newX }));
setSecondDragable(prev => ({ ...prev, x: newX + clampedValue }));
} else {
const newY = Math.min(firstDragable.y, containerSize.height - clampedValue);
setFirstDragable(prev => ({ ...prev, y: newY }));
setSecondDragable(prev => ({ ...prev, y: newY + clampedValue }));
}
2024-11-03 21:39:34 +00:00
};
2024-11-02 22:38:33 +00:00
2024-11-07 19:19:02 +00:00
// Update user inputs when dragging
useEffect(() => {
setUserInputs({
width: Math.round(selectorWidth),
height: Math.round(selectorHeight)
});
}, [selectorWidth, selectorHeight]);
2024-11-02 22:38:33 +00:00
return (
2024-11-07 19:19:02 +00:00
<div className="flex flex-col h-screen w-screen justify-center items-center gap-4">
2024-11-03 21:41:10 +00:00
<div ref={containerRef} className="relative w-1/3 h-1/3 border-black border">
<DragableBox
position={selectorPosition}
mode="box"
width={selectorWidth}
height={selectorHeight}
coordSetter={setSelectorPosition}
onDrag={handleSelectorDrag}
className="border border-black"
/>
<DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle">
2024-11-07 19:19:02 +00:00
<div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
2024-11-03 21:41:10 +00:00
</DragableBox>
<DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle">
2024-11-07 19:19:02 +00:00
<div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
2024-11-03 21:41:10 +00:00
</DragableBox>
</div>
2024-11-07 19:19:02 +00:00
<div className="flex gap-4">
<div className="flex flex-col gap-2">
<label htmlFor="width">Width (px)</label>
<input
id="width"
type="number"
min={0}
max={containerSize.width}
value={userInputs.width.toFixed()}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleDimensionChange('width', e)}
className="w-24"
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="height">Height (px)</label>
<input
id="height"
type="number"
min={0}
max={containerSize.height}
value={userInputs.height.toFixed()}
onChange={(e) => handleDimensionChange('height', e)}
className="w-24"
/>
</div>
</div>
2024-11-02 22:38:33 +00:00
</div>
);
}