Added custom sizing support
This commit is contained in:
parent
43aaf04843
commit
3bd8fce6ab
@ -17,7 +17,7 @@ function Map({
|
|||||||
tokens,
|
tokens,
|
||||||
onMapTokenMove,
|
onMapTokenMove,
|
||||||
onMapTokenRemove,
|
onMapTokenRemove,
|
||||||
onMapChanged
|
onMapChanged,
|
||||||
}) {
|
}) {
|
||||||
function handleProxyDragEnd(isOnMap, token) {
|
function handleProxyDragEnd(isOnMap, token) {
|
||||||
if (isOnMap && onMapTokenMove) {
|
if (isOnMap && onMapTokenMove) {
|
||||||
@ -36,29 +36,29 @@ function Map({
|
|||||||
interact(".map")
|
interact(".map")
|
||||||
.gesturable({
|
.gesturable({
|
||||||
listeners: {
|
listeners: {
|
||||||
move: event => {
|
move: (event) => {
|
||||||
setMapScale(previousMapScale =>
|
setMapScale((previousMapScale) =>
|
||||||
Math.max(Math.min(previousMapScale + event.ds, maxZoom), minZoom)
|
Math.max(Math.min(previousMapScale + event.ds, maxZoom), minZoom)
|
||||||
);
|
);
|
||||||
setMapTranslate(previousMapTranslate => ({
|
setMapTranslate((previousMapTranslate) => ({
|
||||||
x: previousMapTranslate.x + event.dx,
|
x: previousMapTranslate.x + event.dx,
|
||||||
y: previousMapTranslate.y + event.dy
|
y: previousMapTranslate.y + event.dy,
|
||||||
}));
|
}));
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.draggable({
|
.draggable({
|
||||||
inertia: true,
|
inertia: true,
|
||||||
listeners: {
|
listeners: {
|
||||||
move: event => {
|
move: (event) => {
|
||||||
setMapTranslate(previousMapTranslate => ({
|
setMapTranslate((previousMapTranslate) => ({
|
||||||
x: previousMapTranslate.x + event.dx,
|
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();
|
event.preventDefault();
|
||||||
setMapTranslate({ x: 0, y: 0 });
|
setMapTranslate({ x: 0, y: 0 });
|
||||||
setMapScale(1);
|
setMapScale(1);
|
||||||
@ -67,7 +67,7 @@ function Map({
|
|||||||
|
|
||||||
function handleZoom(event) {
|
function handleZoom(event) {
|
||||||
const deltaY = event.deltaY * zoomSpeed;
|
const deltaY = event.deltaY * zoomSpeed;
|
||||||
setMapScale(previousMapScale =>
|
setMapScale((previousMapScale) =>
|
||||||
Math.max(Math.min(previousMapScale + deltaY, maxZoom), minZoom)
|
Math.max(Math.min(previousMapScale + deltaY, maxZoom), minZoom)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ function Map({
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
touchAction: "none"
|
touchAction: "none",
|
||||||
}}
|
}}
|
||||||
bg="background"
|
bg="background"
|
||||||
onWheel={handleZoom}
|
onWheel={handleZoom}
|
||||||
@ -103,19 +103,19 @@ function Map({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)"
|
transform: "translate(-50%, -50%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`
|
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 0,
|
height: 0,
|
||||||
paddingBottom: `${(1 / aspectRatio) * 100}%`
|
paddingBottom: `${(1 / aspectRatio) * 100}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
@ -124,7 +124,7 @@ function Map({
|
|||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0
|
left: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -133,7 +133,7 @@ function Map({
|
|||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
touchAction: "none"
|
touchAction: "none",
|
||||||
}}
|
}}
|
||||||
src={mapSource}
|
src={mapSource}
|
||||||
/>
|
/>
|
||||||
@ -144,24 +144,27 @@ function Map({
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.values(tokens).map(token => (
|
{Object.values(tokens).map((token) => (
|
||||||
<Box
|
<Box
|
||||||
key={token.id}
|
key={token.id}
|
||||||
style={{
|
style={{
|
||||||
left: `${token.x * 100}%`,
|
left: `${token.x * 100}%`,
|
||||||
top: `${token.y * 100}%`,
|
top: `${token.y * 100}%`,
|
||||||
width: `${tokenSizePercent}%`
|
width: `${tokenSizePercent * (token.size || 1)}%`,
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
display: "flex"
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Token
|
<Token
|
||||||
tokenId={token.id}
|
data={{
|
||||||
|
id: token.id,
|
||||||
|
size: token.size,
|
||||||
|
}}
|
||||||
image={token.image}
|
image={token.image}
|
||||||
className={mapTokenClassName}
|
className={mapTokenClassName}
|
||||||
sx={{ position: "absolute" }}
|
sx={{ position: "absolute" }}
|
||||||
@ -177,7 +180,7 @@ function Map({
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "0",
|
top: "0",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translateX(-50%)"
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AddMapButton onMapChanged={onMapChanged} />
|
<AddMapButton onMapChanged={onMapChanged} />
|
||||||
|
@ -16,7 +16,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
interact(`.${tokenClassName}`).draggable({
|
interact(`.${tokenClassName}`).draggable({
|
||||||
listeners: {
|
listeners: {
|
||||||
start: event => {
|
start: (event) => {
|
||||||
let target = event.target;
|
let target = event.target;
|
||||||
// Hide the token and copy it's image to the proxy
|
// Hide the token and copy it's image to the proxy
|
||||||
target.style.opacity = "0.25";
|
target.style.opacity = "0.25";
|
||||||
@ -39,7 +39,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
move: event => {
|
move: (event) => {
|
||||||
let proxy = imageRef.current;
|
let proxy = imageRef.current;
|
||||||
// Move the proxy based off of the movment of the token
|
// Move the proxy based off of the movment of the token
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
@ -66,7 +66,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
end: event => {
|
end: (event) => {
|
||||||
let target = event.target;
|
let target = event.target;
|
||||||
let proxy = imageRef.current;
|
let proxy = imageRef.current;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
@ -85,12 +85,12 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
x = x / (mapRect.right - mapRect.left);
|
x = x / (mapRect.right - mapRect.left);
|
||||||
y = y / (mapRect.bottom - mapRect.top);
|
y = y / (mapRect.bottom - mapRect.top);
|
||||||
|
|
||||||
const id = target.getAttribute("data-token-id");
|
|
||||||
onProxyDragEnd(proxyOnMap.current, {
|
onProxyDragEnd(proxyOnMap.current, {
|
||||||
image: imageSource,
|
image: imageSource,
|
||||||
x,
|
x,
|
||||||
y,
|
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
|
// Show the token
|
||||||
target.style.opacity = "1";
|
target.style.opacity = "1";
|
||||||
setImageSource("");
|
setImageSource("");
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}, [imageSource, onProxyDragEnd, tokenClassName, proxyContainer]);
|
}, [imageSource, onProxyDragEnd, tokenClassName, proxyContainer]);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
right: 0
|
right: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -129,7 +129,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
|||||||
sx={{
|
sx={{
|
||||||
touchAction: "none",
|
touchAction: "none",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
position: "absolute"
|
position: "absolute",
|
||||||
}}
|
}}
|
||||||
ref={imageRef}
|
ref={imageRef}
|
||||||
/>
|
/>
|
||||||
|
52
src/components/SizeInput.js
Normal file
52
src/components/SizeInput.js
Normal 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;
|
@ -1,21 +1,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Image } from "theme-ui";
|
import { Image } from "theme-ui";
|
||||||
|
|
||||||
function Token({ image, className, tokenId, sx }) {
|
// The data prop is used to pass data into the dom element
|
||||||
// Store the token id in the html element for the drag code to pick it up
|
// this can be used to pass state to the ProxyToken
|
||||||
const idProp = tokenId ? { "data-token-id": tokenId } : {};
|
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 (
|
return (
|
||||||
<Image
|
<Image
|
||||||
className={className}
|
className={className}
|
||||||
sx={{ userSelect: "none", touchAction: "none", ...sx }}
|
sx={{ userSelect: "none", touchAction: "none", ...sx }}
|
||||||
src={image}
|
src={image}
|
||||||
{...idProp}
|
{...dataProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.defaultProps = {
|
Token.defaultProps = {
|
||||||
sx: {}
|
data: {},
|
||||||
|
sx: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Token;
|
export default Token;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Flex, Box } from "theme-ui";
|
import { Flex, Box } from "theme-ui";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
@ -6,26 +6,30 @@ import * as tokens from "../tokens";
|
|||||||
|
|
||||||
import Token from "./Token";
|
import Token from "./Token";
|
||||||
import ProxyToken from "./ProxyToken";
|
import ProxyToken from "./ProxyToken";
|
||||||
|
import SizeInput from "./SizeInput";
|
||||||
|
|
||||||
const listTokenClassName = "list-token";
|
const listTokenClassName = "list-token";
|
||||||
|
|
||||||
function Tokens({ onCreateMapToken }) {
|
function Tokens({ onCreateMapToken }) {
|
||||||
|
const [tokenSize, setTokenSize] = useState(1);
|
||||||
|
|
||||||
function handleProxyDragEnd(isOnMap, token) {
|
function handleProxyDragEnd(isOnMap, token) {
|
||||||
if (isOnMap && onCreateMapToken) {
|
if (isOnMap && onCreateMapToken) {
|
||||||
// Give the token an id
|
// Give the token an id
|
||||||
onCreateMapToken({ ...token, id: shortid.generate() });
|
onCreateMapToken({ ...token, id: shortid.generate(), size: tokenSize });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Flex sx={{ flexDirection: "column" }}>
|
||||||
<Flex
|
<Flex
|
||||||
bg="background"
|
bg="background"
|
||||||
sx={{
|
sx={{
|
||||||
width: "80px",
|
width: "80px",
|
||||||
minWidth: "80px",
|
minWidth: "80px",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
overflowY: "auto"
|
overflowY: "auto",
|
||||||
}}
|
}}
|
||||||
px={2}
|
px={2}
|
||||||
>
|
>
|
||||||
@ -35,6 +39,15 @@ function Tokens({ onCreateMapToken }) {
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Box
|
||||||
|
pt={1}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "muted",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SizeInput value={tokenSize} onChange={setTokenSize} />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
<ProxyToken
|
<ProxyToken
|
||||||
tokenClassName={listTokenClassName}
|
tokenClassName={listTokenClassName}
|
||||||
onProxyDragEnd={handleProxyDragEnd}
|
onProxyDragEnd={handleProxyDragEnd}
|
||||||
|
Loading…
Reference in New Issue
Block a user