i lied, its not pixels
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s
This commit is contained in:
parent
499caf6da5
commit
bc9cf1bf25
@ -1,10 +1,10 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import DragableBox from "./DragableBox";
|
||||
import { PhAngle } from "./PhAngle";
|
||||
import { Coordinates } from "~/types";
|
||||
|
||||
type Coordinates = {
|
||||
x: number;
|
||||
y: number;
|
||||
type ContainerDimensions = {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export default function CropSelector() {
|
||||
@ -20,10 +20,17 @@ export default function CropSelector() {
|
||||
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);
|
||||
|
||||
// Update selector position and dimensions based on handle positions
|
||||
useEffect(() => {
|
||||
setSelectorPosition({
|
||||
x: Math.min(firstDragable.x, secondDragable.x),
|
||||
@ -31,28 +38,83 @@ export default function CropSelector() {
|
||||
});
|
||||
}, [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 selectorHeight = Math.abs(secondDragable.y - firstDragable.y);
|
||||
|
||||
// Handle selector drag
|
||||
const handleSelectorDrag = (newPosition: Coordinates) => {
|
||||
const deltaX = newPosition.x - selectorPosition.x;
|
||||
const deltaY = newPosition.y - selectorPosition.y;
|
||||
|
||||
setFirstDragable({
|
||||
x: Math.min(firstDragable.x, secondDragable.x) + deltaX,
|
||||
y: Math.min(firstDragable.y, secondDragable.y) + deltaY
|
||||
});
|
||||
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;
|
||||
|
||||
setSecondDragable({
|
||||
x: Math.max(firstDragable.x, secondDragable.x) + deltaX,
|
||||
y: 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">
|
||||
<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}
|
||||
@ -64,12 +126,39 @@ export default function CropSelector() {
|
||||
className="border border-black"
|
||||
/>
|
||||
<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 position={secondDragable} coordSetter={setSecondDragable} mode="handle">
|
||||
<PhAngle />
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
29
app/routes/RotatableAngle.tsx
Normal file
29
app/routes/RotatableAngle.tsx
Normal 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
4
app/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type Coordinates = {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
Loading…
Reference in New Issue
Block a user