import React, { useEffect, useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import { croperHeight, croperWidth, croperX, croperY, isInpaintingState, } from '../../store/Atoms' const DOC_MOVE_OPTS = { capture: true, passive: false } const DRAG_HANDLE_BORDER = 2 const DRAG_HANDLE_SHORT = 12 const DRAG_HANDLE_LONG = 40 interface EVData { initX: number initY: number initHeight: number initWidth: number startResizeX: number startResizeY: number ord: string // top/right/bottom/left } interface Props { maxHeight: number maxWidth: number scale: number minHeight: number minWidth: number show: boolean } const clamp = ( newPos: number, newLength: number, oldPos: number, oldLength: number, minLength: number, maxLength: number ) => { if (newPos !== oldPos && newLength === oldLength) { if (newPos < 0) { return [0, oldLength] } if (newPos + newLength > maxLength) { return [maxLength - oldLength, oldLength] } } else { if (newLength < minLength) { if (newPos === oldPos) { return [newPos, minLength] } return [newPos + newLength - minLength, minLength] } if (newPos < 0) { return [0, newPos + newLength] } if (newPos + newLength > maxLength) { return [newPos, maxLength - newPos] } } return [newPos, newLength] } const Croper = (props: Props) => { const { minHeight, minWidth, maxHeight, maxWidth, scale, show } = props const [x, setX] = useRecoilState(croperX) const [y, setY] = useRecoilState(croperY) const [height, setHeight] = useRecoilState(croperHeight) const [width, setWidth] = useRecoilState(croperWidth) const isInpainting = useRecoilValue(isInpaintingState) const [isResizing, setIsResizing] = useState(false) const [isMoving, setIsMoving] = useState(false) useEffect(() => { setX(Math.round((maxWidth - 512) / 2)) setY(Math.round((maxHeight - 512) / 2)) }, [maxHeight, maxWidth]) const [evData, setEVData] = useState({ initX: 0, initY: 0, initHeight: 0, initWidth: 0, startResizeX: 0, startResizeY: 0, ord: 'top', }) const onDragFocus = () => { console.log('focus') } const clampLeftRight = (newX: number, newWidth: number) => { return clamp(newX, newWidth, x, width, minWidth, maxWidth) } const clampTopBottom = (newY: number, newHeight: number) => { return clamp(newY, newHeight, y, height, minHeight, maxHeight) } const onPointerMove = (e: PointerEvent) => { if (isInpainting) { return } const curX = e.clientX const curY = e.clientY const offsetY = Math.round((curY - evData.startResizeY) / scale) const offsetX = Math.round((curX - evData.startResizeX) / scale) const moveTop = () => { const newHeight = evData.initHeight - offsetY const newY = evData.initY + offsetY const [clampedY, clampedHeight] = clampTopBottom(newY, newHeight) setHeight(clampedHeight) setY(clampedY) } const moveBottom = () => { const newHeight = evData.initHeight + offsetY const [clampedY, clampedHeight] = clampTopBottom(evData.initY, newHeight) setHeight(clampedHeight) setY(clampedY) } const moveLeft = () => { const newWidth = evData.initWidth - offsetX const newX = evData.initX + offsetX const [clampedX, clampedWidth] = clampLeftRight(newX, newWidth) setWidth(clampedWidth) setX(clampedX) } const moveRight = () => { const newWidth = evData.initWidth + offsetX const [clampedX, clampedWidth] = clampLeftRight(evData.initX, newWidth) setWidth(clampedWidth) setX(clampedX) } if (isResizing) { switch (evData.ord) { case 'topleft': { moveTop() moveLeft() break } case 'topright': { moveTop() moveRight() break } case 'bottomleft': { moveBottom() moveLeft() break } case 'bottomright': { moveBottom() moveRight() break } case 'top': { moveTop() break } case 'right': { moveRight() break } case 'bottom': { moveBottom() break } case 'left': { moveLeft() break } default: break } } if (isMoving) { const newX = evData.initX + offsetX const newY = evData.initY + offsetY const [clampedX, clampedWidth] = clampLeftRight(newX, evData.initWidth) const [clampedY, clampedHeight] = clampTopBottom(newY, evData.initHeight) setWidth(clampedWidth) setHeight(clampedHeight) setX(clampedX) setY(clampedY) } } const onPointerDone = (e: PointerEvent) => { if (isResizing) { setIsResizing(false) } if (isMoving) { setIsMoving(false) } } useEffect(() => { if (isResizing || isMoving) { document.addEventListener('pointermove', onPointerMove, DOC_MOVE_OPTS) document.addEventListener('pointerup', onPointerDone, DOC_MOVE_OPTS) document.addEventListener('pointercancel', onPointerDone, DOC_MOVE_OPTS) return () => { document.removeEventListener( 'pointermove', onPointerMove, DOC_MOVE_OPTS ) document.removeEventListener('pointerup', onPointerDone, DOC_MOVE_OPTS) document.removeEventListener( 'pointercancel', onPointerDone, DOC_MOVE_OPTS ) } } }, [isResizing, isMoving, width, height, evData]) const onCropPointerDown = (e: React.PointerEvent) => { const { ord } = (e.target as HTMLElement).dataset if (ord) { setIsResizing(true) setEVData({ initX: x, initY: y, initHeight: height, initWidth: width, startResizeX: e.clientX, startResizeY: e.clientY, ord, }) } } const createCropSelection = () => { return (
) } const onInfoBarPointerDown = (e: React.PointerEvent) => { setIsMoving(true) setEVData({ initX: x, initY: y, initHeight: height, initWidth: width, startResizeX: e.clientX, startResizeY: e.clientY, ord: '', }) } const createInfoBar = () => { return (
{width} x {height}
) } const createBorder = () => { return (
) } return (
{createBorder()} {createInfoBar()} {createCropSelection()}
) } export default Croper