Skip to content
Snippets Groups Projects
selectlayer.tsx 3.44 KiB
Newer Older
import React, { useState } from "react";
import { Node } from "../../common/graph/node";
Maximilian Giller's avatar
Maximilian Giller committed
import "./selectlayer.css";
import { Coordinate2D } from "../../common/graph/graph";
interface SelectLayerProps {
    children: React.ReactNode | React.ReactNode[];
    nodes: Node[];
    screen2GraphCoords: (x: number, y: number) => Coordinate2D;
Maximilian Giller's avatar
Maximilian Giller committed
    onBoxSelect: (nodes: Node[]) => void;
interface Box {
    bottom: number;
    top: number;
    left: number;
    right: number;
}
function SelectLayer({
    children,
    nodes,
    onBoxSelect,
    isEnabled,
    screen2GraphCoords,
}: SelectLayerProps) {
    const [selectionStart, setSelectionStart] =
        useState<Coordinate2D>(undefined);
    const [selectionEnd, setSelectionEnd] = useState<Coordinate2D>(undefined);
    // Calculate the nodes included in the bounding box
    const makeSelection = (bb: Box) => {
Maximilian Giller's avatar
Maximilian Giller committed
        // Filter out selected nodes
        const hitNodes: Node[] = [];
        const tl = screen2GraphCoords(bb.left, bb.top);
        const br = screen2GraphCoords(bb.right, bb.bottom);
        nodes.forEach((node: Node) => {
Maximilian Giller's avatar
Maximilian Giller committed
            if (
                tl.x < node.x &&
                node.x < br.x &&
                br.y > node.y &&
                node.y > tl.y
            ) {
                // Add node if in box area
                hitNodes.push(node);
            }
        });
        onBoxSelect(hitNodes);
    };
    // Create bounding box from two points
    const getBoundingBox = (p1: Coordinate2D, p2: Coordinate2D): Box => {
        return {
            left: Math.min(p1.x, p2.x),
            top: Math.min(p1.y, p2.y),
            right: Math.max(p1.x, p2.x),
            bottom: Math.max(p1.y, p2.y),
Maximilian Giller's avatar
Maximilian Giller committed
        };
    const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
        if (
            !isEnabled ||
            selectionStart == undefined ||
            selectionEnd == undefined
        ) {
            return;
        }
        e.preventDefault();
        makeSelection(getBoundingBox(selectionStart, selectionEnd));
        setSelectionStart(undefined);
        setSelectionEnd(undefined);
    };
    const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
        if (!isEnabled) {
            return;
        }
Maximilian Giller's avatar
Maximilian Giller committed
        e.preventDefault();
        setSelectionStart({
            x: e.nativeEvent.offsetX,
            y: e.nativeEvent.offsetY,
        });
        setSelectionEnd({ x: e.nativeEvent.offsetX, y: e.nativeEvent.offsetY });
    const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
        if (!isEnabled) {
            return;
        }
        setSelectionEnd({ x: e.nativeEvent.offsetX, y: e.nativeEvent.offsetY });
    let width = 0;
    let height = 0;
    let left = 0;
    let top = 0;
    if (selectionStart && selectionEnd) {
        const box = getBoundingBox(selectionStart, selectionEnd);
        width = box.right - box.left;
        height = box.bottom - box.top;
        left = box.left;
        top = box.top;

    return (
        <div
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            id="select-layer"
        >
            <div
                id="box-select"
                className={selectionStart ? "visible" : ""}
                style={{ width, height, left, top }}
            ></div>
            {children}
        </div>
    );

export default SelectLayer;