securityos/components/system/Files/FileManager/Selection/useSelection.ts

109 lines
3.4 KiB
TypeScript

import type { Size } from "components/system/Window/RndWindow/useResizable";
import { useMenu } from "contexts/menu";
import type { MenuState } from "contexts/menu/useMenuContextState";
import { useRef, useState } from "react";
import type { Position } from "react-rnd";
import { MILLISECONDS_IN_SECOND } from "utils/constants";
export type SelectionRect = Partial<Position> & Partial<Size>;
type Selection = {
isSelecting: boolean;
selectionEvents: {
onMouseDown: React.MouseEventHandler<HTMLElement>;
onMouseLeave?: React.MouseEventHandler<HTMLElement>;
onMouseMove?: React.MouseEventHandler<HTMLElement>;
onMouseUp?: () => void;
};
selectionRect?: SelectionRect;
selectionStyling: React.CSSProperties;
};
const FPS = 60;
const DEBOUNCE_TIME = MILLISECONDS_IN_SECOND / FPS;
const useSelection = (
containerRef: React.MutableRefObject<HTMLElement | null>
): Selection => {
const [position, setPosition] = useState<Position>(
() => Object.create(null) as Position
);
const [size, setSize] = useState<Size>(() => Object.create(null) as Size);
const { x, y } = position;
const { height: h, width: w } = size;
const debounceTimer = useRef<number>();
const onMouseMove: React.MouseEventHandler<HTMLElement> = ({
clientX,
clientY,
}) => {
const { scrollTop = 0 } = containerRef.current || {};
const { x: targetX = 0, y: targetY = 0 } =
containerRef.current?.getBoundingClientRect() || {};
if (!debounceTimer.current) {
setSize({
height: clientY - targetY - (y || 0) + scrollTop,
width: clientX - targetX - (x || 0),
});
debounceTimer.current = window.setTimeout(() => {
debounceTimer.current = undefined;
}, DEBOUNCE_TIME);
}
};
const { menu, setMenu } = useMenu();
const onMouseDown: React.MouseEventHandler<HTMLElement> = ({
clientX,
clientY,
target,
}) => {
if (target === containerRef.current) {
const { scrollTop } = containerRef.current;
const { x: targetX = 0, y: targetY = 0 } =
containerRef.current.getBoundingClientRect();
setSize(Object.create(null) as Size);
setPosition({
x: clientX - targetX,
y: clientY - targetY + scrollTop,
});
if (menu) setMenu(Object.create(null) as MenuState);
}
};
const hasMenu = Object.keys(menu).length > 0;
const hasSize = typeof w === "number" && typeof h === "number";
const hasPosition = typeof x === "number" && typeof y === "number";
const resetSelection = (): void => {
setSize(Object.create(null) as Size);
setPosition(Object.create(null) as Position);
};
const isSelecting = !hasMenu && hasSize && hasPosition;
const selectionStyling = isSelecting
? {
height: `${Math.abs(Number(h))}px`,
transform: `translate(
${Number(x) + (Number(w) < 0 ? Number(w) : 0)}px,
${Number(y) + (Number(h) < 0 ? Number(h) : 0)}px)`,
width: `${Math.abs(Number(w))}px`,
}
: {};
return {
isSelecting,
selectionEvents: {
onMouseDown,
...(hasPosition
? {
onMouseLeave: resetSelection,
onMouseMove,
onMouseUp: resetSelection,
}
: {}),
},
selectionRect: isSelecting ? { ...position, ...size } : undefined,
selectionStyling,
};
};
export default useSelection;