less js more css
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s

This commit is contained in:
christian 2024-11-03 22:39:34 +01:00
parent 6d8620b402
commit 86ba372227
2 changed files with 105 additions and 139 deletions

View File

@ -1,7 +1,6 @@
import { Suspense, useEffect, useRef, useState } from "react";
import DragableBox from "./DragableBox"; import DragableBox from "./DragableBox";
import { ChangeEvent, Suspense, useEffect, useRef, useState } from "react";
import { PhAngle } from "./PhAngle"; import { PhAngle } from "./PhAngle";
import { parse } from "postcss";
type Coordinates = { type Coordinates = {
x: number; x: number;
@ -9,140 +8,69 @@ type Coordinates = {
} }
export default function CropSelector() { export default function CropSelector() {
const [selectorHeight, setSelectorHeight] = useState(0);
const [selectorWidth, setSelectorWidth] = useState(0);
const [selectorTop, setSelectorTop] = useState(0);
const [selectorLeft, setSelectorLeft] = useState(0);
const [firstDragable, setFirstDragable] = useState<Coordinates>({ const [firstDragable, setFirstDragable] = useState<Coordinates>({
x: 0, x: 0,
y: 0 y: 0
}) });
const [secondDragable, setSecondDragable] = useState<Coordinates>({ const [secondDragable, setSecondDragable] = useState<Coordinates>({
x: 100,
y: 100
});
const [selectorPosition, setSelectorPosition] = useState<Coordinates>({
x: 0, x: 0,
y: 0 y: 0
}) });
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
const [scaleRatio, setScaleRatio] = useState({ width: 1, height: 1 });
const containerRef = useRef<HTMLDivElement>(null)
const prevSize = useRef({ width: 0, height: 0 }); const containerRef = useRef<HTMLDivElement>(null);
// Helper function to round pixel values
const roundPixel = (value: number) => Math.round(value);
// Update selector position and dimensions based on handle positions
useEffect(() => { useEffect(() => {
const updateContainerSize = () => { setSelectorPosition({
if (!containerRef.current) return; x: Math.min(firstDragable.x, secondDragable.x),
y: Math.min(firstDragable.y, secondDragable.y)
const newWidth = roundPixel(containerRef.current.offsetWidth); });
const newHeight = roundPixel(containerRef.current.offsetHeight);
// Only calculate ratios if we have previous sizes
if (prevSize.current.width && prevSize.current.height) {
const widthRatio = newWidth / prevSize.current.width;
const heightRatio = newHeight / prevSize.current.height;
setScaleRatio({ width: widthRatio, height: heightRatio });
// Update dragable positions with new ratios and round the results
setFirstDragable(prev => ({
x: roundPixel(prev.x * widthRatio),
y: roundPixel(prev.y * heightRatio)
}));
setSecondDragable(prev => ({
x: roundPixel(prev.x * widthRatio),
y: roundPixel(prev.y * heightRatio)
}));
}
// Update container size and store as previous for next resize
setContainerSize({ width: newWidth, height: newHeight });
prevSize.current = { width: newWidth, height: newHeight };
};
// Initial size setup
updateContainerSize();
// Add resize listener with debounce to prevent too frequent updates
let resizeTimeout: any;
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(updateContainerSize, 16); // roughly 60fps
};
window.addEventListener('resize', handleResize);
// Cleanup
return () => {
window.removeEventListener('resize', handleResize);
clearTimeout(resizeTimeout);
};
}, []); // Empty dependency array since we're using refs
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
event.preventDefault()
const { value, name, id } = event.target
let valueInt = parseInt(value)
if (name === 'x' && containerSize.width !== null && valueInt > containerSize.width) {
valueInt = containerSize.width
}
if (name === 'y' && containerSize.height !== null && valueInt > containerSize.height) {
valueInt = containerSize.height
}
if (id === 'first') {
setFirstDragable(prev => ({
...prev,
[name]: valueInt,
}))
}
if (id === 'second') {
setSecondDragable(prev => ({
...prev,
[name]: valueInt,
}))
}
}
useEffect(() => {
const width = Math.abs(secondDragable.x - firstDragable.x);
setSelectorWidth(width);
const height = Math.abs(secondDragable.y - firstDragable.y);
setSelectorHeight(height);
setSelectorTop(Math.min(firstDragable.y, secondDragable.y));
setSelectorLeft(Math.min(firstDragable.x, secondDragable.x));
}, [firstDragable, secondDragable]); }, [firstDragable, secondDragable]);
// Calculate selector dimensions
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(prev => ({
x: Math.min(firstDragable.x, secondDragable.x) + deltaX,
y: Math.min(firstDragable.y, secondDragable.y) + deltaY
}));
setSecondDragable(prev => ({
x: Math.max(firstDragable.x, secondDragable.x) + deltaX,
y: Math.max(firstDragable.y, secondDragable.y) + deltaY
}));
};
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">
<Suspense fallback={<p>Loading... </p>}> <Suspense fallback={<p>Loading...</p>}>
<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">
<div <DragableBox
className="border border-black absolute" position={selectorPosition}
style={{ mode="box"
top: `${selectorTop}px`, width={selectorWidth}
left: `${selectorLeft}px`, height={selectorHeight}
height: `${selectorHeight}px`, coordSetter={setSelectorPosition}
width: `${selectorWidth}px`, onDrag={handleSelectorDrag}
}} className="border border-black"
/> />
<DragableBox position={firstDragable} coordSetter={setFirstDragable} > <DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle">
<PhAngle /> <PhAngle />
</DragableBox> </DragableBox>
<DragableBox position={secondDragable} coordSetter={setSecondDragable} > <DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle">
<PhAngle /> <PhAngle />
</DragableBox> </DragableBox>
</div> </div>
<input min={0} max={300} id="first" onChange={handleChange} value={firstDragable.x} name="x" type="number" />
<input id="first" onChange={handleChange} value={firstDragable.y} name="y" type="number" />
<input id="second" onChange={handleChange} value={secondDragable.x} name="x" type="number" />
<input id="second" onChange={handleChange} value={secondDragable.y} name="y" type="number" />
<div className="flex flex-col text-left">
<p>{containerSize.width} - {containerSize.height}</p>
<p>firstDragable: x: {firstDragable.x} y: {firstDragable.y}</p>
<p>secondDragable: x: {secondDragable.x} y: {secondDragable.y}</p>
<p>Scale height: {scaleRatio.height}</p>
<p>Scale width: {scaleRatio.width}</p>
</div>
</Suspense> </Suspense>
</div> </div>
); );

