diff --git a/src/components/map/MapTiles.js b/src/components/map/MapTiles.js index 044575e..811bb0c 100644 --- a/src/components/map/MapTiles.js +++ b/src/components/map/MapTiles.js @@ -4,6 +4,7 @@ import MapTile from "./MapTile"; import MapTileGroup from "./MapTileGroup"; import SortableTiles from "../tile/SortableTiles"; +import SortableTilesDragOverlay from "../tile/SortableTilesDragOverlay"; import { getGroupItems } from "../../helpers/group"; @@ -57,7 +58,12 @@ function MapTiles({ mapsById, onMapEdit, onMapSelect, subgroup }) { } } - return ; + return ( + <> + + + + ); } export default MapTiles; diff --git a/src/components/tile/SortableTiles.js b/src/components/tile/SortableTiles.js index 257cc6e..404102f 100644 --- a/src/components/tile/SortableTiles.js +++ b/src/components/tile/SortableTiles.js @@ -1,26 +1,24 @@ import React from "react"; -import { createPortal } from "react-dom"; -import { DragOverlay } from "@dnd-kit/core"; import { SortableContext } from "@dnd-kit/sortable"; -import { animated, useSpring, config } from "react-spring"; -import { Badge } from "theme-ui"; import { moveGroupsInto } from "../../helpers/group"; import { keyBy } from "../../helpers/shared"; -import Vector2 from "../../helpers/Vector2"; import SortableTile from "./SortableTile"; import { - useTileDrag, + useTileDragId, + useTileDragCursor, + useTileOverGroupId, BASE_SORTABLE_ID, GROUP_SORTABLE_ID, - GROUP_ID_PREFIX, } from "../../contexts/TileDragContext"; import { useGroup } from "../../contexts/GroupContext"; function SortableTiles({ renderTile, subgroup }) { - const { dragId, overId, dragCursor } = useTileDrag(); + const dragId = useTileDragId(); + const dragCursor = useTileDragCursor(); + const overGroupId = useTileOverGroupId(); const { groups, selectedGroupIds: allSelectedIds, @@ -46,15 +44,6 @@ function SortableTiles({ renderTile, subgroup }) { const disableSorting = (openGroupId && !subgroup) || filter; const disableGrouping = subgroup || disableSorting || filter; - const dragBounce = useSpring({ - transform: !!dragId ? "scale(0.9)" : "scale(1)", - config: config.wobbly, - position: "relative", - }); - - const overGroupId = - overId && overId.startsWith(GROUP_ID_PREFIX) && overId.slice(9); - function renderSortableGroup(group, selectedGroups) { if (overGroupId === group.id && dragId && group.id !== dragId) { // If dragging over a group render a preview of that group @@ -68,57 +57,6 @@ function SortableTiles({ renderTile, subgroup }) { return renderTile(group); } - function renderDragOverlays() { - let selectedIndices = selectedGroupIds.map((groupId) => - activeGroups.findIndex((group) => group.id === groupId) - ); - const activeIndex = activeGroups.findIndex((group) => group.id === dragId); - // Sort so the draging tile is the first element - selectedIndices = selectedIndices.sort((a, b) => - a === activeIndex ? -1 : b === activeIndex ? 1 : 0 - ); - - selectedIndices = selectedIndices.slice(0, 5); - - let coords = selectedIndices.map( - (_, index) => new Vector2(5 * index, 5 * index) - ); - - // Reverse so the first element is rendered on top - selectedIndices = selectedIndices.reverse(); - coords = coords.reverse(); - - const selectedGroups = selectedIndices.map((index) => activeGroups[index]); - - return selectedGroups.map((group, index) => ( - -
- - {renderTile(group)} - {index === selectedIndices.length - 1 && - selectedGroupIds.length > 1 && ( - - {selectedGroupIds.length} - - )} - -
-
- )); - } - function renderTiles() { const groupsByIds = keyBy(activeGroups, "id"); const selectedGroupIdsSet = new Set(selectedGroupIds); @@ -156,7 +94,6 @@ function SortableTiles({ renderTile, subgroup }) { return ( {renderTiles()} - {createPortal(dragId && renderDragOverlays(), document.body)} ); } diff --git a/src/components/tile/SortableTilesDragOverlay.js b/src/components/tile/SortableTilesDragOverlay.js new file mode 100644 index 0000000..848b793 --- /dev/null +++ b/src/components/tile/SortableTilesDragOverlay.js @@ -0,0 +1,93 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import { DragOverlay } from "@dnd-kit/core"; +import { animated, useSpring, config } from "react-spring"; +import { Badge } from "theme-ui"; + +import Vector2 from "../../helpers/Vector2"; + +import { useTileDragId } from "../../contexts/TileDragContext"; +import { useGroup } from "../../contexts/GroupContext"; + +function SortableTilesDragOverlay({ renderTile, subgroup }) { + const dragId = useTileDragId(); + const { + groups, + selectedGroupIds: allSelectedIds, + filter, + openGroupId, + openGroupItems, + filteredGroupItems, + } = useGroup(); + + const activeGroups = subgroup + ? openGroupItems + : filter + ? filteredGroupItems + : groups; + + // Only populate selected groups if needed + let selectedGroupIds = []; + if ((subgroup && openGroupId) || (!subgroup && !openGroupId)) { + selectedGroupIds = allSelectedIds; + } + const dragBounce = useSpring({ + transform: !!dragId ? "scale(0.9)" : "scale(1)", + config: config.wobbly, + position: "relative", + }); + + function renderDragOverlays() { + let selectedIndices = selectedGroupIds.map((groupId) => + activeGroups.findIndex((group) => group.id === groupId) + ); + const activeIndex = activeGroups.findIndex((group) => group.id === dragId); + // Sort so the draging tile is the first element + selectedIndices = selectedIndices.sort((a, b) => + a === activeIndex ? -1 : b === activeIndex ? 1 : 0 + ); + + selectedIndices = selectedIndices.slice(0, 5); + + let coords = selectedIndices.map( + (_, index) => new Vector2(5 * index, 5 * index) + ); + + // Reverse so the first element is rendered on top + selectedIndices = selectedIndices.reverse(); + coords = coords.reverse(); + + const selectedGroups = selectedIndices.map((index) => activeGroups[index]); + + return selectedGroups.map((group, index) => ( + +
+ + {renderTile(group)} + {index === selectedIndices.length - 1 && + selectedGroupIds.length > 1 && ( + + {selectedGroupIds.length} + + )} + +
+
+ )); + } + + return createPortal(dragId && renderDragOverlays(), document.body); +} + +export default SortableTilesDragOverlay; diff --git a/src/components/tile/Tile.js b/src/components/tile/Tile.js index 9977dc8..115a648 100644 --- a/src/components/tile/Tile.js +++ b/src/components/tile/Tile.js @@ -16,17 +16,14 @@ function Tile({ children, }) { const [ref, inView] = useInView({ triggerOnce: true }); - return ( - )} - + ); } diff --git a/src/components/token/TokenTiles.js b/src/components/token/TokenTiles.js index e61277c..55b5948 100644 --- a/src/components/token/TokenTiles.js +++ b/src/components/token/TokenTiles.js @@ -5,6 +5,7 @@ import TokenTileGroup from "./TokenTileGroup"; import TokenHiddenBadge from "./TokenHiddenBadge"; import SortableTiles from "../tile/SortableTiles"; +import SortableTilesDragOverlay from "../tile/SortableTilesDragOverlay"; import { getGroupItems } from "../../helpers/group"; @@ -61,7 +62,12 @@ function TokenTiles({ tokensById, onTokenEdit, subgroup }) { } } - return ; + return ( + <> + + + + ); } export default TokenTiles; diff --git a/src/contexts/TileDragContext.js b/src/contexts/TileDragContext.js index a146846..3663f5a 100644 --- a/src/contexts/TileDragContext.js +++ b/src/contexts/TileDragContext.js @@ -1,4 +1,4 @@ -import React, { useState, useContext } from "react"; +import React, { useState, useContext, useEffect } from "react"; import { MouseSensor, TouchSensor, @@ -16,7 +16,9 @@ import { moveGroupsInto, moveGroups, ungroup } from "../helpers/group"; import usePreventSelect from "../hooks/usePreventSelect"; -const TileDragContext = React.createContext(); +const TileDragIdContext = React.createContext(); +const TileOverGroupIdContext = React.createContext(); +const TileDragCursorContext = React.createContext(); export const BASE_SORTABLE_ID = "__base__"; export const GROUP_SORTABLE_ID = "__group__"; @@ -61,7 +63,7 @@ export function TileDragProvider({ } = useGroup(); const mouseSensor = useSensor(MouseSensor, { - activationConstraint: { distance: 5 }, + activationConstraint: { distance: 3 }, }); const touchSensor = useSensor(TouchSensor, { activationConstraint: { delay: 250, tolerance: 5 }, @@ -70,16 +72,23 @@ export function TileDragProvider({ const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor); - const [dragId, setDragId] = useState(); - const [overId, setOverId] = useState(); + const [dragId, setDragId] = useState(null); + const [overId, setOverId] = useState(null); const [dragCursor, setDragCursor] = useState("pointer"); const [preventSelect, resumeSelect] = usePreventSelect(); + const [overGroupId, setOverGroupId] = useState(null); + useEffect(() => { + setOverGroupId( + (overId && overId.startsWith(GROUP_ID_PREFIX) && overId.slice(9)) || null + ); + }, [overId]); + function handleDragStart(event) { const { active, over } = event; setDragId(active.id); - setOverId(over?.id); + setOverId(over?.id || null); if (!selectedGroupIds.includes(active.id)) { onGroupSelect(active.id); } @@ -93,7 +102,7 @@ export function TileDragProvider({ function handleDragOver(event) { const { over } = event; - setOverId(over?.id); + setOverId(over?.id || null); if (over) { if ( over.id.startsWith(UNGROUP_ID) || @@ -111,8 +120,8 @@ export function TileDragProvider({ function handleDragEnd(event) { const { active, over, overlayNodeClientRect } = event; - setDragId(); - setOverId(); + setDragId(null); + setOverId(null); setDragCursor("pointer"); if (active && over && active.id !== over.id) { let selectedIndices = selectedGroupIds.map((groupId) => @@ -165,8 +174,8 @@ export function TileDragProvider({ } function handleDragCancel(event) { - setDragId(); - setOverId(); + setDragId(null); + setOverId(null); setDragCursor("pointer"); resumeSelect(); @@ -210,8 +219,6 @@ export function TileDragProvider({ return closestCenter(otherRects, rect); } - const value = { dragId, overId, dragCursor }; - return ( - - {children} - + + + + {children} + + + ); } -export function useTileDrag() { - const context = useContext(TileDragContext); +export function useTileDragId() { + const context = useContext(TileDragIdContext); if (context === undefined) { throw new Error("useTileDrag must be used within a TileDragProvider"); } return context; } -export default TileDragContext; +export function useTileOverGroupId() { + const context = useContext(TileOverGroupIdContext); + if (context === undefined) { + throw new Error("useTileDrag must be used within a TileDragProvider"); + } + return context; +} + +export function useTileDragCursor() { + const context = useContext(TileDragCursorContext); + if (context === undefined) { + throw new Error("useTileDrag must be used within a TileDragProvider"); + } + return context; +}