Added custom sizing support

This commit is contained in:
Mitchell McCaffrey 2020-04-07 11:47:06 +10:00
parent 43aaf04843
commit 3bd8fce6ab
5 changed files with 130 additions and 57 deletions

View File

@ -17,7 +17,7 @@ function Map({
tokens,
onMapTokenMove,
onMapTokenRemove,
onMapChanged
onMapChanged,
}) {
function handleProxyDragEnd(isOnMap, token) {
if (isOnMap && onMapTokenMove) {
@ -36,29 +36,29 @@ function Map({
interact(".map")
.gesturable({
listeners: {
move: event => {
setMapScale(previousMapScale =>
move: (event) => {
setMapScale((previousMapScale) =>
Math.max(Math.min(previousMapScale + event.ds, maxZoom), minZoom)
);
setMapTranslate(previousMapTranslate => ({
setMapTranslate((previousMapTranslate) => ({
x: previousMapTranslate.x + event.dx,
y: previousMapTranslate.y + event.dy
y: previousMapTranslate.y + event.dy,
}));
}
}
},
},
})
.draggable({
inertia: true,
listeners: {
move: event => {
setMapTranslate(previousMapTranslate => ({
move: (event) => {
setMapTranslate((previousMapTranslate) => ({
x: previousMapTranslate.x + event.dx,
y: previousMapTranslate.y + event.dy
y: previousMapTranslate.y + event.dy,
}));
}
}
},
},
});
interact(".map").on("doubletap", event => {
interact(".map").on("doubletap", (event) => {
event.preventDefault();
setMapTranslate({ x: 0, y: 0 });
setMapScale(1);
@ -67,7 +67,7 @@ function Map({
function handleZoom(event) {
const deltaY = event.deltaY * zoomSpeed;
setMapScale(previousMapScale =>
setMapScale((previousMapScale) =>
Math.max(Math.min(previousMapScale + deltaY, maxZoom), minZoom)
);
}
@ -93,7 +93,7 @@ function Map({
overflow: "hidden",
backgroundColor: "rgba(0, 0, 0, 0.1)",
userSelect: "none",
touchAction: "none"
touchAction: "none",
}}
bg="background"
onWheel={handleZoom}
@ -103,19 +103,19 @@ function Map({
position: "relative",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
transform: "translate(-50%, -50%)",
}}
>
<Box
style={{
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`,
}}
>
<Box
sx={{
width: "100%",
height: 0,
paddingBottom: `${(1 / aspectRatio) * 100}%`
paddingBottom: `${(1 / aspectRatio) * 100}%`,
}}
/>
<Box
@ -124,7 +124,7 @@ function Map({
top: 0,
right: 0,
bottom: 0,
left: 0
left: 0,
}}
>
<Image
@ -133,7 +133,7 @@ function Map({
sx={{
width: "100%",
userSelect: "none",
touchAction: "none"
touchAction: "none",
}}
src={mapSource}
/>
@ -144,24 +144,27 @@ function Map({
top: 0,
left: 0,
right: 0,
bottom: 0
bottom: 0,
}}
>
{Object.values(tokens).map(token => (
{Object.values(tokens).map((token) => (
<Box
key={token.id}
style={{
left: `${token.x * 100}%`,
top: `${token.y * 100}%`,
width: `${tokenSizePercent}%`
width: `${tokenSizePercent * (token.size || 1)}%`,
}}
sx={{
position: "absolute",
display: "flex"
display: "flex",
}}
>
<Token
tokenId={token.id}
data={{
id: token.id,
size: token.size,
}}
image={token.image}
className={mapTokenClassName}
sx={{ position: "absolute" }}
@ -177,7 +180,7 @@ function Map({
position: "absolute",
top: "0",
left: "50%",
transform: "translateX(-50%)"
transform: "translateX(-50%)",
}}
>
<AddMapButton onMapChanged={onMapChanged} />

View File

@ -16,7 +16,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
useEffect(() => {
interact(`.${tokenClassName}`).draggable({
listeners: {
start: event => {
start: (event) => {
let target = event.target;
// Hide the token and copy it's image to the proxy
target.style.opacity = "0.25";
@ -39,7 +39,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
}
},
move: event => {
move: (event) => {
let proxy = imageRef.current;
// Move the proxy based off of the movment of the token
if (proxy) {
@ -66,7 +66,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
}
},
end: event => {
end: (event) => {
let target = event.target;
let proxy = imageRef.current;
if (proxy) {
@ -85,12 +85,12 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
x = x / (mapRect.right - mapRect.left);
y = y / (mapRect.bottom - mapRect.top);
const id = target.getAttribute("data-token-id");
onProxyDragEnd(proxyOnMap.current, {
image: imageSource,
x,
y,
id
// Pass in props stored as data- in the dom node
...target.dataset,
});
}
@ -103,8 +103,8 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
// Show the token
target.style.opacity = "1";
setImageSource("");
}
}
},
},
});
}, [imageSource, onProxyDragEnd, tokenClassName, proxyContainer]);
@ -121,7 +121,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
top: 0,
left: 0,
bottom: 0,
right: 0
right: 0,
}}
>
<Image
@ -129,7 +129,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
sx={{
touchAction: "none",
userSelect: "none",
position: "absolute"
position: "absolute",
}}
ref={imageRef}
/>

View File

@ -0,0 +1,52 @@
import React from "react";
import { Box, Flex, IconButton, Text, Label } from "theme-ui";
function SizeInput({ value, onChange }) {
return (
<Box>
<Text sx={{ textAlign: "center" }} variant="heading">
Size
</Text>
<Flex sx={{ alignItems: "center", justifyContent: "center" }}>
<IconButton
aria-label="Lower token size"
onClick={() => value > 1 && onChange(value - 1)}
>
<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 13H6c-.55 0-1-.45-1-1s.45-1 1-1h12c.55 0 1 .45 1 1s-.45 1-1 1z" />
</svg>
</IconButton>
<Text>{value}</Text>
<IconButton
aria-label="Increase token size"
onClick={() => onChange(value + 1)}
>
<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 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z" />
</svg>
</IconButton>
</Flex>
</Box>
);
}
SizeInput.defaultProps = {
value: 1,
onChange: () => {},
};
export default SizeInput;

View File

@ -1,21 +1,26 @@
import React from "react";
import { Image } from "theme-ui";
function Token({ image, className, tokenId, sx }) {
// Store the token id in the html element for the drag code to pick it up
const idProp = tokenId ? { "data-token-id": tokenId } : {};
// The data prop is used to pass data into the dom element
// this can be used to pass state to the ProxyToken
function Token({ image, className, data, sx }) {
// Map the keys in data to have the `data-` prefix
const dataProps = Object.fromEntries(
Object.entries(data).map(([key, value]) => [`data-${key}`, value])
);
return (
<Image
className={className}
sx={{ userSelect: "none", touchAction: "none", ...sx }}
src={image}
{...idProp}
{...dataProps}
/>
);
}
Token.defaultProps = {
sx: {}
data: {},
sx: {},
};
export default Token;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { Flex, Box } from "theme-ui";
import shortid from "shortid";
@ -6,26 +6,30 @@ import * as tokens from "../tokens";
import Token from "./Token";
import ProxyToken from "./ProxyToken";
import SizeInput from "./SizeInput";
const listTokenClassName = "list-token";
function Tokens({ onCreateMapToken }) {
const [tokenSize, setTokenSize] = useState(1);
function handleProxyDragEnd(isOnMap, token) {
if (isOnMap && onCreateMapToken) {
// Give the token an id
onCreateMapToken({ ...token, id: shortid.generate() });
onCreateMapToken({ ...token, id: shortid.generate(), size: tokenSize });
}
}
return (
<>
<Flex sx={{ flexDirection: "column" }}>
<Flex
bg="background"
sx={{
width: "80px",
minWidth: "80px",
flexDirection: "column",
overflowY: "auto"
overflowY: "auto",
}}
px={2}
>
@ -35,6 +39,15 @@ function Tokens({ onCreateMapToken }) {
</Box>
))}
</Flex>
<Box
pt={1}
sx={{
backgroundColor: "muted",
}}
>
<SizeInput value={tokenSize} onChange={setTokenSize} />
</Box>
</Flex>
<ProxyToken
tokenClassName={listTokenClassName}
onProxyDragEnd={handleProxyDragEnd}