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 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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