grungnet/src/contexts/TileDragContext.js

207 lines
5.4 KiB
JavaScript
Raw Normal View History

import React, { useState, useContext } from "react";
import {
DndContext,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
closestCenter,
} from "@dnd-kit/core";
import { useGroup } from "./GroupContext";
import { moveGroupsInto, moveGroups, ungroup } from "../helpers/group";
const TileDragContext = React.createContext();
export const BASE_SORTABLE_ID = "__base__";
export const GROUP_SORTABLE_ID = "__group__";
export const GROUP_ID_PREFIX = "__group__";
export const UNGROUP_ID = "__ungroup__";
export const ADD_TO_MAP_ID = "__add__";
// Custom rectIntersect that takes a point
function rectIntersection(rects, point) {
for (let rect of rects) {
const [id, bounds] = rect;
if (
id &&
bounds &&
point.x > bounds.offsetLeft &&
point.x < bounds.offsetLeft + bounds.width &&
point.y > bounds.offsetTop &&
point.y < bounds.offsetTop + bounds.height
) {
return id;
}
}
return null;
}
export function TileDragProvider({ onDragAdd, children }) {
const {
2021-06-05 02:38:01 -04:00
groups,
activeGroups,
openGroupId,
selectedGroupIds,
onGroupsChange,
onGroupSelect,
onGroupClose,
2021-06-05 02:38:01 -04:00
filter,
} = useGroup();
const mouseSensor = useSensor(MouseSensor, {
activationConstraint: { delay: 250, tolerance: 5 },
});
const touchSensor = useSensor(TouchSensor, {
activationConstraint: { delay: 250, tolerance: 5 },
});
const sensors = useSensors(mouseSensor, touchSensor);
const [dragId, setDragId] = useState();
const [overId, setOverId] = useState();
2021-06-04 23:04:56 -04:00
const [dragCursor, setDragCursor] = useState("pointer");
function handleDragStart({ active, over }) {
setDragId(active.id);
setOverId(over?.id);
if (!selectedGroupIds.includes(active.id)) {
onGroupSelect(active.id);
}
2021-06-04 23:04:56 -04:00
setDragCursor("grabbing");
}
function handleDragOver({ over }) {
setOverId(over?.id);
if (over) {
2021-06-04 23:04:56 -04:00
if (
over.id.startsWith(UNGROUP_ID) ||
2021-06-04 23:04:56 -04:00
over.id.startsWith(GROUP_ID_PREFIX)
) {
setDragCursor("alias");
} else if (over.id.startsWith(ADD_TO_MAP_ID)) {
setDragCursor(onDragAdd ? "copy" : "no-drop");
} else {
setDragCursor("grabbing");
}
}
}
function handleDragEnd({ active, over }) {
setDragId();
setOverId();
2021-06-04 23:04:56 -04:00
setDragCursor("pointer");
if (!active || !over || active.id === over.id) {
return;
}
let selectedIndices = selectedGroupIds.map((groupId) =>
2021-06-05 02:38:01 -04:00
activeGroups.findIndex((group) => group.id === groupId)
);
// Maintain current group sorting
selectedIndices = selectedIndices.sort((a, b) => a - b);
if (over.id.startsWith(GROUP_ID_PREFIX)) {
onGroupSelect();
// Handle tile group
const overId = over.id.slice(9);
if (overId === active.id) {
return;
}
2021-06-05 02:38:01 -04:00
const overGroupIndex = activeGroups.findIndex(
(group) => group.id === overId
);
onGroupsChange(
2021-06-05 02:38:01 -04:00
moveGroupsInto(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
} else if (over.id === UNGROUP_ID) {
onGroupSelect();
// Handle tile ungroup
2021-06-05 02:38:01 -04:00
const newGroups = ungroup(groups, openGroupId, selectedIndices);
// Close group if it was removed
if (!newGroups.find((group) => group.id === openGroupId)) {
onGroupClose();
}
onGroupsChange(newGroups);
} else if (over.id === ADD_TO_MAP_ID) {
onDragAdd && onDragAdd(selectedGroupIds, over.rect);
2021-06-05 02:38:01 -04:00
} else if (!filter) {
// Hanlde tile move only if we have no filter
const overGroupIndex = activeGroups.findIndex(
(group) => group.id === over.id
);
onGroupsChange(
2021-06-05 02:38:01 -04:00
moveGroups(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
}
}
function customCollisionDetection(rects, rect) {
// Calculate rect bottom taking into account any scroll offset
const rectBottom = rect.top + rect.bottom - rect.offsetTop;
const rectCenter = {
x: rect.left + rect.width / 2,
y: rectBottom - rect.height / 2,
};
// Find whether out rect center is outside our add to map rect
const addRect = rects.find(([id]) => id === ADD_TO_MAP_ID);
if (addRect) {
const intersectingAddRect = rectIntersection([addRect], rectCenter);
if (!intersectingAddRect) {
return ADD_TO_MAP_ID;
}
}
// Find whether out rect center is outside our ungroup rect
if (openGroupId) {
const ungroupRect = rects.find(([id]) => id === UNGROUP_ID);
if (ungroupRect) {
const intersectingGroupRect = rectIntersection(
[ungroupRect],
rectCenter
);
if (!intersectingGroupRect) {
return UNGROUP_ID;
}
}
}
const otherRects = rects.filter(
([id]) => id !== ADD_TO_MAP_ID && id !== UNGROUP_ID
);
return closestCenter(otherRects, rect);
}
const value = { dragId, overId, dragCursor };
return (
<DndContext
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
sensors={sensors}
collisionDetection={customCollisionDetection}
>
<TileDragContext.Provider value={value}>
{children}
</TileDragContext.Provider>
</DndContext>
);
}
export function useTileDrag() {
const context = useContext(TileDragContext);
if (context === undefined) {
throw new Error("useTileDrag must be used within a TileDragProvider");
}
return context;
}
export default TileDragContext;