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

216 lines
7.4 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-15 21:34:20 +00:00
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
import { useSearchParams } from "@remix-run/react";
2024-11-02 22:38:33 +00:00
2024-11-07 19:19:02 +00:00
type ContainerDimensions = {
height: number;
width: number;
2024-11-15 21:34:20 +00:00
};
type UnitType = "pixels" | "percentage";
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,
2024-11-15 21:34:20 +00:00
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,
2024-11-15 21:34:20 +00:00
y: 100,
2024-11-03 21:39:34 +00:00
});
const [selectorPosition, setSelectorPosition] = useState<Coordinates>({
2024-11-02 22:38:33 +00:00
x: 0,
2024-11-15 21:34:20 +00:00
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,
2024-11-15 21:34:20 +00:00
width: 0,
2024-11-07 19:19:02 +00:00
});
2024-11-15 21:34:20 +00:00
const [pixelDimensions, setPixelDimensions] = useState<ContainerDimensions>({
2024-11-07 19:19:02 +00:00
height: 100,
2024-11-15 21:34:20 +00:00
width: 100,
2024-11-07 19:19:02 +00:00
});
2024-11-15 21:34:20 +00:00
const [unit, setUnit] = useState<UnitType>("pixels");
const [searchParams, setSearchParams] = useSearchParams();
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),
2024-11-15 21:34:20 +00:00
y: Math.min(firstDragable.y, secondDragable.y),
2024-11-03 21:39:34 +00:00
});
2024-11-15 21:34:20 +00:00
const setUrlParams = setTimeout(() => {
writeDimensionsToURL();
}, 500);
return () => clearTimeout(setUrlParams);
2024-11-03 21:39:34 +00:00
}, [firstDragable, secondDragable]);
2024-11-03 20:56:34 +00:00
2024-11-07 19:19:02 +00:00
useEffect(() => {
if (!containerRef.current) return;
2024-11-15 21:34:20 +00:00
const resizeObserver = new ResizeObserver((entries) => {
2024-11-07 19:19:02 +00:00
const entry = entries[0];
setContainerSize({
width: entry.contentRect.width,
2024-11-15 21:34:20 +00:00
height: entry.contentRect.height,
2024-11-07 19:19:02 +00:00
});
});
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-15 21:34:20 +00:00
if (newFirstX >= 0 && newSecondX <= 100 && newFirstY >= 0 && newSecondY <= 100) {
2024-11-07 19:19:02 +00:00
setFirstDragable({
x: newFirstX,
2024-11-15 21:34:20 +00:00
y: newFirstY,
2024-11-07 19:19:02 +00:00
});
setSecondDragable({
x: newSecondX,
2024-11-15 21:34:20 +00:00
y: newSecondY,
2024-11-07 19:19:02 +00:00
});
}
};
2024-11-15 21:34:20 +00:00
const pixelsToPercentage = (pixels: number, totalSize: number): number => {
return totalSize > 0 ? (pixels / totalSize) * 100 : 0;
};
const percentageToPixels = (percentage: number, totalSize: number): number => {
return (percentage * totalSize) / 100;
};
const getActualPixelDimensions = (): ContainerDimensions => {
return {
width: Math.round(percentageToPixels(selectorWidth, containerSize.width)),
height: Math.round(percentageToPixels(selectorHeight, containerSize.height)),
};
};
const writeDimensionsToURL = (): void => {
setSearchParams((prev) => ({
...prev,
x1: ((firstDragable.x / 100) * containerSize.width).toFixed(),
y1: ((firstDragable.y / 100) * containerSize.height).toFixed(),
x2: ((secondDragable.x / 100) * containerSize.width).toFixed(),
y2: ((secondDragable.y / 100) * containerSize.height).toFixed(),
}));
};
const getDisplayValue = (dimension: "width" | "height"): number => {
const actualPixels = getActualPixelDimensions();
if (unit === "percentage") {
return dimension === "width" ? selectorWidth : selectorHeight;
}
return dimension === "width" ? actualPixels.width : actualPixels.height;
};
2024-11-07 19:19:02 +00:00
const handleDimensionChange = (
2024-11-15 21:34:20 +00:00
dimension: "width" | "height",
2024-11-07 19:19:02 +00:00
event: React.ChangeEvent<HTMLInputElement>
) => {
2024-11-15 21:34:20 +00:00
const inputValue = Math.max(0, parseFloat(event.target.value) || 0);
let newPercentage: number;
2024-11-07 19:19:02 +00:00
2024-11-15 21:34:20 +00:00
if (unit === "percentage") {
newPercentage = Math.min(inputValue, 100);
} else {
// Convert pixel input to percentage
const totalSize = dimension === "width" ? containerSize.width : containerSize.height;
newPercentage = pixelsToPercentage(Math.min(inputValue, totalSize), totalSize);
}
2024-11-07 19:19:02 +00:00
2024-11-15 21:34:20 +00:00
if (dimension === "width") {
const newX = Math.min(firstDragable.x, 100 - newPercentage);
setFirstDragable((prev) => ({ ...prev, x: newX }));
setSecondDragable((prev) => ({ ...prev, x: newX + newPercentage }));
2024-11-07 19:19:02 +00:00
} else {
2024-11-15 21:34:20 +00:00
const newY = Math.min(firstDragable.y, 100 - newPercentage);
setFirstDragable((prev) => ({ ...prev, y: newY }));
setSecondDragable((prev) => ({ ...prev, y: newY + newPercentage }));
2024-11-07 19:19:02 +00:00
}
2024-11-03 21:39:34 +00:00
};
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-15 21:34:20 +00:00
<RadioGroup
defaultValue="pixels"
value={unit}
onValueChange={(value) => setUnit(value as UnitType)}
className="flex gap-4 mb-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="pixels" id="pixels" />
<Label htmlFor="pixels">Pixels</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="percentage" id="percentage" />
<Label htmlFor="percentage">Percentage</Label>
</div>
</RadioGroup>
2024-11-07 19:19:02 +00:00
<div className="flex gap-4">
<div className="flex flex-col gap-2">
2024-11-15 21:34:20 +00:00
<label htmlFor="width">Width ({unit === "pixels" ? "px" : "%"})</label>
2024-11-07 19:19:02 +00:00
<input
id="width"
type="number"
min={0}
2024-11-15 21:34:20 +00:00
max={unit === "percentage" ? 100 : containerSize.width}
value={getDisplayValue("width").toFixed(unit === "percentage" ? 1 : 0)}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleDimensionChange("width", e)}
2024-11-07 19:19:02 +00:00
className="w-24"
/>
</div>
<div className="flex flex-col gap-2">
2024-11-15 21:34:20 +00:00
<label htmlFor="height">Height ({unit === "pixels" ? "px" : "%"})</label>
2024-11-07 19:19:02 +00:00
<input
id="height"
type="number"
min={0}
2024-11-15 21:34:20 +00:00
max={unit === "percentage" ? 100 : containerSize.height}
value={getDisplayValue("height").toFixed(unit === "percentage" ? 1 : 0)}
onChange={(e) => handleDimensionChange("height", e)}
2024-11-07 19:19:02 +00:00
className="w-24"
/>
</div>
</div>
2024-11-02 22:38:33 +00:00
</div>
);
}