Add support for ungrouping tiles and refactor tile drag functions
This commit is contained in:
parent
456f91c9ae
commit
57aafce938
@ -1,10 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDroppable } from "@dnd-kit/core";
|
import { useDroppable } from "@dnd-kit/core";
|
||||||
|
|
||||||
function Droppable({ id, children, disabled }) {
|
function Droppable({ id, children, disabled, ...props }) {
|
||||||
const { setNodeRef } = useDroppable({ id, disabled });
|
const { setNodeRef } = useDroppable({ id, disabled });
|
||||||
|
|
||||||
return <div ref={setNodeRef}>{children}</div>;
|
return (
|
||||||
|
<div ref={setNodeRef} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Droppable.defaultProps = {
|
Droppable.defaultProps = {
|
||||||
|
@ -9,15 +9,11 @@ import { getGroupItems } from "../../helpers/group";
|
|||||||
|
|
||||||
import { useGroup } from "../../contexts/GroupContext";
|
import { useGroup } from "../../contexts/GroupContext";
|
||||||
|
|
||||||
function MapTiles({ maps, onMapEdit, onMapSelect, subgroup, columns }) {
|
function MapTiles({ maps, onMapEdit, onMapSelect, subgroup }) {
|
||||||
const {
|
const {
|
||||||
groups,
|
|
||||||
selectedGroupIds,
|
selectedGroupIds,
|
||||||
openGroupId,
|
|
||||||
openGroupItems,
|
|
||||||
selectMode,
|
selectMode,
|
||||||
onGroupOpen,
|
onGroupOpen,
|
||||||
onGroupsChange,
|
|
||||||
onGroupSelect,
|
onGroupSelect,
|
||||||
} = useGroup();
|
} = useGroup();
|
||||||
|
|
||||||
@ -57,17 +53,7 @@ function MapTiles({ maps, onMapEdit, onMapSelect, subgroup, columns }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <SortableTiles renderTile={renderTile} subgroup={subgroup} />;
|
||||||
<SortableTiles
|
|
||||||
groups={subgroup ? openGroupItems : groups}
|
|
||||||
selectedGroupIds={selectedGroupIds}
|
|
||||||
onGroupChange={onGroupsChange}
|
|
||||||
renderTile={renderTile}
|
|
||||||
onTileSelect={onGroupSelect}
|
|
||||||
disableGrouping={subgroup}
|
|
||||||
openGroupId={openGroupId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MapTiles;
|
export default MapTiles;
|
||||||
|
@ -4,7 +4,16 @@ import { useDroppable } from "@dnd-kit/core";
|
|||||||
import { useSortable } from "@dnd-kit/sortable";
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
import { animated, useSpring } from "react-spring";
|
import { animated, useSpring } from "react-spring";
|
||||||
|
|
||||||
function SortableTile({ id, disableGrouping, hidden, children, isDragging }) {
|
import { GROUP_ID_PREFIX } from "../../contexts/TileDragContext";
|
||||||
|
|
||||||
|
function SortableTile({
|
||||||
|
id,
|
||||||
|
disableGrouping,
|
||||||
|
disableSorting,
|
||||||
|
hidden,
|
||||||
|
children,
|
||||||
|
isDragging,
|
||||||
|
}) {
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
listeners,
|
listeners,
|
||||||
@ -12,9 +21,10 @@ function SortableTile({ id, disableGrouping, hidden, children, isDragging }) {
|
|||||||
setDraggableNodeRef,
|
setDraggableNodeRef,
|
||||||
over,
|
over,
|
||||||
active,
|
active,
|
||||||
} = useSortable({ id });
|
} = useSortable({ id, disabled: disableSorting });
|
||||||
|
|
||||||
const { setNodeRef: setGroupNodeRef } = useDroppable({
|
const { setNodeRef: setGroupNodeRef } = useDroppable({
|
||||||
id: `__group__${id}`,
|
id: `${GROUP_ID_PREFIX}${id}`,
|
||||||
disabled: disableGrouping,
|
disabled: disableGrouping,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,7 +54,9 @@ function SortableTile({ id, disableGrouping, hidden, children, isDragging }) {
|
|||||||
borderWidth: "4px",
|
borderWidth: "4px",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
borderStyle:
|
borderStyle:
|
||||||
over?.id === `__group__${id}` && active.id !== id ? "solid" : "none",
|
over?.id === `${GROUP_ID_PREFIX}${id}` && active.id !== id
|
||||||
|
? "solid"
|
||||||
|
: "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { opacity } = useSpring({ opacity: hidden ? 0 : 1 });
|
const { opacity } = useSpring({ opacity: hidden ? 0 : 1 });
|
||||||
|
@ -1,88 +1,43 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import {
|
import { DragOverlay } from "@dnd-kit/core";
|
||||||
DndContext,
|
|
||||||
DragOverlay,
|
|
||||||
MouseSensor,
|
|
||||||
TouchSensor,
|
|
||||||
useSensor,
|
|
||||||
useSensors,
|
|
||||||
closestCenter,
|
|
||||||
} from "@dnd-kit/core";
|
|
||||||
import { SortableContext } from "@dnd-kit/sortable";
|
import { SortableContext } from "@dnd-kit/sortable";
|
||||||
import { animated, useSpring, config } from "react-spring";
|
import { animated, useSpring, config } from "react-spring";
|
||||||
import { Badge } from "theme-ui";
|
import { Badge } from "theme-ui";
|
||||||
|
|
||||||
import { moveGroupsInto, moveGroups } from "../../helpers/group";
|
import { moveGroupsInto } from "../../helpers/group";
|
||||||
import { keyBy } from "../../helpers/shared";
|
import { keyBy } from "../../helpers/shared";
|
||||||
import Vector2 from "../../helpers/Vector2";
|
import Vector2 from "../../helpers/Vector2";
|
||||||
|
|
||||||
import SortableTile from "./SortableTile";
|
import SortableTile from "./SortableTile";
|
||||||
|
|
||||||
function SortableTiles({
|
import {
|
||||||
groups,
|
useTileDrag,
|
||||||
selectedGroupIds,
|
BASE_SORTABLE_ID,
|
||||||
onGroupChange,
|
GROUP_SORTABLE_ID,
|
||||||
renderTile,
|
GROUP_ID_PREFIX,
|
||||||
onTileSelect,
|
} from "../../contexts/TileDragContext";
|
||||||
disableGrouping,
|
import { useGroup } from "../../contexts/GroupContext";
|
||||||
openGroupId,
|
|
||||||
}) {
|
|
||||||
const mouseSensor = useSensor(MouseSensor, {
|
|
||||||
activationConstraint: { delay: 250, tolerance: 5 },
|
|
||||||
});
|
|
||||||
const touchSensor = useSensor(TouchSensor, {
|
|
||||||
activationConstraint: { delay: 250, tolerance: 5 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const sensors = useSensors(mouseSensor, touchSensor);
|
function SortableTiles({ renderTile, subgroup }) {
|
||||||
|
const { dragId, overId } = useTileDrag();
|
||||||
|
const {
|
||||||
|
groups: allGroups,
|
||||||
|
selectedGroupIds: allSelectedIds,
|
||||||
|
openGroupId,
|
||||||
|
openGroupItems,
|
||||||
|
} = useGroup();
|
||||||
|
|
||||||
const [dragId, setDragId] = useState();
|
const sortableId = subgroup ? GROUP_SORTABLE_ID : BASE_SORTABLE_ID;
|
||||||
const [overId, setOverId] = useState();
|
|
||||||
|
|
||||||
function handleDragStart({ active, over }) {
|
const groups = subgroup ? openGroupItems : allGroups;
|
||||||
setDragId(active.id);
|
// Only populate selected groups if needed
|
||||||
setOverId(over?.id);
|
let selectedGroupIds = [];
|
||||||
if (!selectedGroupIds.includes(active.id)) {
|
if ((subgroup && openGroupId) || (!subgroup && !openGroupId)) {
|
||||||
onTileSelect(active.id);
|
selectedGroupIds = allSelectedIds;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDragOver({ over }) {
|
|
||||||
setOverId(over?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDragEnd({ active, over }) {
|
|
||||||
setDragId();
|
|
||||||
setOverId();
|
|
||||||
if (!active || !over) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedIndices = selectedGroupIds.map((groupId) =>
|
|
||||||
groups.findIndex((group) => group.id === groupId)
|
|
||||||
);
|
|
||||||
// Maintain current group sorting
|
|
||||||
selectedIndices = selectedIndices.sort((a, b) => a - b);
|
|
||||||
|
|
||||||
if (over.id.startsWith("__group__")) {
|
|
||||||
const overId = over.id.slice(9);
|
|
||||||
if (overId === active.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overGroupIndex = groups.findIndex((group) => group.id === overId);
|
|
||||||
onGroupChange(moveGroupsInto(groups, overGroupIndex, selectedIndices));
|
|
||||||
onTileSelect();
|
|
||||||
} else {
|
|
||||||
if (active.id === over.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overGroupIndex = groups.findIndex((group) => group.id === over.id);
|
|
||||||
onGroupChange(moveGroups(groups, overGroupIndex, selectedIndices));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const disableSorting = openGroupId && !subgroup;
|
||||||
|
const disableGrouping = subgroup || disableSorting;
|
||||||
|
|
||||||
const dragBounce = useSpring({
|
const dragBounce = useSpring({
|
||||||
transform: !!dragId ? "scale(0.9)" : "scale(1)",
|
transform: !!dragId ? "scale(0.9)" : "scale(1)",
|
||||||
@ -91,7 +46,7 @@ function SortableTiles({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const overGroupId =
|
const overGroupId =
|
||||||
overId && overId.startsWith("__group__") && overId.slice(9);
|
overId && overId.startsWith(GROUP_ID_PREFIX) && overId.slice(9);
|
||||||
|
|
||||||
function renderSortableGroup(group, selectedGroups) {
|
function renderSortableGroup(group, selectedGroups) {
|
||||||
if (overGroupId === group.id && dragId && group.id !== dragId) {
|
if (overGroupId === group.id && dragId && group.id !== dragId) {
|
||||||
@ -179,6 +134,7 @@ function SortableTiles({
|
|||||||
id={group.id}
|
id={group.id}
|
||||||
key={group.id}
|
key={group.id}
|
||||||
disableGrouping={disableTileGrouping}
|
disableGrouping={disableTileGrouping}
|
||||||
|
disableSorting={disableSorting}
|
||||||
hidden={group.id === openGroupId}
|
hidden={group.id === openGroupId}
|
||||||
isDragging={isDragging}
|
isDragging={isDragging}
|
||||||
>
|
>
|
||||||
@ -189,18 +145,10 @@ function SortableTiles({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<SortableContext items={groups} id={sortableId}>
|
||||||
onDragStart={handleDragStart}
|
{renderTiles()}
|
||||||
onDragEnd={handleDragEnd}
|
{createPortal(dragId && renderDragOverlays(), document.body)}
|
||||||
onDragOver={handleDragOver}
|
</SortableContext>
|
||||||
sensors={sensors}
|
|
||||||
collisionDetection={closestCenter}
|
|
||||||
>
|
|
||||||
<SortableContext items={groups}>
|
|
||||||
{renderTiles()}
|
|
||||||
{createPortal(dragId && renderDragOverlays(), document.body)}
|
|
||||||
</SortableContext>
|
|
||||||
</DndContext>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import { Box, Close, Grid, useThemeUI } from "theme-ui";
|
import { Box, Close, Grid, useThemeUI } from "theme-ui";
|
||||||
import { useSpring, animated, config } from "react-spring";
|
import { useSpring, animated, config } from "react-spring";
|
||||||
import ReactResizeDetector from "react-resize-detector";
|
import ReactResizeDetector from "react-resize-detector";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
|
|
||||||
import { useGroup } from "../../contexts/GroupContext";
|
import { useGroup } from "../../contexts/GroupContext";
|
||||||
|
import { UNGROUP_ID_PREFIX } from "../../contexts/TileDragContext";
|
||||||
|
|
||||||
|
import Droppable from "../drag/Droppable";
|
||||||
|
|
||||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||||
|
|
||||||
@ -27,8 +31,55 @@ function TilesOverlay({ children }) {
|
|||||||
setContinerSize(size);
|
setContinerSize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderUngroupBoxes() {
|
||||||
|
return createPortal(
|
||||||
|
<div>
|
||||||
|
<Droppable
|
||||||
|
id={`${UNGROUP_ID_PREFIX}-1`}
|
||||||
|
style={{
|
||||||
|
width: "100vw",
|
||||||
|
height: `calc(50vh - ${containerSize / 2}px)`,
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Droppable
|
||||||
|
id={`${UNGROUP_ID_PREFIX}-2`}
|
||||||
|
style={{
|
||||||
|
width: "100vw",
|
||||||
|
height: `calc(50vh - ${containerSize / 2}px)`,
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Droppable
|
||||||
|
id={`${UNGROUP_ID_PREFIX}-3`}
|
||||||
|
style={{
|
||||||
|
width: `calc(50vw - ${containerSize / 2}px)`,
|
||||||
|
height: "100vh",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Droppable
|
||||||
|
id={`${UNGROUP_ID_PREFIX}-4`}
|
||||||
|
style={{
|
||||||
|
width: `calc(50vw - ${containerSize / 2}px)`,
|
||||||
|
height: "100vh",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{openGroupId && renderUngroupBoxes()}
|
||||||
{openGroupId && (
|
{openGroupId && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -11,13 +11,9 @@ import { useGroup } from "../../contexts/GroupContext";
|
|||||||
|
|
||||||
function TokenTiles({ tokens, onTokenEdit, subgroup }) {
|
function TokenTiles({ tokens, onTokenEdit, subgroup }) {
|
||||||
const {
|
const {
|
||||||
groups,
|
|
||||||
selectedGroupIds,
|
selectedGroupIds,
|
||||||
openGroupId,
|
|
||||||
openGroupItems,
|
|
||||||
selectMode,
|
selectMode,
|
||||||
onGroupOpen,
|
onGroupOpen,
|
||||||
onGroupsChange,
|
|
||||||
onGroupSelect,
|
onGroupSelect,
|
||||||
} = useGroup();
|
} = useGroup();
|
||||||
|
|
||||||
@ -62,17 +58,7 @@ function TokenTiles({ tokens, onTokenEdit, subgroup }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <SortableTiles renderTile={renderTile} subgroup={subgroup} />;
|
||||||
<SortableTiles
|
|
||||||
groups={subgroup ? openGroupItems : groups}
|
|
||||||
selectedGroupIds={selectedGroupIds}
|
|
||||||
onGroupChange={onGroupsChange}
|
|
||||||
renderTile={renderTile}
|
|
||||||
onTileSelect={onGroupSelect}
|
|
||||||
disableGrouping={subgroup}
|
|
||||||
openGroupId={openGroupId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TokenTiles;
|
export default TokenTiles;
|
||||||
|
@ -40,10 +40,13 @@ export function GroupProvider({
|
|||||||
setOpenGroupId();
|
setOpenGroupId();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGroupsChange(newGroups) {
|
/**
|
||||||
if (openGroupId) {
|
* @param {string|undefined} groupId The group to apply changes to, leave undefined to replace the full group object
|
||||||
// If a group is open then update that group with the new items
|
*/
|
||||||
const groupIndex = groups.findIndex((group) => group.id === openGroupId);
|
function handleGroupsChange(newGroups, groupId) {
|
||||||
|
if (groupId) {
|
||||||
|
// If a group is specidifed then update that group with the new items
|
||||||
|
const groupIndex = groups.findIndex((group) => group.id === groupId);
|
||||||
let updatedGroups = cloneDeep(groups);
|
let updatedGroups = cloneDeep(groups);
|
||||||
const group = updatedGroups[groupIndex];
|
const group = updatedGroups[groupIndex];
|
||||||
updatedGroups[groupIndex] = { ...group, items: newGroups };
|
updatedGroups[groupIndex] = { ...group, items: newGroups };
|
||||||
|
148
src/contexts/TileDragContext.js
Normal file
148
src/contexts/TileDragContext.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
MouseSensor,
|
||||||
|
TouchSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
closestCenter,
|
||||||
|
rectIntersection,
|
||||||
|
} 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_PREFIX = "__ungroup__";
|
||||||
|
|
||||||
|
export function TileDragProvider({ children }) {
|
||||||
|
const {
|
||||||
|
groups: allGroups,
|
||||||
|
openGroupId,
|
||||||
|
openGroupItems,
|
||||||
|
selectedGroupIds,
|
||||||
|
onGroupsChange,
|
||||||
|
onGroupSelect,
|
||||||
|
onGroupClose,
|
||||||
|
} = useGroup();
|
||||||
|
|
||||||
|
const groupOpen = !!openGroupId;
|
||||||
|
|
||||||
|
const groups = groupOpen ? openGroupItems : allGroups;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
function handleDragStart({ active, over }) {
|
||||||
|
setDragId(active.id);
|
||||||
|
setOverId(over?.id);
|
||||||
|
if (!selectedGroupIds.includes(active.id)) {
|
||||||
|
onGroupSelect(active.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver({ over }) {
|
||||||
|
setOverId(over?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd({ active, over }) {
|
||||||
|
setDragId();
|
||||||
|
setOverId();
|
||||||
|
if (!active || !over || active.id === over.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedIndices = selectedGroupIds.map((groupId) =>
|
||||||
|
groups.findIndex((group) => group.id === groupId)
|
||||||
|
);
|
||||||
|
// Maintain current group sorting
|
||||||
|
selectedIndices = selectedIndices.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
if (over.id.startsWith(GROUP_ID_PREFIX)) {
|
||||||
|
// Handle tile group
|
||||||
|
const overId = over.id.slice(9);
|
||||||
|
if (overId === active.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const overGroupIndex = groups.findIndex((group) => group.id === overId);
|
||||||
|
onGroupsChange(
|
||||||
|
moveGroupsInto(groups, overGroupIndex, selectedIndices),
|
||||||
|
openGroupId
|
||||||
|
);
|
||||||
|
onGroupSelect();
|
||||||
|
} else if (over.id.startsWith(UNGROUP_ID_PREFIX)) {
|
||||||
|
// Handle tile ungroup
|
||||||
|
const newGroups = ungroup(allGroups, openGroupId, selectedIndices);
|
||||||
|
// Close group if it was removed
|
||||||
|
if (!newGroups.find((group) => group.id === openGroupId)) {
|
||||||
|
onGroupClose();
|
||||||
|
}
|
||||||
|
onGroupsChange(newGroups);
|
||||||
|
onGroupSelect();
|
||||||
|
} else {
|
||||||
|
// Hanlde tile move
|
||||||
|
const overGroupIndex = groups.findIndex((group) => group.id === over.id);
|
||||||
|
onGroupsChange(
|
||||||
|
moveGroups(groups, overGroupIndex, selectedIndices),
|
||||||
|
openGroupId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function customCollisionDetection(rects, rect) {
|
||||||
|
if (groupOpen) {
|
||||||
|
const ungroupRects = rects.filter(([id]) =>
|
||||||
|
id.startsWith(UNGROUP_ID_PREFIX)
|
||||||
|
);
|
||||||
|
const intersectingGroupRect = rectIntersection(ungroupRects, rect);
|
||||||
|
if (intersectingGroupRect) {
|
||||||
|
return intersectingGroupRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherRects = rects.filter(([id]) => id !== UNGROUP_ID_PREFIX);
|
||||||
|
|
||||||
|
return closestCenter(otherRects, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = { dragId, overId };
|
||||||
|
|
||||||
|
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;
|
@ -156,6 +156,39 @@ export function moveGroups(groups, to, indices) {
|
|||||||
return newGroups;
|
return newGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move items from a sub group to the start of the base group
|
||||||
|
* @param {Group[]} groups
|
||||||
|
* @param {string} fromId The id of the group to move from
|
||||||
|
* @param {number[]} indices The indices of the items in the group
|
||||||
|
*/
|
||||||
|
export function ungroup(groups, fromId, indices) {
|
||||||
|
const newGroups = cloneDeep(groups);
|
||||||
|
|
||||||
|
let fromIndex = newGroups.findIndex((group) => group.id === fromId);
|
||||||
|
|
||||||
|
let items = [];
|
||||||
|
for (let i of indices) {
|
||||||
|
items.push(newGroups[fromIndex].items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove items from previous group
|
||||||
|
for (let item of items) {
|
||||||
|
const i = newGroups[fromIndex].items.findIndex((el) => el.id === item.id);
|
||||||
|
newGroups[fromIndex].items.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no more items in the group delete it
|
||||||
|
if (newGroups[fromIndex].items.length === 0) {
|
||||||
|
newGroups.splice(fromIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to base group
|
||||||
|
newGroups.splice(0, 0, ...items);
|
||||||
|
|
||||||
|
return newGroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively find a group within a group array
|
* Recursively find a group within a group array
|
||||||
* @param {Group[]} groups
|
* @param {Group[]} groups
|
||||||
|
@ -24,6 +24,7 @@ import { useAuth } from "../contexts/AuthContext";
|
|||||||
import { useKeyboard } from "../contexts/KeyboardContext";
|
import { useKeyboard } from "../contexts/KeyboardContext";
|
||||||
import { useAssets } from "../contexts/AssetsContext";
|
import { useAssets } from "../contexts/AssetsContext";
|
||||||
import { GroupProvider } from "../contexts/GroupContext";
|
import { GroupProvider } from "../contexts/GroupContext";
|
||||||
|
import { TileDragProvider } from "../contexts/TileDragContext";
|
||||||
|
|
||||||
import shortcuts from "../shortcuts";
|
import shortcuts from "../shortcuts";
|
||||||
|
|
||||||
@ -259,21 +260,25 @@ function SelectMapModal({
|
|||||||
onGroupsSelect={setSelectedGroupIds}
|
onGroupsSelect={setSelectedGroupIds}
|
||||||
disabled={!isOpen}
|
disabled={!isOpen}
|
||||||
>
|
>
|
||||||
<TilesContainer>
|
<TileDragProvider>
|
||||||
<MapTiles
|
<TilesContainer>
|
||||||
maps={maps}
|
<MapTiles
|
||||||
onMapEdit={() => setIsEditModalOpen(true)}
|
maps={maps}
|
||||||
onMapSelect={handleMapSelect}
|
onMapEdit={() => setIsEditModalOpen(true)}
|
||||||
/>
|
onMapSelect={handleMapSelect}
|
||||||
</TilesContainer>
|
/>
|
||||||
<TilesOverlay>
|
</TilesContainer>
|
||||||
<MapTiles
|
</TileDragProvider>
|
||||||
maps={maps}
|
<TileDragProvider>
|
||||||
onMapEdit={() => setIsEditModalOpen(true)}
|
<TilesOverlay>
|
||||||
onMapSelect={handleMapSelect}
|
<MapTiles
|
||||||
subgroup
|
maps={maps}
|
||||||
/>
|
onMapEdit={() => setIsEditModalOpen(true)}
|
||||||
</TilesOverlay>
|
onMapSelect={handleMapSelect}
|
||||||
|
subgroup
|
||||||
|
/>
|
||||||
|
</TilesOverlay>
|
||||||
|
</TileDragProvider>
|
||||||
</GroupProvider>
|
</GroupProvider>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
|
@ -24,6 +24,7 @@ import { useAuth } from "../contexts/AuthContext";
|
|||||||
import { useKeyboard } from "../contexts/KeyboardContext";
|
import { useKeyboard } from "../contexts/KeyboardContext";
|
||||||
import { useAssets } from "../contexts/AssetsContext";
|
import { useAssets } from "../contexts/AssetsContext";
|
||||||
import { GroupProvider } from "../contexts/GroupContext";
|
import { GroupProvider } from "../contexts/GroupContext";
|
||||||
|
import { TileDragProvider } from "../contexts/TileDragContext";
|
||||||
|
|
||||||
import shortcuts from "../shortcuts";
|
import shortcuts from "../shortcuts";
|
||||||
|
|
||||||
@ -200,19 +201,23 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
|||||||
onGroupsSelect={setSelectedGroupIds}
|
onGroupsSelect={setSelectedGroupIds}
|
||||||
disabled={!isOpen}
|
disabled={!isOpen}
|
||||||
>
|
>
|
||||||
<TilesContainer>
|
<TileDragProvider>
|
||||||
<TokenTiles
|
<TilesContainer>
|
||||||
tokens={tokens}
|
<TokenTiles
|
||||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
tokens={tokens}
|
||||||
/>
|
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||||
</TilesContainer>
|
/>
|
||||||
<TilesOverlay>
|
</TilesContainer>
|
||||||
<TokenTiles
|
</TileDragProvider>
|
||||||
tokens={tokens}
|
<TileDragProvider>
|
||||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
<TilesOverlay>
|
||||||
subgroup
|
<TokenTiles
|
||||||
/>
|
tokens={tokens}
|
||||||
</TilesOverlay>
|
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||||
|
subgroup
|
||||||
|
/>
|
||||||
|
</TilesOverlay>
|
||||||
|
</TileDragProvider>
|
||||||
</GroupProvider>
|
</GroupProvider>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user