graphicek/client/src/pages/dashboard/hooks/useDragging.ts

86 lines
2.0 KiB
TypeScript

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,
},
}
}