Add token group drag from token bar
This commit is contained in:
parent
1ae9ce06cb
commit
597141d7fb
@ -2,7 +2,13 @@ import React, { useState, useRef } from "react";
|
|||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Box, Flex } from "theme-ui";
|
import { Box, Flex } from "theme-ui";
|
||||||
import SimpleBar from "simplebar-react";
|
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 TokenBarToken from "./TokenBarToken";
|
||||||
import TokenBarTokenGroup from "./TokenBarTokenGroup";
|
import TokenBarTokenGroup from "./TokenBarTokenGroup";
|
||||||
@ -20,6 +26,8 @@ import {
|
|||||||
createTokenState,
|
createTokenState,
|
||||||
clientPositionToMapPosition,
|
clientPositionToMapPosition,
|
||||||
} from "../../helpers/token";
|
} from "../../helpers/token";
|
||||||
|
import { findGroup } from "../../helpers/group";
|
||||||
|
import Vector2 from "../../helpers/Vector2";
|
||||||
|
|
||||||
function TokenBar({ onMapTokensStateCreate }) {
|
function TokenBar({ onMapTokensStateCreate }) {
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
@ -34,6 +42,11 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
// https://github.com/clauderic/dnd-kit/issues/238
|
// https://github.com/clauderic/dnd-kit/issues/238
|
||||||
const dragOverlayRef = useRef();
|
const dragOverlayRef = useRef();
|
||||||
|
|
||||||
|
const pointerSensor = useSensor(PointerSensor, {
|
||||||
|
activationConstraint: { distance: 5 },
|
||||||
|
});
|
||||||
|
const sensors = useSensors(pointerSensor);
|
||||||
|
|
||||||
function handleDragStart({ active }) {
|
function handleDragStart({ active }) {
|
||||||
setDragId(active.id);
|
setDragId(active.id);
|
||||||
}
|
}
|
||||||
@ -50,44 +63,67 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
y: dragRect.top + dragRect.height / 2,
|
y: dragRect.top + dragRect.height / 2,
|
||||||
};
|
};
|
||||||
const mapPosition = clientPositionToMapPosition(mapStage, dragPosition);
|
const mapPosition = clientPositionToMapPosition(mapStage, dragPosition);
|
||||||
const token = tokensById[active.id];
|
const group = findGroup(tokenGroups, active.id);
|
||||||
if (token && mapPosition) {
|
if (group && mapPosition) {
|
||||||
const tokenState = createTokenState(token, mapPosition, userId);
|
if (group.type === "item") {
|
||||||
onMapTokensStateCreate([tokenState]);
|
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() {
|
function renderToken(group, draggable = true) {
|
||||||
let tokens = [];
|
if (group.type === "item") {
|
||||||
for (let group of tokenGroups) {
|
const token = tokensById[group.id];
|
||||||
if (group.type === "item") {
|
if (token && !token.hideInSidebar) {
|
||||||
const token = tokensById[group.id];
|
if (draggable) {
|
||||||
if (token && !token.hideInSidebar) {
|
return (
|
||||||
tokens.push(
|
|
||||||
<Draggable id={token.id} key={token.id}>
|
<Draggable id={token.id} key={token.id}>
|
||||||
<TokenBarToken token={token} />
|
<TokenBarToken token={token} />
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return <TokenBarToken token={token} key={token.id} />;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const groupTokens = [];
|
|
||||||
for (let item of group.items) {
|
|
||||||
const token = tokensById[item.id];
|
|
||||||
if (token && !token.hideInSidebar) {
|
|
||||||
groupTokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens.push(
|
|
||||||
<TokenBarTokenGroup
|
|
||||||
group={group}
|
|
||||||
tokens={groupTokens}
|
|
||||||
key={group.id}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const groupTokens = [];
|
||||||
|
for (let item of group.items) {
|
||||||
|
const token = tokensById[item.id];
|
||||||
|
if (token && !token.hideInSidebar) {
|
||||||
|
groupTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TokenBarTokenGroup
|
||||||
|
group={group}
|
||||||
|
tokens={groupTokens}
|
||||||
|
key={group.id}
|
||||||
|
draggable={draggable}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return tokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -95,6 +131,7 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
autoScroll={false}
|
autoScroll={false}
|
||||||
|
sensors={sensors}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -113,7 +150,9 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
padding: "0 16px",
|
padding: "0 16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex sx={{ flexDirection: "column" }}>{renderTokens()}</Flex>
|
<Flex sx={{ flexDirection: "column" }}>
|
||||||
|
{tokenGroups.map((group) => renderToken(group))}
|
||||||
|
</Flex>
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
<Flex
|
<Flex
|
||||||
bg="muted"
|
bg="muted"
|
||||||
@ -136,7 +175,7 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
>
|
>
|
||||||
{dragId && (
|
{dragId && (
|
||||||
<div ref={dragOverlayRef}>
|
<div ref={dragOverlayRef}>
|
||||||
<TokenBarToken token={tokensById[dragId]} />
|
{renderToken(findGroup(tokenGroups, dragId), false)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</DragOverlay>,
|
</DragOverlay>,
|
||||||
|
@ -21,6 +21,7 @@ function TokenBarToken({ token }) {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
alt={token.name}
|
alt={token.name}
|
||||||
title={token.name}
|
title={token.name}
|
||||||
|
@ -1,84 +1,131 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useRef } from "react";
|
||||||
import { Grid, Flex } from "theme-ui";
|
import { Grid, Flex, Box } from "theme-ui";
|
||||||
import { useSpring, animated } from "react-spring";
|
import { useSpring, animated } from "react-spring";
|
||||||
|
import { useDraggable } from "@dnd-kit/core";
|
||||||
|
|
||||||
import TokenImage from "./TokenImage";
|
import TokenImage from "./TokenImage";
|
||||||
import TokenBarToken from "./TokenBarToken";
|
import TokenBarToken from "./TokenBarToken";
|
||||||
|
|
||||||
import Draggable from "../drag/Draggable";
|
import Draggable from "../drag/Draggable";
|
||||||
|
|
||||||
|
import Vector2 from "../../helpers/Vector2";
|
||||||
|
|
||||||
import GroupIcon from "../../icons/GroupIcon";
|
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 [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const { height } = useSpring({
|
const { height } = useSpring({
|
||||||
height: isOpen ? (tokens.length + 1) * 56 : 56,
|
height: isOpen ? (tokens.length + 1) * 56 : 56,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderToken(token) {
|
||||||
|
if (draggable) {
|
||||||
|
return (
|
||||||
|
<Draggable id={token.id} key={token.id}>
|
||||||
|
<TokenBarToken token={token} />
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <TokenBarToken token={token} key={token.id} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderTokens() {
|
function renderTokens() {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
return (
|
return (
|
||||||
<>
|
<Grid
|
||||||
|
columns="1fr"
|
||||||
|
alt={group.name}
|
||||||
|
title={group.name}
|
||||||
|
bg="muted"
|
||||||
|
sx={{ borderRadius: "8px", gridGap: 0 }}
|
||||||
|
p={0}
|
||||||
|
>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
width: "48px",
|
width: "48px",
|
||||||
height: "48px",
|
height: "48px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
cursor: "pointer",
|
cursor: isDragging ? "grabbing" : "pointer",
|
||||||
color: "primary",
|
color: "primary",
|
||||||
}}
|
}}
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={(e) => handleOpenClick(e, false)}
|
||||||
key="group"
|
key="group"
|
||||||
alt={group.name}
|
alt={group.name}
|
||||||
title={group.name}
|
title={group.name}
|
||||||
|
{...listeners}
|
||||||
|
{...attributes}
|
||||||
>
|
>
|
||||||
<GroupIcon />
|
<GroupIcon />
|
||||||
</Flex>
|
</Flex>
|
||||||
{tokens.map((token) => (
|
{tokens.map(renderToken)}
|
||||||
<Draggable id={token.id} key={token.id}>
|
</Grid>
|
||||||
<TokenBarToken token={token} />
|
|
||||||
</Draggable>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return tokens.slice(0, 4).map((token) => (
|
return (
|
||||||
<TokenImage
|
<Grid
|
||||||
token={token}
|
columns="2fr 2fr"
|
||||||
key={token.id}
|
alt={group.name}
|
||||||
sx={{
|
title={group.name}
|
||||||
userSelect: "none",
|
bg="muted"
|
||||||
touchAction: "none",
|
sx={{ borderRadius: "8px", gridGap: "4px" }}
|
||||||
pointerEvents: "none",
|
p="2px"
|
||||||
}}
|
{...listeners}
|
||||||
/>
|
{...attributes}
|
||||||
));
|
>
|
||||||
|
{tokens.slice(0, 4).map((token) => (
|
||||||
|
<TokenImage
|
||||||
|
token={token}
|
||||||
|
key={token.id}
|
||||||
|
sx={{
|
||||||
|
userSelect: "none",
|
||||||
|
touchAction: "none",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<animated.div
|
<Box ref={setNodeRef}>
|
||||||
style={{
|
<animated.div
|
||||||
padding: "4px 0",
|
style={{
|
||||||
width: "48px",
|
padding: "4px 0",
|
||||||
height,
|
width: "48px",
|
||||||
cursor: isOpen ? "default" : "pointer",
|
height,
|
||||||
}}
|
cursor: isOpen ? "default" : isDragging ? "grabbing" : "pointer",
|
||||||
onClick={() => !isOpen && setIsOpen(true)}
|
}}
|
||||||
>
|
onPointerDown={handleOpenDown}
|
||||||
<Grid
|
onClick={(e) => !isOpen && handleOpenClick(e, true)}
|
||||||
columns={isOpen ? "1fr" : "2fr 2fr"}
|
|
||||||
alt={group.name}
|
|
||||||
title={group.name}
|
|
||||||
bg="muted"
|
|
||||||
sx={{ borderRadius: "8px", gridGap: isOpen ? 0 : "4px" }}
|
|
||||||
p={isOpen ? 0 : "2px"}
|
|
||||||
>
|
>
|
||||||
{renderTokens()}
|
{renderTokens()}
|
||||||
</Grid>
|
</animated.div>
|
||||||
</animated.div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user