i lied, its not pixels
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s

This commit is contained in:
christian 2024-11-07 20:19:02 +01:00
parent 499caf6da5
commit bc9cf1bf25
3 changed files with 141 additions and 19 deletions

View File

@ -1,10 +1,10 @@
import { useEffect, useRef, useState } from "react"; import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import DragableBox from "./DragableBox"; import DragableBox from "./DragableBox";
import { PhAngle } from "./PhAngle"; import { Coordinates } from "~/types";
type Coordinates = { type ContainerDimensions = {
x: number; height: number;
y: number; width: number;
} }
export default function CropSelector() { export default function CropSelector() {
@ -20,10 +20,17 @@ export default function CropSelector() {
x: 0, x: 0,
y: 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); const containerRef = useRef<HTMLDivElement>(null);
// Update selector position and dimensions based on handle positions
useEffect(() => { useEffect(() => {
setSelectorPosition({ setSelectorPosition({
x: Math.min(firstDragable.x, secondDragable.x), x: Math.min(firstDragable.x, secondDragable.x),
@ -31,28 +38,83 @@ export default function CropSelector() {
}); });
}, [firstDragable, secondDragable]); }, [firstDragable, secondDragable]);
// Calculate selector dimensions 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 selectorWidth = Math.abs(secondDragable.x - firstDragable.x);
const selectorHeight = Math.abs(secondDragable.y - firstDragable.y); const selectorHeight = Math.abs(secondDragable.y - firstDragable.y);
// Handle selector drag
const handleSelectorDrag = (newPosition: Coordinates) => { const handleSelectorDrag = (newPosition: Coordinates) => {
const deltaX = newPosition.x - selectorPosition.x; const deltaX = newPosition.x - selectorPosition.x;
const deltaY = newPosition.y - selectorPosition.y; const deltaY = newPosition.y - selectorPosition.y;
setFirstDragable({ const newFirstX = Math.min(firstDragable.x, secondDragable.x) + deltaX;
x: Math.min(firstDragable.x, secondDragable.x) + deltaX, const newFirstY = Math.min(firstDragable.y, secondDragable.y) + deltaY;
y: 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;
setSecondDragable({ // Ensure the selector stays within container bounds
x: Math.max(firstDragable.x, secondDragable.x) + deltaX, if (newFirstX >= 0 && newSecondX <= containerSize.width &&
y: Math.max(firstDragable.y, secondDragable.y) + deltaY 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 ( return (
<div className="flex flex-col h-screen w-screen justify-center items-center"> <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"> <div ref={containerRef} className="relative w-1/3 h-1/3 border-black border">
<DragableBox <DragableBox
position={selectorPosition} position={selectorPosition}
@ -64,12 +126,39 @@ export default function CropSelector() {
className="border border-black" className="border border-black"
/> />
<DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle"> <DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle">
<PhAngle className="-rotate-90 absolute scale-150 -top-[13px] -left-[14px]" /> <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
</DragableBox> </DragableBox>
<DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle"> <DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle">
<PhAngle /> <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" />
</DragableBox> </DragableBox>
</div> </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> </div>
); );
} }

View File

@ -0,0 +1,29 @@
import { Coordinates } from "~/types";
import { PhAngle } from "./PhAngle";
interface ComponentProps {
position: Coordinates;
referencePosition: Coordinates;
}
export default function RotatableAngle({ position, referencePosition }: ComponentProps) {
// Calculate angle between points
const getRotationAngle = () => {
const deltaX = referencePosition.x - position.x;
const deltaY = referencePosition.y - position.y;
// Convert from radians to degrees and adjust for SVG orientation
const degrees = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
return degrees - 135; // Adjust baseline rotation to point right
};
return (
<PhAngle
className="absolute scale-125"
style={{
transform: `rotate(${getRotationAngle()}deg)`,
top: '-4px',
left: '-12px'
}}
/>
);
};

4
app/types.ts Normal file
View File

@ -0,0 +1,4 @@
export type Coordinates = {
x: number;
y: number;
}