import { useWindowEvent } from '@/utils/hooks/useWindowEvent' import { useState } from 'preact/hooks' import { BoxDefinition } from '../types' import { GRID_H_SNAP, GRID_WIDTH } from '../constants' import { range } from '@/utils/range' type Props = { cellWidth: number boxes: BoxDefinition[] box: BoxDefinition } // TODO: This is not optimized at all export const useDragging = ({ cellWidth, boxes, box }: Props) => { const [state, setState] = useState({ active: false, x: 0, y: 0, offsetX: 0, offsetY: 0, }) const actualX = Math.max( 0, Math.min(GRID_WIDTH - box.w, Math.round(state.x / cellWidth)) ) const dragY = Math.max(0, Math.round(state.y / GRID_H_SNAP) * GRID_H_SNAP) const maxHeight = Math.max( ...range(actualX, actualX + box.w).map((x) => { // All boxes that are in this column const boxesAtColumn = boxes.filter( (b) => b.id !== box.id && b.x <= x && b.x + b.w > x ) // Find boxes that would be colliding at this position const collisions = boxesAtColumn.filter( (b) => b.y < dragY + box.h && dragY < b.y + b.h ) if (collisions.length > 0) { const upperBoxes = boxesAtColumn.filter((b) => b.y + b.h > dragY) // Try to find a space to fit somewhere up for (let i = 1; i < upperBoxes.length; i++) { const prevBox = upperBoxes[i - 1] const thisBox = upperBoxes[i] if (thisBox.y - (prevBox.y + prevBox.h) >= box.h) { return prevBox.y + prevBox.h } } // Nothing found, we'll have to stack to the upper box const lastBox = boxesAtColumn[boxesAtColumn.length - 1] return lastBox ? lastBox.y + lastBox.h : 0 } else { const bottomBox = boxesAtColumn.reverse().find((b) => b.y < dragY) return bottomBox ? bottomBox.y + bottomBox.h : 0 } }) ) useWindowEvent('mousemove', (e) => { if (state.active) { setState({ ...state, x: e.clientX - state.offsetX, y: e.clientY - state.offsetY, }) } }) return { dragging: state, setDragging: setState, draggingPosition: { x: actualX, y: maxHeight, }, } }