89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useRef } from "react";
|
|
import { useBuilderState } from "./useBuilderState";
|
|
|
|
interface DragState {
|
|
id: string;
|
|
startX: number;
|
|
startY: number;
|
|
origX: number;
|
|
origY: number;
|
|
origW: number;
|
|
origH: number;
|
|
mode: "move" | "resize";
|
|
}
|
|
|
|
/**
|
|
* 블록 드래그(이동)/리사이즈 훅.
|
|
* mousedown → mousemove → mouseup 패턴.
|
|
* Shift 키: 8px 스냅.
|
|
*/
|
|
export function useBlockDrag() {
|
|
const dragRef = useRef<DragState | null>(null);
|
|
const moveBlock = useBuilderState((s) => s.moveBlock);
|
|
const resizeBlock = useBuilderState((s) => s.resizeBlock);
|
|
const selectBlock = useBuilderState((s) => s.selectBlock);
|
|
|
|
const startDrag = useCallback(
|
|
(e: React.MouseEvent, id: string, origX: number, origY: number, origW: number, origH: number) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
selectBlock(id);
|
|
dragRef.current = { id, startX: e.clientX, startY: e.clientY, origX, origY, origW, origH, mode: "move" };
|
|
document.body.style.cursor = "grabbing";
|
|
document.body.style.userSelect = "none";
|
|
|
|
const onMove = (ev: MouseEvent) => {
|
|
const d = dragRef.current;
|
|
if (!d || d.mode !== "move") return;
|
|
let nx = d.origX + (ev.clientX - d.startX);
|
|
let ny = d.origY + (ev.clientY - d.startY);
|
|
if (ev.shiftKey) { nx = Math.round(nx / 8) * 8; ny = Math.round(ny / 8) * 8; }
|
|
moveBlock(d.id, Math.round(nx), Math.round(ny));
|
|
};
|
|
const onUp = () => {
|
|
dragRef.current = null;
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
document.removeEventListener("mousemove", onMove);
|
|
document.removeEventListener("mouseup", onUp);
|
|
};
|
|
document.addEventListener("mousemove", onMove);
|
|
document.addEventListener("mouseup", onUp);
|
|
},
|
|
[moveBlock, selectBlock]
|
|
);
|
|
|
|
const startResize = useCallback(
|
|
(e: React.MouseEvent, id: string, origX: number, origY: number, origW: number, origH: number) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
dragRef.current = { id, startX: e.clientX, startY: e.clientY, origX, origY, origW, origH, mode: "resize" };
|
|
document.body.style.cursor = "nwse-resize";
|
|
document.body.style.userSelect = "none";
|
|
|
|
const onMove = (ev: MouseEvent) => {
|
|
const d = dragRef.current;
|
|
if (!d || d.mode !== "resize") return;
|
|
let nw = d.origW + (ev.clientX - d.startX);
|
|
let nh = d.origH + (ev.clientY - d.startY);
|
|
if (ev.shiftKey) { nw = Math.round(nw / 8) * 8; nh = Math.round(nh / 8) * 8; }
|
|
resizeBlock(d.id, Math.round(nw), Math.round(nh));
|
|
};
|
|
const onUp = () => {
|
|
dragRef.current = null;
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
document.removeEventListener("mousemove", onMove);
|
|
document.removeEventListener("mouseup", onUp);
|
|
};
|
|
document.addEventListener("mousemove", onMove);
|
|
document.addEventListener("mouseup", onUp);
|
|
},
|
|
[resizeBlock]
|
|
);
|
|
|
|
return { startDrag, startResize };
|
|
}
|