diff --git a/src/components/token/TokenBar.js b/src/components/token/TokenBar.js index ec7539a..4279c1b 100644 --- a/src/components/token/TokenBar.js +++ b/src/components/token/TokenBar.js @@ -2,7 +2,13 @@ import React, { useState, useRef } from "react"; import { createPortal } from "react-dom"; import { Box, Flex } from "theme-ui"; import SimpleBar from "simplebar-react"; -import { DragOverlay, DndContext } from "@dnd-kit/core"; +import { + DragOverlay, + DndContext, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; import TokenBarToken from "./TokenBarToken"; import TokenBarTokenGroup from "./TokenBarTokenGroup"; @@ -20,6 +26,8 @@ import { createTokenState, clientPositionToMapPosition, } from "../../helpers/token"; +import { findGroup } from "../../helpers/group"; +import Vector2 from "../../helpers/Vector2"; function TokenBar({ onMapTokensStateCreate }) { const { userId } = useAuth(); @@ -34,6 +42,11 @@ function TokenBar({ onMapTokensStateCreate }) { // https://github.com/clauderic/dnd-kit/issues/238 const dragOverlayRef = useRef(); + const pointerSensor = useSensor(PointerSensor, { + activationConstraint: { distance: 5 }, + }); + const sensors = useSensors(pointerSensor); + function handleDragStart({ active }) { setDragId(active.id); } @@ -50,44 +63,67 @@ function TokenBar({ onMapTokensStateCreate }) { y: dragRect.top + dragRect.height / 2, }; const mapPosition = clientPositionToMapPosition(mapStage, dragPosition); - const token = tokensById[active.id]; - if (token && mapPosition) { - const tokenState = createTokenState(token, mapPosition, userId); - onMapTokensStateCreate([tokenState]); + const group = findGroup(tokenGroups, active.id); + if (group && mapPosition) { + if (group.type === "item") { + const token = tokensById[group.id]; + const tokenState = createTokenState(token, mapPosition, userId); + onMapTokensStateCreate([tokenState]); + } else { + let tokenStates = []; + let offset = new Vector2(0, 0); + for (let item of group.items) { + const token = tokensById[item.id]; + if (token) { + tokenStates.push( + createTokenState( + token, + Vector2.add(mapPosition, offset), + userId + ) + ); + offset = Vector2.add(offset, 0.01); + } + } + if (tokenStates.length > 0) { + onMapTokensStateCreate(tokenStates); + } + } } } } - function renderTokens() { - let tokens = []; - for (let group of tokenGroups) { - if (group.type === "item") { - const token = tokensById[group.id]; - if (token && !token.hideInSidebar) { - tokens.push( + function renderToken(group, draggable = true) { + if (group.type === "item") { + const token = tokensById[group.id]; + if (token && !token.hideInSidebar) { + if (draggable) { + return ( ); + } else { + return ; } - } else { - const groupTokens = []; - for (let item of group.items) { - const token = tokensById[item.id]; - if (token && !token.hideInSidebar) { - groupTokens.push(token); - } - } - tokens.push( - - ); } + } else { + const groupTokens = []; + for (let item of group.items) { + const token = tokensById[item.id]; + if (token && !token.hideInSidebar) { + groupTokens.push(token); + } + } + return ( + + ); } - return tokens; } return ( @@ -95,6 +131,7 @@ function TokenBar({ onMapTokensStateCreate }) { onDragStart={handleDragStart} onDragEnd={handleDragEnd} autoScroll={false} + sensors={sensors} > - {renderTokens()} + + {tokenGroups.map((group) => renderToken(group))} + {dragId && (
- + {renderToken(findGroup(tokenGroups, dragId), false)}
)} , diff --git a/src/components/token/TokenBarToken.js b/src/components/token/TokenBarToken.js index f6dd70f..7baa2fa 100644 --- a/src/components/token/TokenBarToken.js +++ b/src/components/token/TokenBarToken.js @@ -21,6 +21,7 @@ function TokenBarToken({ token }) { width: "100%", height: "100%", objectFit: "cover", + pointerEvents: "none", }} alt={token.name} title={token.name} diff --git a/src/components/token/TokenBarTokenGroup.js b/src/components/token/TokenBarTokenGroup.js index 5494fad..25dc12d 100644 --- a/src/components/token/TokenBarTokenGroup.js +++ b/src/components/token/TokenBarTokenGroup.js @@ -1,84 +1,131 @@ -import React, { useState } from "react"; -import { Grid, Flex } from "theme-ui"; +import React, { useState, useRef } from "react"; +import { Grid, Flex, Box } from "theme-ui"; import { useSpring, animated } from "react-spring"; +import { useDraggable } from "@dnd-kit/core"; import TokenImage from "./TokenImage"; import TokenBarToken from "./TokenBarToken"; import Draggable from "../drag/Draggable"; +import Vector2 from "../../helpers/Vector2"; + import GroupIcon from "../../icons/GroupIcon"; -function TokenBarTokenGroup({ group, tokens }) { +function TokenBarTokenGroup({ group, tokens, draggable }) { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ + id: draggable && group.id, + disabled: !draggable, + }); const [isOpen, setIsOpen] = useState(false); const { height } = useSpring({ height: isOpen ? (tokens.length + 1) * 56 : 56, }); + function renderToken(token) { + if (draggable) { + return ( + + + + ); + } else { + return ; + } + } + function renderTokens() { if (isOpen) { return ( - <> + setIsOpen(false)} + onClick={(e) => handleOpenClick(e, false)} key="group" alt={group.name} title={group.name} + {...listeners} + {...attributes} > - {tokens.map((token) => ( - - - - ))} - + {tokens.map(renderToken)} + ); } else { - return tokens.slice(0, 4).map((token) => ( - - )); + return ( + + {tokens.slice(0, 4).map((token) => ( + + ))} + + ); + } + } + + // Reject the opening of a group if the pointer has moved + const clickDownPositionRef = useRef(new Vector2(0, 0)); + function handleOpenDown(event) { + clickDownPositionRef.current = new Vector2(event.clientX, event.clientY); + } + function handleOpenClick(event, newOpen) { + const clickPosition = new Vector2(event.clientX, event.clientY); + const distance = Vector2.distance( + clickPosition, + clickDownPositionRef.current + ); + if (distance < 5) { + setIsOpen(newOpen); } } return ( - !isOpen && setIsOpen(true)} - > - + !isOpen && handleOpenClick(e, true)} > {renderTokens()} - - + +
); }