View File

@ -1,10 +1,5 @@
import { useState, useEffect, MouseEvent, useRef, Dispatch, SetStateAction } from 'react'; import { useState, useEffect, MouseEvent, useRef, Dispatch, SetStateAction } from 'react';
// Todo
// Have selector start at full width and height
// dragable icons rotation
// drag selector
type Coordinates = { type Coordinates = {
x: number; x: number;
y: number; y: number;
@ -15,13 +10,21 @@ interface DraggableBoxProps {
className?: string; className?: string;
children?: React.ReactNode; children?: React.ReactNode;
coordSetter: Dispatch<SetStateAction<Coordinates>>; coordSetter: Dispatch<SetStateAction<Coordinates>>;
mode?: 'handle' | 'box';
width?: number;
height?: number;
onDrag?: (delta: Coordinates) => void;
} }
const DraggableBox = ({ const DraggableBox = ({
position, position,
className = "", className = "",
children = "", children = "",
coordSetter coordSetter,
mode = 'handle',
width = 0,
height = 0,
onDrag
}: DraggableBoxProps) => { }: DraggableBoxProps) => {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
@ -36,19 +39,34 @@ const DraggableBox = ({
const parent = dragRef.current.parentElement; const parent = dragRef.current.parentElement;
const parentRect = parent.getBoundingClientRect(); const parentRect = parent.getBoundingClientRect();
const relativeX = e.clientX - parentRect.left - dragOffset.x; if (mode === 'handle') {
const relativeY = e.clientY - parentRect.top - dragOffset.y; const x = ((e.clientX - parentRect.left - dragOffset.x) / parentRect.width) * 100;
const y = ((e.clientY - parentRect.top - dragOffset.y) / parentRect.height) * 100;
const maxX = parentRect.width; const newX = Math.min(Math.max(0, x), 100);
const maxY = parentRect.height; const newY = Math.min(Math.max(0, y), 100);
let newX = (Math.max(0, Math.min(relativeX, maxX))); coordSetter({
let newY = (Math.max(0, Math.min(relativeY, maxY))); x: Number(newX.toFixed(1)),
y: Number(newY.toFixed(1))
});
} else {
const x = ((e.clientX - parentRect.left - dragOffset.x) / parentRect.width) * 100;
const y = ((e.clientY - parentRect.top - dragOffset.y) / parentRect.height) * 100;
coordSetter({ const maxX = 100 - width;
x: newX, const maxY = 100 - height;
y: newY
}) const newX = Math.min(Math.max(0, x), maxX);
const newY = Math.min(Math.max(0, y), maxY);
coordSetter({
x: Number(newX.toFixed(1)),
y: Number(newY.toFixed(1))
});
onDrag?.({ x: newX, y: newY });
}
}; };
const handleMouseUp = () => setIsDragging(false); const handleMouseUp = () => setIsDragging(false);
@ -60,26 +78,46 @@ const DraggableBox = ({
window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp); window.removeEventListener('mouseup', handleMouseUp);
}; };
}, [isDragging, dragOffset, position.x, position.y]); }, [isDragging, dragOffset, mode, width, height, onDrag]);
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
if (!dragRef.current?.parentElement) return; if (!dragRef.current?.parentElement) return;
const parentRect = dragRef.current.parentElement.getBoundingClientRect(); const parentRect = dragRef.current.parentElement.getBoundingClientRect();
const elementRect = dragRef.current.getBoundingClientRect();
if (mode === 'handle') {
setDragOffset({
x: e.clientX - elementRect.left,
y: e.clientY - elementRect.top
});
} else {
setDragOffset({
x: e.clientX - parentRect.left - (position.x * parentRect.width / 100),
y: e.clientY - parentRect.top - (position.y * parentRect.height / 100)
});
}
setDragOffset({
x: e.clientX - parentRect.left - position.x,
y: e.clientY - parentRect.top - position.y
});
setIsDragging(true); setIsDragging(true);
}; };
const style = mode === 'handle' ? {
left: `${position.x}%`,
top: `${position.y}%`,
transform: 'translate(-50%, -50%)', // Center the handle
} : {
left: `${position.x}%`,
top: `${position.y}%`,
width: `${width}%`,
height: `${height}%`,
};
return ( return (
<div <div
ref={dragRef} ref={dragRef}
className={`absolute cursor-pointer ${className}`} className={`absolute cursor-pointer transform-gpu ${mode === 'box' ? 'cursor-move' : ''} ${className}`}
style={{ style={{
transform: `translate(${position.x}px, ${position.y}px)`, ...style,
userSelect: 'none', userSelect: 'none',
}} }}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}