Added lock and visibility options to tokens

This commit is contained in:
Mitchell McCaffrey
2020-08-27 17:30:17 +10:00
parent 172df0f6ac
commit 9ab584cbe7
10 changed files with 188 additions and 15 deletions

View File

@@ -228,7 +228,9 @@ function Map({
})
}
draggable={
selectedToolId === "pan" && !(tokenState.id in disabledTokens)
selectedToolId === "pan" &&
!(tokenState.id in disabledTokens) &&
!tokenState.locked
}
mapState={mapState}
fadeOnHover={selectedToolId === "drawing"}
@@ -245,6 +247,7 @@ function Map({
onTokenStateChange={onMapTokenStateChange}
tokenState={mapState && mapState.tokens[tokenMenuOptions.tokenStateId]}
tokenImage={tokenMenuOptions.tokenImage}
map={map}
/>
);

View File

@@ -113,7 +113,8 @@ function MapToken({
...mapState.tokens[mountedToken.id()],
x: mountedToken.x() / mapWidth,
y: mountedToken.y() / mapHeight,
lastEditedBy: userId,
lastModifiedBy: userId,
lastModified: Date.now(),
};
}
}
@@ -125,7 +126,8 @@ function MapToken({
...tokenState,
x: tokenGroup.x() / mapWidth,
y: tokenGroup.y() / mapHeight,
lastEditedBy: userId,
lastModifiedBy: userId,
lastModified: Date.now(),
},
});
onTokenDragEnd(event);
@@ -139,16 +141,37 @@ function MapToken({
}
const [tokenOpacity, setTokenOpacity] = useState(1);
function handlePointerDown() {
// Store token pointer down position to check for a click when token is locked
const tokenPointerDownPositionRef = useRef();
function handlePointerDown(event) {
if (draggable) {
setPreventMapInteraction(true);
}
if (tokenState.locked && map.owner === userId) {
const pointerPosition = { x: event.evt.clientX, y: event.evt.clientY };
tokenPointerDownPositionRef.current = pointerPosition;
}
}
function handlePointerUp() {
function handlePointerUp(event) {
if (draggable) {
setPreventMapInteraction(false);
}
// Check token click when locked and we are the map owner
// We can't use onClick because that doesn't check pointer distance
if (tokenState.locked && map.owner === userId) {
// If down and up distance is small trigger a click
const pointerPosition = { x: event.evt.clientX, y: event.evt.clientY };
const distance = Vector2.distance(
tokenPointerDownPositionRef.current,
pointerPosition,
"euclidean"
);
if (distance < 5) {
const tokenImage = event.target;
onTokenMenuOpen(tokenState.id, tokenImage);
}
}
}
function handlePointerEnter() {
@@ -192,13 +215,18 @@ function MapToken({
const previousWidth = usePrevious(mapWidth);
const previousHeight = usePrevious(mapHeight);
const resized = mapWidth !== previousWidth || mapHeight !== previousHeight;
const skipAnimation = tokenState.lastEditedBy === userId || resized;
const skipAnimation = tokenState.lastModifiedBy === userId || resized;
const props = useSpring({
x: tokenX,
y: tokenY,
immediate: skipAnimation,
});
// When a token is hidden if you aren't the map owner hide it completely
if (map && !tokenState.visible && map.owner !== userId) {
return null;
}
return (
<animated.Group
{...props}
@@ -216,7 +244,7 @@ function MapToken({
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
opacity={tokenOpacity}
opacity={tokenState.visible ? tokenOpacity : 0.5}
name={token && token.isVehicle ? "vehicle" : "token"}
id={tokenState.id}
>

View File

@@ -79,7 +79,8 @@ function TokenDragOverlay({
...mapState.tokens[mountedToken.id()],
x: mountedToken.x() / mapWidth,
y: mountedToken.y() / mapHeight,
lastEditedBy: userId,
lastModifiedBy: userId,
lastModified: Date.now(),
},
});
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { Box, Input, Slider, Flex, Text } from "theme-ui";
import React, { useEffect, useState, useContext } from "react";
import { Box, Input, Slider, Flex, Text, IconButton } from "theme-ui";
import MapMenu from "../map/MapMenu";
@@ -7,6 +7,13 @@ import colors, { colorOptions } from "../../helpers/colors";
import usePrevious from "../../helpers/usePrevious";
import LockIcon from "../../icons/TokenLockIcon";
import UnlockIcon from "../../icons/TokenUnlockIcon";
import ShowIcon from "../../icons/TokenShowIcon";
import HideIcon from "../../icons/TokenHideIcon";
import AuthContext from "../../contexts/AuthContext";
const defaultTokenMaxSize = 6;
function TokenMenu({
isOpen,
@@ -14,7 +21,10 @@ function TokenMenu({
tokenState,
tokenImage,
onTokenStateChange,
map,
}) {
const { userId } = useContext(AuthContext);
const wasOpen = usePrevious(isOpen);
const [tokenMaxSize, setTokenMaxSize] = useState(defaultTokenMaxSize);
@@ -26,8 +36,8 @@ function TokenMenu({
// Update menu position
if (tokenImage) {
const imageRect = tokenImage.getClientRect();
const map = document.querySelector(".map");
const mapRect = map.getBoundingClientRect();
const mapElement = document.querySelector(".map");
const mapRect = mapElement.getBoundingClientRect();
// Center X for the menu which is 156px wide
setMenuLeft(mapRect.left + imageRect.x + imageRect.width / 2 - 156 / 2);
@@ -67,6 +77,18 @@ function TokenMenu({
});
}
function handleVisibleChange() {
onTokenStateChange({
[tokenState.id]: { ...tokenState, visible: !tokenState.visible },
});
}
function handleLockChange() {
onTokenStateChange({
[tokenState.id]: { ...tokenState, locked: !tokenState.locked },
});
}
function handleModalContent(node) {
if (node) {
// Focus input
@@ -76,8 +98,8 @@ function TokenMenu({
// Ensure menu is in bounds
const nodeRect = node.getBoundingClientRect();
const map = document.querySelector(".map");
const mapRect = map.getBoundingClientRect();
const mapElement = document.querySelector(".map");
const mapRect = mapElement.getBoundingClientRect();
setMenuLeft((prevLeft) =>
Math.min(
mapRect.right - nodeRect.width,
@@ -203,6 +225,33 @@ function TokenMenu({
mr={1}
/>
</Flex>
{/* Only show hide and lock token actions to map owners */}
{map && map.owner === userId && (
<Flex sx={{ alignItems: "center", justifyContent: "space-around" }}>
<IconButton
onClick={handleVisibleChange}
title={
tokenState && tokenState.visible ? "Hide Token" : "Show Token"
}
aria-label={
tokenState && tokenState.visible ? "Hide Token" : "Show Token"
}
>
{tokenState && tokenState.visible ? <ShowIcon /> : <HideIcon />}
</IconButton>
<IconButton
onClick={handleLockChange}
title={
tokenState && tokenState.locked ? "Unlock Token" : "Lock Token"
}
aria-label={
tokenState && tokenState.locked ? "Unlock Token" : "Lock Token"
}
>
{tokenState && tokenState.locked ? <LockIcon /> : <UnlockIcon />}
</IconButton>
</Flex>
)}
</Box>
</MapMenu>
);

View File

@@ -31,8 +31,11 @@ function Tokens({ onMapTokenStateCreate }) {
statuses: [],
x: token.x,
y: token.y,
lastEditedBy: userId,
lastModifiedBy: userId,
lastModified: Date.now(),
rotation: 0,
locked: false,
visible: true,
});
}
}