dekk-app/react-mops

View on GitHub
packages/react-mops/src/box.tsx

Summary

Maintainability
D
2 days
Test Coverage
import React from "react";
import {BoundingBox, Content, Handle, Handles, PropProvider, Wrapper} from "./elements";
import {
    listenRR,
    useCursorSlice,
    useDown,
    useDrag,
    useHandleMouse,
    useHandleMouseEvent,
    useHandlers,
    useHandles,
    useHandlesDown,
    useInitialSize,
    useLoaded,
    useMeta,
    useWithCornerHandle,
    useWithDown,
    useWithHandle
} from "./hooks";
import {Mops} from "./types";
import {getBoundingBox} from "./utils";

export const Box: React.RefForwardingComponent<
    HTMLElement,
    Mops.BoxProps & Mops.GuidesContext
> = React.forwardRef(
    (
        {
            as,
            children,
            className,
            drawBoundingBox,
            drawBox,
            isResizable,
            isRotatable,
            isDraggable,
            fullHandles,
            marker,
            minHeight,
            minWidth,
            onDrag,
            onDragStart,
            onDragEnd,
            onResize,
            onResizeStart,
            onResizeEnd,
            onRotate,
            onRotateStart,
            onRotateEnd,
            position,
            rotation,
            scale,
            showGuides,
            hideGuides,
            updateGuide,
            addGuides,
            removeGuides,
            guides,
            guideRequests,
            shouldSnap,
            size,
            style,
            ...props
        },
        ref
    ) => {
        const contentRef = React.useRef<HTMLDivElement>();
        const [loaded, setLoaded] = React.useState(false);
        const [initialSize, setInitialSize] = React.useState<Mops.SizeModel>(
            size as Mops.SizeModel
        );
        const [currentSize, setSize] = React.useState<Mops.SizeModel>(initialSize);
        const [initialPosition, setInitialPosition] = React.useState<Mops.PositionModel>(position);
        const [currentPosition, setPosition] = React.useState<Mops.PositionModel>(initialPosition);
        const [initialRotation, setInitialRotation] = React.useState<Mops.RotationModel>(rotation);
        const [currentRotation, setRotation] = React.useState<Mops.RotationModel>(initialRotation);
        const [additionalAngle, setAdditionalAngle] = React.useState<Mops.RotationModel>(rotation);
        const metaKey = useMeta();
        const {
            handleDrag,
            handleDragEnd,
            handleDragStart,
            handleResize,
            handleResizeEnd,
            handleResizeStart,
            handleRotate,
            handleRotateEnd,
            handleRotateStart
        } = useHandlers({
            currentPosition,
            currentRotation,
            currentSize,
            onDrag,
            onDragEnd,
            onDragStart,
            onResize,
            onResizeEnd,
            onResizeStart,
            onRotate,
            onRotateEnd,
            onRotateStart
        });
        const withHandle = useWithHandle({
            contentRef,
            currentPosition,
            currentRotation,
            initialPosition,
            initialSize,
            isResizable,
            minHeight,
            minWidth,
            scale,
            setInitialPosition,
            setInitialSize,
            setPosition,
            setSize
        });

        // const getLimit = React.useCallback(
        //     (radius, angle) => {
        //         const {x, y} = polarToCartesian(angle + initialRotation.z);
        //         return {
        //             x: (n: number) => chooseFn(x)(initialPosition.x + x * radius, n),
        //             y: (n: number) => chooseFn(y)( initialPosition.y + y * radius, n)
        //         };
        //     },
        //     [initialPosition, initialRotation]
        // );
        // const diff = React.useMemo(
        //     () => ({
        //         x: (initialSize.width - minWidth) / 2,
        //         y: (initialSize.height - minHeight) / 2
        //     }),
        //     [initialSize, minHeight, minWidth]
        // );
        // const limitLeft = React.useMemo(() => getLimit(diff.x, 0), [diff, getLimit]);
        // const limitTop = React.useMemo(() => getLimit(diff.y, 90), [diff, getLimit]);
        // const limitRight = React.useMemo(() => getLimit(diff.x, 180), [diff, getLimit]);
        // const limitBottom = React.useMemo(() => getLimit(diff.y, 270), [diff, getLimit]);
        // const limitTopLeft = React.useMemo(() => {
        //     const distance = getHypotenuse(diff.y, diff.x);
        //     const angle = atan2(diff.y, diff.x);
        //     return getLimit(distance, angle);
        // }, [diff, getLimit]);

        const withCornerHandle = useWithCornerHandle({
            currentRotation,
            initialPosition,
            initialSize,
            withHandle
        });

        const {
            isBottomDown,
            isBottomLeftDown,
            isBottomRightDown,
            isLeftDown,
            isRightDown,
            isTopDown,
            isTopLeftDown,
            isTopRightDown,
            setBottomDown,
            setBottomLeftDown,
            setBottomRightDown,
            setLeftDown,
            setRightDown,
            setTopDown,
            setTopLeftDown,
            setTopRightDown
        } = useHandlesDown({
            currentRotation,
            initialPosition,
            initialSize,
            // limitBottom,
            // limitLeft,
            // limitRight,
            // limitTop,
            // limitTopLeft,
            withCornerHandle,
            withHandle
        });
        const handleMouse = useHandleMouse({
            addGuides,
            currentRotation,
            currentSize,
            guideRequests,
            guides,
            hideGuides,
            initialPosition,
            removeGuides,
            shouldSnap,
            showGuides,
            updateGuide
        });
        const handleMouseEvent = useHandleMouseEvent({
            additionalAngle,
            contentRef,
            initialRotation,
            isRotatable
        });
        const {handleRotationDown, isDown, isRotationDown, setDown} = useWithDown({
            handleMouse,
            handleMouseEvent,
            hideGuides,
            scale,
            setAdditionalAngle,
            setInitialPosition,
            setInitialRotation,
            setPosition,
            setRotation
        });
        const getCursorSlice = useCursorSlice(currentRotation);
        const handles = useHandles({
            setBottomDown,
            setBottomLeftDown,
            setBottomRightDown,
            setLeftDown,
            setRightDown,
            setTopDown,
            setTopLeftDown,
            setTopRightDown
        });
        const wrapperStyle = {
            ...currentSize,
            transform: `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0) translate3d(-50%, -50%, 0)`
        };
        const boxStyle = {
            ...getBoundingBox({
                ...currentSize,
                angle: currentRotation.z
            })
        };
        const contentStyle = {
            ...currentSize,
            transform: `rotate3d(0, 0, 1, ${currentRotation.z}deg)`
        };
        useInitialSize({contentRef, setInitialSize, setSize});
        listenRR({
            currentPosition,
            currentRotation,
            currentSize,
            handleDrag,
            handleResize,
            handleRotate,
            isBottomDown,
            isBottomLeftDown,
            isBottomRightDown,
            isDown,
            isLeftDown,
            isRightDown,
            isRotationDown,
            isTopDown,
            isTopLeftDown,
            isTopRightDown,
            loaded
        });
        useDown({
            handleDragEnd,
            handleDragStart,
            handleResizeEnd,
            handleResizeStart,
            handleRotateEnd,
            handleRotateStart,
            isBottomDown,
            isBottomLeftDown,
            isBottomRightDown,
            isDown,
            isLeftDown,
            isRightDown,
            isRotationDown,
            isTopDown,
            isTopLeftDown,
            isTopRightDown,
            loaded,
            metaKey
        });
        useDrag({loaded, isDown, handleDragEnd, handleDragStart});
        useLoaded(setLoaded);
        return (
            <Wrapper
                ref={ref as React.Ref<HTMLElement>}
                as={as}
                style={{...(style || {}), ...wrapperStyle}}
                isDown={isDown}
                className={className}>
                <BoundingBox style={boxStyle} draw={drawBoundingBox} />
                <Content
                    ref={contentRef as React.Ref<HTMLDivElement>}
                    style={contentStyle}
                    onMouseDown={!metaKey && isDraggable ? setDown : undefined}>
                    {children}
                </Content>
                {(isResizable || isRotatable) && (
                    <PropProvider
                        value={{
                            getCursorSlice,
                            handleRotationDown,
                            isDraggable,
                            isResizable,
                            isRotatable,
                            metaKey
                        }}>
                        <Handles style={contentStyle} draw={drawBox}>
                            {handles.map(handle => {
                                return (
                                    <Handle
                                        key={handle.variation}
                                        {...handle}
                                        marker={marker}
                                        full={fullHandles}
                                    />
                                );
                            })}
                        </Handles>
                    </PropProvider>
                )}
            </Wrapper>
        );
    }
);

Box.defaultProps = {
    as: "div",
    drawBoundingBox: false,
    drawBox: true,
    minHeight: 40,
    minWidth: 40,
    position: {
        x: 0,
        y: 0
    },
    rotation: {
        x: 0,
        y: 0,
        z: 0
    },
    scale: 1,
    shouldSnap: [],
    size: {
        height: "auto",
        width: "auto"
    }
};