Added lock and visibility options to tokens
This commit is contained in:
parent
172df0f6ac
commit
9ab584cbe7
@ -228,7 +228,9 @@ function Map({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
draggable={
|
draggable={
|
||||||
selectedToolId === "pan" && !(tokenState.id in disabledTokens)
|
selectedToolId === "pan" &&
|
||||||
|
!(tokenState.id in disabledTokens) &&
|
||||||
|
!tokenState.locked
|
||||||
}
|
}
|
||||||
mapState={mapState}
|
mapState={mapState}
|
||||||
fadeOnHover={selectedToolId === "drawing"}
|
fadeOnHover={selectedToolId === "drawing"}
|
||||||
@ -245,6 +247,7 @@ function Map({
|
|||||||
onTokenStateChange={onMapTokenStateChange}
|
onTokenStateChange={onMapTokenStateChange}
|
||||||
tokenState={mapState && mapState.tokens[tokenMenuOptions.tokenStateId]}
|
tokenState={mapState && mapState.tokens[tokenMenuOptions.tokenStateId]}
|
||||||
tokenImage={tokenMenuOptions.tokenImage}
|
tokenImage={tokenMenuOptions.tokenImage}
|
||||||
|
map={map}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -113,7 +113,8 @@ function MapToken({
|
|||||||
...mapState.tokens[mountedToken.id()],
|
...mapState.tokens[mountedToken.id()],
|
||||||
x: mountedToken.x() / mapWidth,
|
x: mountedToken.x() / mapWidth,
|
||||||
y: mountedToken.y() / mapHeight,
|
y: mountedToken.y() / mapHeight,
|
||||||
lastEditedBy: userId,
|
lastModifiedBy: userId,
|
||||||
|
lastModified: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,7 +126,8 @@ function MapToken({
|
|||||||
...tokenState,
|
...tokenState,
|
||||||
x: tokenGroup.x() / mapWidth,
|
x: tokenGroup.x() / mapWidth,
|
||||||
y: tokenGroup.y() / mapHeight,
|
y: tokenGroup.y() / mapHeight,
|
||||||
lastEditedBy: userId,
|
lastModifiedBy: userId,
|
||||||
|
lastModified: Date.now(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
onTokenDragEnd(event);
|
onTokenDragEnd(event);
|
||||||
@ -139,16 +141,37 @@ function MapToken({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [tokenOpacity, setTokenOpacity] = useState(1);
|
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) {
|
if (draggable) {
|
||||||
setPreventMapInteraction(true);
|
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) {
|
if (draggable) {
|
||||||
setPreventMapInteraction(false);
|
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() {
|
function handlePointerEnter() {
|
||||||
@ -192,13 +215,18 @@ function MapToken({
|
|||||||
const previousWidth = usePrevious(mapWidth);
|
const previousWidth = usePrevious(mapWidth);
|
||||||
const previousHeight = usePrevious(mapHeight);
|
const previousHeight = usePrevious(mapHeight);
|
||||||
const resized = mapWidth !== previousWidth || mapHeight !== previousHeight;
|
const resized = mapWidth !== previousWidth || mapHeight !== previousHeight;
|
||||||
const skipAnimation = tokenState.lastEditedBy === userId || resized;
|
const skipAnimation = tokenState.lastModifiedBy === userId || resized;
|
||||||
const props = useSpring({
|
const props = useSpring({
|
||||||
x: tokenX,
|
x: tokenX,
|
||||||
y: tokenY,
|
y: tokenY,
|
||||||
immediate: skipAnimation,
|
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 (
|
return (
|
||||||
<animated.Group
|
<animated.Group
|
||||||
{...props}
|
{...props}
|
||||||
@ -216,7 +244,7 @@ function MapToken({
|
|||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragMove={handleDragMove}
|
onDragMove={handleDragMove}
|
||||||
opacity={tokenOpacity}
|
opacity={tokenState.visible ? tokenOpacity : 0.5}
|
||||||
name={token && token.isVehicle ? "vehicle" : "token"}
|
name={token && token.isVehicle ? "vehicle" : "token"}
|
||||||
id={tokenState.id}
|
id={tokenState.id}
|
||||||
>
|
>
|
||||||
|
@ -79,7 +79,8 @@ function TokenDragOverlay({
|
|||||||
...mapState.tokens[mountedToken.id()],
|
...mapState.tokens[mountedToken.id()],
|
||||||
x: mountedToken.x() / mapWidth,
|
x: mountedToken.x() / mapWidth,
|
||||||
y: mountedToken.y() / mapHeight,
|
y: mountedToken.y() / mapHeight,
|
||||||
lastEditedBy: userId,
|
lastModifiedBy: userId,
|
||||||
|
lastModified: Date.now(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useContext } from "react";
|
||||||
import { Box, Input, Slider, Flex, Text } from "theme-ui";
|
import { Box, Input, Slider, Flex, Text, IconButton } from "theme-ui";
|
||||||
|
|
||||||
import MapMenu from "../map/MapMenu";
|
import MapMenu from "../map/MapMenu";
|
||||||
|
|
||||||
@ -7,6 +7,13 @@ import colors, { colorOptions } from "../../helpers/colors";
|
|||||||
|
|
||||||
import usePrevious from "../../helpers/usePrevious";
|
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;
|
const defaultTokenMaxSize = 6;
|
||||||
function TokenMenu({
|
function TokenMenu({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -14,7 +21,10 @@ function TokenMenu({
|
|||||||
tokenState,
|
tokenState,
|
||||||
tokenImage,
|
tokenImage,
|
||||||
onTokenStateChange,
|
onTokenStateChange,
|
||||||
|
map,
|
||||||
}) {
|
}) {
|
||||||
|
const { userId } = useContext(AuthContext);
|
||||||
|
|
||||||
const wasOpen = usePrevious(isOpen);
|
const wasOpen = usePrevious(isOpen);
|
||||||
|
|
||||||
const [tokenMaxSize, setTokenMaxSize] = useState(defaultTokenMaxSize);
|
const [tokenMaxSize, setTokenMaxSize] = useState(defaultTokenMaxSize);
|
||||||
@ -26,8 +36,8 @@ function TokenMenu({
|
|||||||
// Update menu position
|
// Update menu position
|
||||||
if (tokenImage) {
|
if (tokenImage) {
|
||||||
const imageRect = tokenImage.getClientRect();
|
const imageRect = tokenImage.getClientRect();
|
||||||
const map = document.querySelector(".map");
|
const mapElement = document.querySelector(".map");
|
||||||
const mapRect = map.getBoundingClientRect();
|
const mapRect = mapElement.getBoundingClientRect();
|
||||||
|
|
||||||
// Center X for the menu which is 156px wide
|
// Center X for the menu which is 156px wide
|
||||||
setMenuLeft(mapRect.left + imageRect.x + imageRect.width / 2 - 156 / 2);
|
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) {
|
function handleModalContent(node) {
|
||||||
if (node) {
|
if (node) {
|
||||||
// Focus input
|
// Focus input
|
||||||
@ -76,8 +98,8 @@ function TokenMenu({
|
|||||||
|
|
||||||
// Ensure menu is in bounds
|
// Ensure menu is in bounds
|
||||||
const nodeRect = node.getBoundingClientRect();
|
const nodeRect = node.getBoundingClientRect();
|
||||||
const map = document.querySelector(".map");
|
const mapElement = document.querySelector(".map");
|
||||||
const mapRect = map.getBoundingClientRect();
|
const mapRect = mapElement.getBoundingClientRect();
|
||||||
setMenuLeft((prevLeft) =>
|
setMenuLeft((prevLeft) =>
|
||||||
Math.min(
|
Math.min(
|
||||||
mapRect.right - nodeRect.width,
|
mapRect.right - nodeRect.width,
|
||||||
@ -203,6 +225,33 @@ function TokenMenu({
|
|||||||
mr={1}
|
mr={1}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</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>
|
</Box>
|
||||||
</MapMenu>
|
</MapMenu>
|
||||||
);
|
);
|
||||||
|
@ -31,8 +31,11 @@ function Tokens({ onMapTokenStateCreate }) {
|
|||||||
statuses: [],
|
statuses: [],
|
||||||
x: token.x,
|
x: token.x,
|
||||||
y: token.y,
|
y: token.y,
|
||||||
lastEditedBy: userId,
|
lastModifiedBy: userId,
|
||||||
|
lastModified: Date.now(),
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
|
locked: false,
|
||||||
|
visible: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,23 @@ function loadVersions(db) {
|
|||||||
map.snapToGrid = true;
|
map.snapToGrid = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// v1.5.1 - Added lock, visibility and modified to tokens
|
||||||
|
db.version(9)
|
||||||
|
.stores({})
|
||||||
|
.upgrade((tx) => {
|
||||||
|
return tx
|
||||||
|
.table("states")
|
||||||
|
.toCollection()
|
||||||
|
.modify((state) => {
|
||||||
|
for (let id in state.tokens) {
|
||||||
|
state.tokens[id].lastModifiedBy = state.tokens[id].lastEditedBy;
|
||||||
|
delete state.tokens[id].lastEditedBy;
|
||||||
|
state.tokens[id].lastModified = Date.now();
|
||||||
|
state.tokens[id].locked = false;
|
||||||
|
state.tokens[id].visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the dexie database used in DatabaseContext
|
// Get the dexie database used in DatabaseContext
|
||||||
|
18
src/icons/TokenHideIcon.js
Normal file
18
src/icons/TokenHideIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function TokenHideIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM2.71 3.16c-.39.39-.39 1.02 0 1.41l1.97 1.97C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l2.72 2.72c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L4.13 3.16c-.39-.39-1.03-.39-1.42 0zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33c-.15-1.4-1.25-2.49-2.64-2.64l2.64 2.64z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenHideIcon;
|
18
src/icons/TokenLockIcon.js
Normal file
18
src/icons/TokenLockIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function TokenLockIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenLockIcon;
|
18
src/icons/TokenShowIcon.js
Normal file
18
src/icons/TokenShowIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function TokenShowIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M12 4C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenShowIcon;
|
18
src/icons/TokenUnlockIcon.js
Normal file
18
src/icons/TokenUnlockIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function TokenUnlockIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M12 13c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6-5h-1V6c0-2.76-2.24-5-5-5-2.28 0-4.27 1.54-4.84 3.75-.14.54.18 1.08.72 1.22.53.14 1.08-.18 1.22-.72C9.44 3.93 10.63 3 12 3c1.65 0 3 1.35 3 3v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 11c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-8c0-.55.45-1 1-1h10c.55 0 1 .45 1 1v8z" />{" "}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenUnlockIcon;
|
Loading…
Reference in New Issue
Block a user