christian
bc9cf1bf25
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s
165 lines
5.2 KiB
TypeScript
165 lines
5.2 KiB
TypeScript
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
|
import DragableBox from "./DragableBox";
|
|
import { Coordinates } from "~/types";
|
|
|
|
type ContainerDimensions = {
|
|
height: number;
|
|
width: number;
|
|
}
|
|
|
|
export default function CropSelector() {
|
|
const [firstDragable, setFirstDragable] = useState<Coordinates>({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
const [secondDragable, setSecondDragable] = useState<Coordinates>({
|
|
x: 100,
|
|
y: 100
|
|
});
|
|
const [selectorPosition, setSelectorPosition] = useState<Coordinates>({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
const [containerSize, setContainerSize] = useState<ContainerDimensions>({
|
|
height: 0,
|
|
width: 0
|
|
});
|
|
const [userInputs, setUserInputs] = useState<ContainerDimensions>({
|
|
height: 100,
|
|
width: 100
|
|
});
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
setSelectorPosition({
|
|
x: Math.min(firstDragable.x, secondDragable.x),
|
|
y: Math.min(firstDragable.y, secondDragable.y)
|
|
});
|
|
}, [firstDragable, secondDragable]);
|
|
|
|
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]);
|
|
|
|
const selectorWidth = Math.abs(secondDragable.x - firstDragable.x);
|
|
const selectorHeight = Math.abs(secondDragable.y - firstDragable.y);
|
|
|
|
const handleSelectorDrag = (newPosition: Coordinates) => {
|
|
const deltaX = newPosition.x - selectorPosition.x;
|
|
const deltaY = newPosition.y - selectorPosition.y;
|
|
|
|
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;
|
|
|
|
// 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 }));
|
|
}
|
|
};
|
|
|
|
// Update user inputs when dragging
|
|
useEffect(() => {
|
|
setUserInputs({
|
|
width: Math.round(selectorWidth),
|
|
height: Math.round(selectorHeight)
|
|
});
|
|
}, [selectorWidth, selectorHeight]);
|
|
|
|
return (
|
|
<div className="flex flex-col h-screen w-screen justify-center items-center gap-4">
|
|
<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">
|
|
<div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
|
|
</DragableBox>
|
|
<DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle">
|
|
<div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
|
|
</DragableBox>
|
|
</div>
|
|
|
|
<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>
|
|
</div>
|
|
);
|
|
}
|