Typescript
This commit is contained in:
parent
fecf8090ea
commit
97734a2f55
@ -5,7 +5,7 @@ import { Data } from "@dnd-kit/core/dist/store/types";
|
|||||||
type DraggableProps = {
|
type DraggableProps = {
|
||||||
id: string;
|
id: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
data: Data;
|
data?: Data;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Draggable({ id, children, data }: DraggableProps) {
|
function Draggable({ id, children, data }: DraggableProps) {
|
||||||
|
@ -259,11 +259,9 @@ function Map({
|
|||||||
onMapTokenStateRemove(state);
|
onMapTokenStateRemove(state);
|
||||||
setTokenDraggingOptions(undefined);
|
setTokenDraggingOptions(undefined);
|
||||||
}}
|
}}
|
||||||
onTokenStateChange={onMapTokenStateChange}
|
|
||||||
tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
|
tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
|
||||||
tokenGroup={tokenDraggingOptions && tokenDraggingOptions.tokenGroup}
|
tokenNode={tokenDraggingOptions && tokenDraggingOptions.tokenNode}
|
||||||
dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
|
dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
|
||||||
token={tokensById[tokenDraggingOptions.tokenState.tokenId]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ function MapTokens({
|
|||||||
setTokenDraggingOptions({
|
setTokenDraggingOptions({
|
||||||
dragging: true,
|
dragging: true,
|
||||||
tokenState,
|
tokenState,
|
||||||
tokenGroup: e.target,
|
tokenNode: e.target,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onTokenDragEnd={() =>
|
onTokenDragEnd={() =>
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { IconButton } from "theme-ui";
|
import { IconButton } from "theme-ui";
|
||||||
|
|
||||||
import SelectTokensIcon from "../../icons/SelectTokensIcon";
|
import SelectTokensIcon from "../../icons/SelectTokensIcon";
|
||||||
|
|
||||||
import SelectTokensModal from "../../modals/SelectTokensModal";
|
import SelectTokensModal from "../../modals/SelectTokensModal";
|
||||||
|
import { MapTokensStateCreateHandler } from "../../types/Events";
|
||||||
|
|
||||||
function SelectTokensButton({ onMapTokensStateCreate }) {
|
type SelectTokensButtonProps = {
|
||||||
|
onMapTokensStateCreate: MapTokensStateCreateHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
function SelectTokensButton({
|
||||||
|
onMapTokensStateCreate,
|
||||||
|
}: SelectTokensButtonProps) {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
function openModal() {
|
function openModal() {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
@ -14,9 +21,6 @@ function SelectTokensButton({ onMapTokensStateCreate }) {
|
|||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDone() {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -29,7 +33,6 @@ function SelectTokensButton({ onMapTokensStateCreate }) {
|
|||||||
<SelectTokensModal
|
<SelectTokensModal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
onDone={handleDone}
|
|
||||||
onMapTokensStateCreate={onMapTokensStateCreate}
|
onMapTokensStateCreate={onMapTokensStateCreate}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Box, Flex, Grid } from "theme-ui";
|
import { Box, Flex, Grid } from "theme-ui";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
KeyboardSensor,
|
KeyboardSensor,
|
||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
|
DragStartEvent,
|
||||||
} from "@dnd-kit/core";
|
} from "@dnd-kit/core";
|
||||||
|
|
||||||
import TokenBarToken from "./TokenBarToken";
|
import TokenBarToken from "./TokenBarToken";
|
||||||
@ -23,7 +24,7 @@ import usePreventSelect from "../../hooks/usePreventSelect";
|
|||||||
import { useTokenData } from "../../contexts/TokenDataContext";
|
import { useTokenData } from "../../contexts/TokenDataContext";
|
||||||
import { useUserId } from "../../contexts/UserIdContext";
|
import { useUserId } from "../../contexts/UserIdContext";
|
||||||
import { useMapStage } from "../../contexts/MapStageContext";
|
import { useMapStage } from "../../contexts/MapStageContext";
|
||||||
import DragContext from "../../contexts/DragContext";
|
import DragContext, { CustomDragEndEvent } from "../../contexts/DragContext";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createTokenState,
|
createTokenState,
|
||||||
@ -31,13 +32,19 @@ import {
|
|||||||
} from "../../helpers/token";
|
} from "../../helpers/token";
|
||||||
import { findGroup } from "../../helpers/group";
|
import { findGroup } from "../../helpers/group";
|
||||||
import Vector2 from "../../helpers/Vector2";
|
import Vector2 from "../../helpers/Vector2";
|
||||||
|
import { MapTokensStateCreateHandler } from "../../types/Events";
|
||||||
|
import { Group } from "../../types/Group";
|
||||||
|
|
||||||
function TokenBar({ onMapTokensStateCreate }) {
|
type TokenBarProps = {
|
||||||
|
onMapTokensStateCreate: MapTokensStateCreateHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenBar({ onMapTokensStateCreate }: TokenBarProps) {
|
||||||
const userId = useUserId();
|
const userId = useUserId();
|
||||||
const { tokensById, tokenGroups } = useTokenData();
|
const { tokensById, tokenGroups } = useTokenData();
|
||||||
const [fullScreen] = useSetting("map.fullScreen");
|
const [fullScreen] = useSetting<boolean>("map.fullScreen");
|
||||||
|
|
||||||
const [dragId, setDragId] = useState();
|
const [dragId, setDragId] = useState<string | null>(null);
|
||||||
|
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
|
|
||||||
@ -52,14 +59,20 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
|
|
||||||
const [preventSelect, resumeSelect] = usePreventSelect();
|
const [preventSelect, resumeSelect] = usePreventSelect();
|
||||||
|
|
||||||
function handleDragStart({ active }) {
|
function handleDragStart({ active }: DragStartEvent) {
|
||||||
setDragId(active.id);
|
setDragId(active.id);
|
||||||
preventSelect();
|
preventSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd({ active, overlayNodeClientRect }) {
|
function handleDragEnd({
|
||||||
|
active,
|
||||||
|
overlayNodeClientRect,
|
||||||
|
}: CustomDragEndEvent) {
|
||||||
setDragId(null);
|
setDragId(null);
|
||||||
|
resumeSelect();
|
||||||
|
if (!userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mapStage = mapStageRef.current;
|
const mapStage = mapStageRef.current;
|
||||||
if (mapStage && overlayNodeClientRect) {
|
if (mapStage && overlayNodeClientRect) {
|
||||||
const dragRect = overlayNodeClientRect;
|
const dragRect = overlayNodeClientRect;
|
||||||
@ -96,8 +109,6 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeSelect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragCancel() {
|
function handleDragCancel() {
|
||||||
@ -105,7 +116,7 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
resumeSelect();
|
resumeSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderToken(group, draggable = true) {
|
function renderToken(group: Group, draggable = true) {
|
||||||
if (group.type === "item") {
|
if (group.type === "item") {
|
||||||
const token = tokensById[group.id];
|
const token = tokensById[group.id];
|
||||||
if (token && !token.hideInSidebar) {
|
if (token && !token.hideInSidebar) {
|
||||||
@ -140,6 +151,8 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dragGroup = dragId && findGroup(tokenGroups, dragId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragContext
|
<DragContext
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
@ -188,7 +201,7 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<DragOverlay dropAnimation={null}>
|
<DragOverlay dropAnimation={null}>
|
||||||
{dragId && renderToken(findGroup(tokenGroups, dragId), false)}
|
{dragGroup && renderToken(dragGroup, false)}
|
||||||
</DragOverlay>,
|
</DragOverlay>,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
@ -1,10 +1,15 @@
|
|||||||
import React from "react";
|
|
||||||
import { Box } from "theme-ui";
|
import { Box } from "theme-ui";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
|
|
||||||
import TokenImage from "./TokenImage";
|
import TokenImage from "./TokenImage";
|
||||||
|
|
||||||
function TokenBarToken({ token }) {
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
|
type TokenBarTokenProps = {
|
||||||
|
token: Token;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenBarToken({ token }: TokenBarTokenProps) {
|
||||||
const [ref, inView] = useInView({ triggerOnce: true });
|
const [ref, inView] = useInView({ triggerOnce: true });
|
||||||
|
|
||||||
return (
|
return (
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { Grid, Flex, Box } 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 { useDraggable } from "@dnd-kit/core";
|
||||||
@ -11,10 +11,22 @@ import Draggable from "../drag/Draggable";
|
|||||||
import Vector2 from "../../helpers/Vector2";
|
import Vector2 from "../../helpers/Vector2";
|
||||||
|
|
||||||
import GroupIcon from "../../icons/GroupIcon";
|
import GroupIcon from "../../icons/GroupIcon";
|
||||||
|
import { GroupContainer } from "../../types/Group";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
function TokenBarTokenGroup({ group, tokens, draggable }) {
|
type TokenBarTokenGroupProps = {
|
||||||
|
group: GroupContainer;
|
||||||
|
tokens: Token[];
|
||||||
|
draggable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenBarTokenGroup({
|
||||||
|
group,
|
||||||
|
tokens,
|
||||||
|
draggable,
|
||||||
|
}: TokenBarTokenGroupProps) {
|
||||||
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
||||||
id: draggable && group.id,
|
id: group.id,
|
||||||
disabled: !draggable,
|
disabled: !draggable,
|
||||||
});
|
});
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -23,7 +35,7 @@ function TokenBarTokenGroup({ group, tokens, draggable }) {
|
|||||||
height: isOpen ? (tokens.length + 1) * 56 : 56,
|
height: isOpen ? (tokens.length + 1) * 56 : 56,
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderToken(token) {
|
function renderToken(token: Token) {
|
||||||
if (draggable) {
|
if (draggable) {
|
||||||
return (
|
return (
|
||||||
<Draggable id={token.id} key={token.id}>
|
<Draggable id={token.id} key={token.id}>
|
||||||
@ -77,7 +89,6 @@ function TokenBarTokenGroup({ group, tokens, draggable }) {
|
|||||||
gridTemplateRows: "1fr 1fr",
|
gridTemplateRows: "1fr 1fr",
|
||||||
}}
|
}}
|
||||||
p="2px"
|
p="2px"
|
||||||
alt={group.name}
|
|
||||||
title={group.name}
|
title={group.name}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
@ -100,10 +111,13 @@ function TokenBarTokenGroup({ group, tokens, draggable }) {
|
|||||||
|
|
||||||
// Reject the opening of a group if the pointer has moved
|
// Reject the opening of a group if the pointer has moved
|
||||||
const clickDownPositionRef = useRef(new Vector2(0, 0));
|
const clickDownPositionRef = useRef(new Vector2(0, 0));
|
||||||
function handleOpenDown(event) {
|
function handleOpenDown(event: React.PointerEvent<HTMLDivElement>) {
|
||||||
clickDownPositionRef.current = new Vector2(event.clientX, event.clientY);
|
clickDownPositionRef.current = new Vector2(event.clientX, event.clientY);
|
||||||
}
|
}
|
||||||
function handleOpenClick(event, newOpen) {
|
function handleOpenClick(
|
||||||
|
event: React.MouseEvent<HTMLDivElement>,
|
||||||
|
newOpen: boolean
|
||||||
|
) {
|
||||||
const clickPosition = new Vector2(event.clientX, event.clientY);
|
const clickPosition = new Vector2(event.clientX, event.clientY);
|
||||||
const distance = Vector2.distance(
|
const distance = Vector2.distance(
|
||||||
clickPosition,
|
clickPosition,
|
@ -1,56 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { useUserId } from "../../contexts/UserIdContext";
|
|
||||||
import {
|
|
||||||
useMapWidth,
|
|
||||||
useMapHeight,
|
|
||||||
} from "../../contexts/MapInteractionContext";
|
|
||||||
|
|
||||||
import DragOverlay from "../map/DragOverlay";
|
|
||||||
|
|
||||||
function TokenDragOverlay({
|
|
||||||
onTokenStateRemove,
|
|
||||||
onTokenStateChange,
|
|
||||||
token,
|
|
||||||
tokenState,
|
|
||||||
tokenGroup,
|
|
||||||
dragging,
|
|
||||||
}) {
|
|
||||||
const userId = useUserId();
|
|
||||||
|
|
||||||
const mapWidth = useMapWidth();
|
|
||||||
const mapHeight = useMapHeight();
|
|
||||||
|
|
||||||
function handleTokenRemove() {
|
|
||||||
// Handle other tokens when a vehicle gets deleted
|
|
||||||
if (token && token.category === "vehicle") {
|
|
||||||
const layer = tokenGroup.getLayer();
|
|
||||||
const mountedTokens = tokenGroup.find(".token");
|
|
||||||
for (let mountedToken of mountedTokens) {
|
|
||||||
// Save and restore token position after moving layer
|
|
||||||
const position = mountedToken.absolutePosition();
|
|
||||||
mountedToken.moveTo(layer);
|
|
||||||
mountedToken.absolutePosition(position);
|
|
||||||
onTokenStateChange({
|
|
||||||
[mountedToken.id()]: {
|
|
||||||
x: mountedToken.x() / mapWidth,
|
|
||||||
y: mountedToken.y() / mapHeight,
|
|
||||||
lastModifiedBy: userId,
|
|
||||||
lastModified: Date.now(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onTokenStateRemove(tokenState);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DragOverlay
|
|
||||||
dragging={dragging}
|
|
||||||
onRemove={handleTokenRemove}
|
|
||||||
node={tokenGroup}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TokenDragOverlay;
|
|
33
src/components/token/TokenDragOverlay.tsx
Normal file
33
src/components/token/TokenDragOverlay.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Konva from "konva";
|
||||||
|
|
||||||
|
import DragOverlay from "../map/DragOverlay";
|
||||||
|
import { MapTokenStateRemoveHandler } from "../../types/Events";
|
||||||
|
import { TokenState } from "../../types/TokenState";
|
||||||
|
|
||||||
|
type TokenDragOverlayProps = {
|
||||||
|
onTokenStateRemove: MapTokenStateRemoveHandler;
|
||||||
|
tokenState: TokenState;
|
||||||
|
tokenNode: Konva.Node;
|
||||||
|
dragging: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenDragOverlay({
|
||||||
|
onTokenStateRemove,
|
||||||
|
tokenState,
|
||||||
|
tokenNode,
|
||||||
|
dragging,
|
||||||
|
}: TokenDragOverlayProps) {
|
||||||
|
function handleTokenRemove() {
|
||||||
|
onTokenStateRemove(tokenState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragOverlay
|
||||||
|
dragging={dragging}
|
||||||
|
onRemove={handleTokenRemove}
|
||||||
|
node={tokenNode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenDragOverlay;
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Flex, Close, IconButton } from "theme-ui";
|
import { Flex, Close, IconButton } from "theme-ui";
|
||||||
|
|
||||||
import { groupsFromIds, itemsFromGroups } from "../../helpers/group";
|
import { groupsFromIds, itemsFromGroups } from "../../helpers/group";
|
||||||
@ -15,10 +15,15 @@ import { useKeyboard } from "../../contexts/KeyboardContext";
|
|||||||
|
|
||||||
import shortcuts from "../../shortcuts";
|
import shortcuts from "../../shortcuts";
|
||||||
|
|
||||||
function TokenEditBar({ disabled, onLoad }) {
|
type TokenEditBarProps = {
|
||||||
|
disabled: boolean;
|
||||||
|
onLoad: (load: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenEditBar({ disabled, onLoad }: TokenEditBarProps) {
|
||||||
const { tokens, removeTokens, updateTokensHidden } = useTokenData();
|
const { tokens, removeTokens, updateTokensHidden } = useTokenData();
|
||||||
|
|
||||||
const { activeGroups, selectedGroupIds, onGroupSelect } = useGroup();
|
const { activeGroups, selectedGroupIds, onClearSelection } = useGroup();
|
||||||
|
|
||||||
const [allTokensVisible, setAllTokensVisisble] = useState(false);
|
const [allTokensVisible, setAllTokensVisisble] = useState(false);
|
||||||
|
|
||||||
@ -40,12 +45,12 @@ function TokenEditBar({ disabled, onLoad }) {
|
|||||||
setIsTokensRemoveModalOpen(false);
|
setIsTokensRemoveModalOpen(false);
|
||||||
const selectedTokens = getSelectedTokens();
|
const selectedTokens = getSelectedTokens();
|
||||||
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||||
onGroupSelect();
|
onClearSelection();
|
||||||
await removeTokens(selectedTokenIds);
|
await removeTokens(selectedTokenIds);
|
||||||
onLoad(false);
|
onLoad(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTokensHide(hideInSidebar) {
|
async function handleTokensHide(hideInSidebar: boolean) {
|
||||||
const selectedTokens = getSelectedTokens();
|
const selectedTokens = getSelectedTokens();
|
||||||
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||||
// Show loading indicator if hiding more than 10 tokens
|
// Show loading indicator if hiding more than 10 tokens
|
||||||
@ -61,7 +66,7 @@ function TokenEditBar({ disabled, onLoad }) {
|
|||||||
/**
|
/**
|
||||||
* Shortcuts
|
* Shortcuts
|
||||||
*/
|
*/
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -101,7 +106,7 @@ function TokenEditBar({ disabled, onLoad }) {
|
|||||||
<Close
|
<Close
|
||||||
title="Clear Selection"
|
title="Clear Selection"
|
||||||
aria-label="Clear Selection"
|
aria-label="Clear Selection"
|
||||||
onClick={() => onGroupSelect()}
|
onClick={() => onClearSelection()}
|
||||||
/>
|
/>
|
||||||
<Flex>
|
<Flex>
|
||||||
<IconButton
|
<IconButton
|
@ -1,10 +1,13 @@
|
|||||||
import React from "react";
|
|
||||||
import { Flex } from "theme-ui";
|
import { Flex } from "theme-ui";
|
||||||
|
|
||||||
import TokenShowIcon from "../../icons/TokenShowIcon";
|
import TokenShowIcon from "../../icons/TokenShowIcon";
|
||||||
import TokenHideIcon from "../../icons/TokenHideIcon";
|
import TokenHideIcon from "../../icons/TokenHideIcon";
|
||||||
|
|
||||||
function TokenHiddenBadge({ hidden }) {
|
type TokenHiddenBadgeProps = {
|
||||||
|
hidden: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenHiddenBadge({ hidden }: TokenHiddenBadgeProps) {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
@ -1,13 +1,18 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Image, Box } from "theme-ui";
|
import { Image, Box, ImageProps } from "theme-ui";
|
||||||
|
|
||||||
import { useDataURL } from "../../contexts/AssetsContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import { tokenSources as defaultTokenSources } from "../../tokens";
|
import { tokenSources as defaultTokenSources } from "../../tokens";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
import { TokenOutlineSVG } from "./TokenOutline";
|
import { TokenOutlineSVG } from "./TokenOutline";
|
||||||
|
|
||||||
const TokenImage = React.forwardRef(({ token, ...props }, ref) => {
|
type TokenImageProps = {
|
||||||
|
token: Token;
|
||||||
|
} & ImageProps;
|
||||||
|
|
||||||
|
function TokenImage({ token, ...props }: TokenImageProps) {
|
||||||
const tokenURL = useDataURL(
|
const tokenURL = useDataURL(
|
||||||
token,
|
token,
|
||||||
defaultTokenSources,
|
defaultTokenSources,
|
||||||
@ -35,12 +40,11 @@ const TokenImage = React.forwardRef(({ token, ...props }, ref) => {
|
|||||||
<Image
|
<Image
|
||||||
onLoad={() => setShowOutline(false)}
|
onLoad={() => setShowOutline(false)}
|
||||||
src={tokenURL}
|
src={tokenURL}
|
||||||
ref={ref}
|
|
||||||
style={showOutline ? { display: "none" } : props.style}
|
style={showOutline ? { display: "none" } : props.style}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
export default TokenImage;
|
export default TokenImage;
|
@ -1,13 +1,21 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import Konva from "konva";
|
||||||
|
import { useRef, useEffect, useState } from "react";
|
||||||
import { Rect, Text, Group } from "react-konva";
|
import { Rect, Text, Group } from "react-konva";
|
||||||
|
|
||||||
import useSetting from "../../hooks/useSetting";
|
import useSetting from "../../hooks/useSetting";
|
||||||
|
import { TokenState } from "../../types/TokenState";
|
||||||
|
|
||||||
const maxTokenSize = 3;
|
const maxTokenSize = 3;
|
||||||
const defaultFontSize = 16;
|
const defaultFontSize = 16;
|
||||||
|
|
||||||
function TokenLabel({ tokenState, width, height }) {
|
type TokenLabelProps = {
|
||||||
const [labelSize] = useSetting("map.labelSize");
|
tokenState: TokenState;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenLabel({ tokenState, width, height }: TokenLabelProps) {
|
||||||
|
const [labelSize] = useSetting<number>("map.labelSize");
|
||||||
|
|
||||||
const paddingY =
|
const paddingY =
|
||||||
(height / 12 / tokenState.size) * Math.min(tokenState.size, maxTokenSize);
|
(height / 12 / tokenState.size) * Math.min(tokenState.size, maxTokenSize);
|
||||||
@ -22,7 +30,7 @@ function TokenLabel({ tokenState, width, height }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fontSizes = [];
|
let fontSizes: number[] = [];
|
||||||
for (let size = 20 * labelSize; size >= 6; size--) {
|
for (let size = 20 * labelSize; size >= 6; size--) {
|
||||||
const verticalSize = height / size / tokenState.size;
|
const verticalSize = height / size / tokenState.size;
|
||||||
const tokenSize = Math.min(tokenState.size, maxTokenSize);
|
const tokenSize = Math.min(tokenState.size, maxTokenSize);
|
||||||
@ -30,7 +38,7 @@ function TokenLabel({ tokenState, width, height }) {
|
|||||||
fontSizes.push(fontSize);
|
fontSizes.push(fontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFontScale() {
|
const findFontScale = () => {
|
||||||
const size = fontSizes.reduce((prev, curr) => {
|
const size = fontSizes.reduce((prev, curr) => {
|
||||||
text.fontSize(curr);
|
text.fontSize(curr);
|
||||||
const textWidth = text.getTextWidth() + paddingX * 2;
|
const textWidth = text.getTextWidth() + paddingX * 2;
|
||||||
@ -42,7 +50,7 @@ function TokenLabel({ tokenState, width, height }) {
|
|||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
setFontScale(size / defaultFontSize);
|
setFontScale(size / defaultFontSize);
|
||||||
}
|
};
|
||||||
|
|
||||||
findFontScale();
|
findFontScale();
|
||||||
}, [
|
}, [
|
||||||
@ -68,8 +76,8 @@ function TokenLabel({ tokenState, width, height }) {
|
|||||||
}
|
}
|
||||||
}, [tokenState.label, paddingX, width, fontScale]);
|
}, [tokenState.label, paddingX, width, fontScale]);
|
||||||
|
|
||||||
const textRef = useRef();
|
const textRef = useRef<Konva.Text>(null);
|
||||||
const textSizerRef = useRef();
|
const textSizerRef = useRef<Konva.Text>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group y={height - (defaultFontSize * fontScale + paddingY) / 2}>
|
<Group y={height - (defaultFontSize * fontScale + paddingY) / 2}>
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Box, Input, Flex, Text, IconButton } from "theme-ui";
|
import { Box, Input, Flex, Text, IconButton } from "theme-ui";
|
||||||
|
import Konva from "konva";
|
||||||
|
|
||||||
import Slider from "../Slider";
|
import Slider from "../Slider";
|
||||||
|
|
||||||
@ -16,6 +17,22 @@ import HideIcon from "../../icons/TokenHideIcon";
|
|||||||
|
|
||||||
import { useUserId } from "../../contexts/UserIdContext";
|
import { useUserId } from "../../contexts/UserIdContext";
|
||||||
|
|
||||||
|
import {
|
||||||
|
RequestCloseEventHandler,
|
||||||
|
TokenStateChangeEventHandler,
|
||||||
|
} from "../../types/Events";
|
||||||
|
import { TokenState } from "../../types/TokenState";
|
||||||
|
import { Map } from "../../types/Map";
|
||||||
|
|
||||||
|
type TokenMenuProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onRequestClose: RequestCloseEventHandler;
|
||||||
|
tokenState: TokenState;
|
||||||
|
tokenImage: Konva.Node;
|
||||||
|
onTokenStateChange: TokenStateChangeEventHandler;
|
||||||
|
map: Map;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultTokenMaxSize = 6;
|
const defaultTokenMaxSize = 6;
|
||||||
function TokenMenu({
|
function TokenMenu({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -24,7 +41,7 @@ function TokenMenu({
|
|||||||
tokenImage,
|
tokenImage,
|
||||||
onTokenStateChange,
|
onTokenStateChange,
|
||||||
map,
|
map,
|
||||||
}) {
|
}: TokenMenuProps) {
|
||||||
const userId = useUserId();
|
const userId = useUserId();
|
||||||
|
|
||||||
const wasOpen = usePrevious(isOpen);
|
const wasOpen = usePrevious(isOpen);
|
||||||
@ -39,22 +56,25 @@ function TokenMenu({
|
|||||||
if (tokenImage) {
|
if (tokenImage) {
|
||||||
const imageRect = tokenImage.getClientRect();
|
const imageRect = tokenImage.getClientRect();
|
||||||
const mapElement = document.querySelector(".map");
|
const mapElement = document.querySelector(".map");
|
||||||
|
if (mapElement) {
|
||||||
const mapRect = mapElement.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
|
||||||
|
);
|
||||||
// Y 12px from the bottom
|
// Y 12px from the bottom
|
||||||
setMenuTop(mapRect.top + imageRect.y + imageRect.height + 12);
|
setMenuTop(mapRect.top + imageRect.y + imageRect.height + 12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [isOpen, tokenState, wasOpen, tokenImage]);
|
}, [isOpen, tokenState, wasOpen, tokenImage]);
|
||||||
|
|
||||||
function handleLabelChange(event) {
|
function handleLabelChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const label = event.target.value.substring(0, 48);
|
const label = event.target.value.substring(0, 48);
|
||||||
tokenState && onTokenStateChange({ [tokenState.id]: { label: label } });
|
tokenState && onTokenStateChange({ [tokenState.id]: { label: label } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStatusChange(status) {
|
function handleStatusChange(status: string) {
|
||||||
if (!tokenState) {
|
if (!tokenState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,12 +89,12 @@ function TokenMenu({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSizeChange(event) {
|
function handleSizeChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const newSize = parseFloat(event.target.value);
|
const newSize = parseFloat(event.target.value);
|
||||||
tokenState && onTokenStateChange({ [tokenState.id]: { size: newSize } });
|
tokenState && onTokenStateChange({ [tokenState.id]: { size: newSize } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRotationChange(event) {
|
function handleRotationChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const newRotation = parseInt(event.target.value);
|
const newRotation = parseInt(event.target.value);
|
||||||
tokenState &&
|
tokenState &&
|
||||||
onTokenStateChange({
|
onTokenStateChange({
|
||||||
@ -96,16 +116,20 @@ function TokenMenu({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModalContent(node) {
|
function handleModalContent(node: HTMLElement) {
|
||||||
if (node) {
|
if (node) {
|
||||||
// Focus input
|
// Focus input
|
||||||
const tokenLabelInput = node.querySelector("#changeTokenLabel");
|
const tokenLabelInput =
|
||||||
|
node.querySelector<HTMLInputElement>("#changeTokenLabel");
|
||||||
|
if (tokenLabelInput) {
|
||||||
tokenLabelInput.focus();
|
tokenLabelInput.focus();
|
||||||
tokenLabelInput.select();
|
tokenLabelInput.select();
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure menu is in bounds
|
// Ensure menu is in bounds
|
||||||
const nodeRect = node.getBoundingClientRect();
|
const nodeRect = node.getBoundingClientRect();
|
||||||
const mapElement = document.querySelector(".map");
|
const mapElement = document.querySelector(".map");
|
||||||
|
if (mapElement) {
|
||||||
const mapRect = mapElement.getBoundingClientRect();
|
const mapRect = mapElement.getBoundingClientRect();
|
||||||
setMenuLeft((prevLeft) =>
|
setMenuLeft((prevLeft) =>
|
||||||
Math.min(
|
Math.min(
|
||||||
@ -118,6 +142,7 @@ function TokenMenu({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapMenu
|
<MapMenu
|
@ -1,9 +1,19 @@
|
|||||||
import React from "react";
|
|
||||||
import { Rect, Circle, Line } from "react-konva";
|
import { Rect, Circle, Line } from "react-konva";
|
||||||
|
|
||||||
import colors from "../../helpers/colors";
|
import colors from "../../helpers/colors";
|
||||||
|
import { Outline } from "../../types/Outline";
|
||||||
|
|
||||||
export function TokenOutlineSVG({ outline, width, height }) {
|
type TokenOutlineSVGProps = {
|
||||||
|
outline: Outline;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TokenOutlineSVG({
|
||||||
|
outline,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}: TokenOutlineSVGProps) {
|
||||||
if (outline.type === "rect") {
|
if (outline.type === "rect") {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@ -55,7 +65,12 @@ export function TokenOutlineSVG({ outline, width, height }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function TokenOutline({ outline, hidden }) {
|
type TokenOutlineProps = {
|
||||||
|
outline: Outline;
|
||||||
|
hidden: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenOutline({ outline, hidden }: TokenOutlineProps) {
|
||||||
const sharedProps = {
|
const sharedProps = {
|
||||||
fill: colors.black,
|
fill: colors.black,
|
||||||
opacity: hidden ? 0 : 0.8,
|
opacity: hidden ? 0 : 0.8,
|
||||||
@ -84,7 +99,7 @@ function TokenOutline({ outline, hidden }) {
|
|||||||
<Line
|
<Line
|
||||||
points={outline.points}
|
points={outline.points}
|
||||||
closed
|
closed
|
||||||
tension={outline.points < 200 ? 0 : 0.33}
|
tension={outline.points.length < 200 ? 0 : 0.33}
|
||||||
{...sharedProps}
|
{...sharedProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { Box, IconButton } from "theme-ui";
|
import { Box, IconButton } from "theme-ui";
|
||||||
import { Stage, Layer, Image, Rect, Group } from "react-konva";
|
import { Stage, Layer, Image, Rect, Group } from "react-konva";
|
||||||
import ReactResizeDetector from "react-resize-detector";
|
import ReactResizeDetector from "react-resize-detector";
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
|
import Konva from "konva";
|
||||||
|
|
||||||
import usePreventOverscroll from "../../hooks/usePreventOverscroll";
|
import usePreventOverscroll from "../../hooks/usePreventOverscroll";
|
||||||
import useStageInteraction from "../../hooks/useStageInteraction";
|
import useStageInteraction from "../../hooks/useStageInteraction";
|
||||||
@ -18,32 +19,32 @@ import GridOffIcon from "../../icons/GridOffIcon";
|
|||||||
import { tokenSources } from "../../tokens";
|
import { tokenSources } from "../../tokens";
|
||||||
|
|
||||||
import Grid from "../Grid";
|
import Grid from "../Grid";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
function TokenPreview({ token }) {
|
type TokenPreviewProps = {
|
||||||
const [tokenSourceData, setTokenSourceData] = useState({});
|
token: Token;
|
||||||
useEffect(() => {
|
};
|
||||||
if (token.id !== tokenSourceData.id) {
|
|
||||||
setTokenSourceData(token);
|
|
||||||
}
|
|
||||||
}, [token, tokenSourceData]);
|
|
||||||
|
|
||||||
const tokenURL = useDataURL(tokenSourceData, tokenSources);
|
function TokenPreview({ token }: TokenPreviewProps) {
|
||||||
const [tokenSourceImage] = useImage(tokenURL);
|
const tokenURL = useDataURL(token, tokenSources);
|
||||||
|
const [tokenSourceImage] = useImage(tokenURL || "");
|
||||||
|
|
||||||
const [stageWidth, setStageWidth] = useState(1);
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
|
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
const tokenStageRef = useRef();
|
const tokenStageRef = useRef<Konva.Stage>(null);
|
||||||
const tokenLayerRef = useRef();
|
const tokenLayerRef = useRef<Konva.Layer>(null);
|
||||||
|
|
||||||
function handleResize(width, height) {
|
function handleResize(width?: number, height?: number) {
|
||||||
|
if (width && height) {
|
||||||
setStageWidth(width);
|
setStageWidth(width);
|
||||||
setStageHeight(height);
|
setStageHeight(height);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const containerRef = useRef();
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
usePreventOverscroll(containerRef);
|
usePreventOverscroll(containerRef);
|
||||||
|
|
||||||
const [tokenWidth, tokenHeight] = useImageCenter(
|
const [tokenWidth, tokenHeight] = useImageCenter(
|
||||||
@ -59,11 +60,11 @@ function TokenPreview({ token }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useStageInteraction(
|
useStageInteraction(
|
||||||
tokenStageRef.current,
|
tokenStageRef,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
tokenLayerRef.current
|
tokenLayerRef
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showGridPreview, setShowGridPreview] = useState(true);
|
const [showGridPreview, setShowGridPreview] = useState(true);
|
@ -1,17 +1,25 @@
|
|||||||
import React from "react";
|
|
||||||
import { Flex, Box, Input, Label } from "theme-ui";
|
import { Flex, Box, Input, Label } from "theme-ui";
|
||||||
|
|
||||||
import { isEmpty } from "../../helpers/shared";
|
import { isEmpty } from "../../helpers/shared";
|
||||||
|
|
||||||
import Select from "../Select";
|
import Select from "../Select";
|
||||||
|
|
||||||
const categorySettings = [
|
import { Token, TokenCategory } from "../../types/Token";
|
||||||
|
import { TokenSettingsChangeEventHandler } from "../../types/Events";
|
||||||
|
|
||||||
|
type CategorySetting = { value: TokenCategory; label: string };
|
||||||
|
const categorySettings: CategorySetting[] = [
|
||||||
{ value: "character", label: "Character" },
|
{ value: "character", label: "Character" },
|
||||||
{ value: "prop", label: "Prop" },
|
{ value: "prop", label: "Prop" },
|
||||||
{ value: "vehicle", label: "Vehicle / Mount" },
|
{ value: "vehicle", label: "Vehicle / Mount" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function TokenSettings({ token, onSettingsChange }) {
|
type TokenSettingsProps = {
|
||||||
|
token: Token;
|
||||||
|
onSettingsChange: TokenSettingsChangeEventHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenSettings({ token, onSettingsChange }: TokenSettingsProps) {
|
||||||
const tokenEmpty = !token || isEmpty(token);
|
const tokenEmpty = !token || isEmpty(token);
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ flexDirection: "column" }}>
|
<Flex sx={{ flexDirection: "column" }}>
|
||||||
@ -20,7 +28,7 @@ function TokenSettings({ token, onSettingsChange }) {
|
|||||||
<Input
|
<Input
|
||||||
name="name"
|
name="name"
|
||||||
value={(token && token.name) || ""}
|
value={(token && token.name) || ""}
|
||||||
onChange={(e) => onSettingsChange("name", e.target.value)}
|
onChange={(e) => onSettingsChange({ name: e.target.value })}
|
||||||
disabled={tokenEmpty}
|
disabled={tokenEmpty}
|
||||||
my={1}
|
my={1}
|
||||||
/>
|
/>
|
||||||
@ -30,12 +38,14 @@ function TokenSettings({ token, onSettingsChange }) {
|
|||||||
<Select
|
<Select
|
||||||
options={categorySettings}
|
options={categorySettings}
|
||||||
value={
|
value={
|
||||||
!tokenEmpty &&
|
tokenEmpty
|
||||||
categorySettings.find((s) => s.value === token.defaultCategory)
|
? undefined
|
||||||
|
: categorySettings.find((s) => s.value === token.defaultCategory)
|
||||||
}
|
}
|
||||||
isDisabled={tokenEmpty}
|
isDisabled={tokenEmpty}
|
||||||
onChange={(option) =>
|
onChange={
|
||||||
onSettingsChange("defaultCategory", option.value)
|
((option: CategorySetting) =>
|
||||||
|
onSettingsChange({ defaultCategory: option.value })) as any
|
||||||
}
|
}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
/>
|
/>
|
||||||
@ -47,7 +57,7 @@ function TokenSettings({ token, onSettingsChange }) {
|
|||||||
name="tokenSize"
|
name="tokenSize"
|
||||||
value={`${(token && token.defaultSize) || 0}`}
|
value={`${(token && token.defaultSize) || 0}`}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSettingsChange("defaultSize", parseFloat(e.target.value))
|
onSettingsChange({ defaultSize: parseFloat(e.target.value) })
|
||||||
}
|
}
|
||||||
disabled={tokenEmpty}
|
disabled={tokenEmpty}
|
||||||
min={1}
|
min={1}
|
||||||
@ -59,7 +69,7 @@ function TokenSettings({ token, onSettingsChange }) {
|
|||||||
<Input
|
<Input
|
||||||
name="label"
|
name="label"
|
||||||
value={(token && token.defaultLabel) || ""}
|
value={(token && token.defaultLabel) || ""}
|
||||||
onChange={(e) => onSettingsChange("defaultLabel", e.target.value)}
|
onChange={(e) => onSettingsChange({ defaultLabel: e.target.value })}
|
||||||
disabled={tokenEmpty}
|
disabled={tokenEmpty}
|
||||||
my={1}
|
my={1}
|
||||||
/>
|
/>
|
@ -1,9 +1,15 @@
|
|||||||
import React from "react";
|
|
||||||
import { Circle, Group } from "react-konva";
|
import { Circle, Group } from "react-konva";
|
||||||
|
|
||||||
import colors from "../../helpers/colors";
|
import colors from "../../helpers/colors";
|
||||||
|
import { TokenState } from "../../types/TokenState";
|
||||||
|
|
||||||
function TokenStatus({ tokenState, width, height }) {
|
type TokenStatusProps = {
|
||||||
|
tokenState: TokenState;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenStatus({ tokenState, width, height }: TokenStatusProps) {
|
||||||
// Ensure statuses is an array and filter empty values
|
// Ensure statuses is an array and filter empty values
|
||||||
const statuses = [...new Set((tokenState?.statuses || []).filter((s) => s))];
|
const statuses = [...new Set((tokenState?.statuses || []).filter((s) => s))];
|
||||||
return (
|
return (
|
@ -1,8 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
import Tile from "../tile/Tile";
|
import Tile from "../tile/Tile";
|
||||||
import TokenImage from "./TokenImage";
|
import TokenImage from "./TokenImage";
|
||||||
|
|
||||||
|
type TokenTileProps = {
|
||||||
|
token: Token;
|
||||||
|
isSelected: boolean;
|
||||||
|
onSelect: (tokenId: string) => void;
|
||||||
|
onTokenEdit: (tokenId: string) => void;
|
||||||
|
canEdit: boolean;
|
||||||
|
badges: React.ReactChild[];
|
||||||
|
};
|
||||||
|
|
||||||
function TokenTile({
|
function TokenTile({
|
||||||
token,
|
token,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -10,7 +20,7 @@ function TokenTile({
|
|||||||
onTokenEdit,
|
onTokenEdit,
|
||||||
canEdit,
|
canEdit,
|
||||||
badges,
|
badges,
|
||||||
}) {
|
}: TokenTileProps) {
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile
|
||||||
title={token.name}
|
title={token.name}
|
@ -1,10 +1,19 @@
|
|||||||
import React from "react";
|
|
||||||
import { Grid } from "theme-ui";
|
import { Grid } from "theme-ui";
|
||||||
|
|
||||||
import Tile from "../tile/Tile";
|
import Tile from "../tile/Tile";
|
||||||
import TokenImage from "./TokenImage";
|
import TokenImage from "./TokenImage";
|
||||||
|
|
||||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||||
|
import { GroupContainer } from "../../types/Group";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
|
||||||
|
type TokenTileProps = {
|
||||||
|
group: GroupContainer;
|
||||||
|
tokens: Token[];
|
||||||
|
isSelected: boolean;
|
||||||
|
onSelect: (tokenId: string) => void;
|
||||||
|
onDoubleClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
function TokenTileGroup({
|
function TokenTileGroup({
|
||||||
group,
|
group,
|
||||||
@ -12,7 +21,7 @@ function TokenTileGroup({
|
|||||||
isSelected,
|
isSelected,
|
||||||
onSelect,
|
onSelect,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
}) {
|
}: TokenTileProps) {
|
||||||
const layout = useResponsiveLayout();
|
const layout = useResponsiveLayout();
|
||||||
|
|
||||||
return (
|
return (
|
@ -1,5 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import TokenTile from "./TokenTile";
|
import TokenTile from "./TokenTile";
|
||||||
import TokenTileGroup from "./TokenTileGroup";
|
import TokenTileGroup from "./TokenTileGroup";
|
||||||
import TokenHiddenBadge from "./TokenHiddenBadge";
|
import TokenHiddenBadge from "./TokenHiddenBadge";
|
||||||
@ -10,12 +8,20 @@ import SortableTilesDragOverlay from "../tile/SortableTilesDragOverlay";
|
|||||||
import { getGroupItems } from "../../helpers/group";
|
import { getGroupItems } from "../../helpers/group";
|
||||||
|
|
||||||
import { useGroup } from "../../contexts/GroupContext";
|
import { useGroup } from "../../contexts/GroupContext";
|
||||||
|
import { Token } from "../../types/Token";
|
||||||
|
import { Group } from "../../types/Group";
|
||||||
|
|
||||||
function TokenTiles({ tokensById, onTokenEdit, subgroup }) {
|
type TokenTilesProps = {
|
||||||
|
tokensById: Record<string, Token>;
|
||||||
|
onTokenEdit: (tokenId: string) => void;
|
||||||
|
subgroup: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TokenTiles({ tokensById, onTokenEdit, subgroup }: TokenTilesProps) {
|
||||||
const { selectedGroupIds, selectMode, onGroupOpen, onGroupSelect } =
|
const { selectedGroupIds, selectMode, onGroupOpen, onGroupSelect } =
|
||||||
useGroup();
|
useGroup();
|
||||||
|
|
||||||
function renderTile(group) {
|
function renderTile(group: Group) {
|
||||||
if (group.type === "item") {
|
if (group.type === "item") {
|
||||||
const token = tokensById[group.id];
|
const token = tokensById[group.id];
|
||||||
if (token) {
|
if (token) {
|
@ -44,10 +44,10 @@ function EditTokenModal({
|
|||||||
>({});
|
>({});
|
||||||
|
|
||||||
// TODO: CHANGE MAP BACK? OR CHANGE THIS TO PARTIAL
|
// TODO: CHANGE MAP BACK? OR CHANGE THIS TO PARTIAL
|
||||||
function handleTokenSettingsChange(key: string, value: Pick<Token, any>) {
|
function handleTokenSettingsChange(change: Partial<Token>) {
|
||||||
setTokenSettingChanges((prevChanges) => ({
|
setTokenSettingChanges((prevChanges) => ({
|
||||||
...prevChanges,
|
...prevChanges,
|
||||||
[key]: value,
|
...change,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ function EditTokenModal({
|
|||||||
const selectedTokenWithChanges = {
|
const selectedTokenWithChanges = {
|
||||||
...token,
|
...token,
|
||||||
...tokenSettingChanges,
|
...tokenSettingChanges,
|
||||||
};
|
} as Token;
|
||||||
|
|
||||||
const layout = useResponsiveLayout();
|
const layout = useResponsiveLayout();
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { DefaultDice } from "./Dice";
|
|||||||
import { Map } from "./Map";
|
import { Map } from "./Map";
|
||||||
import { MapState } from "./MapState";
|
import { MapState } from "./MapState";
|
||||||
import { Note } from "./Note";
|
import { Note } from "./Note";
|
||||||
|
import { Token } from "./Token";
|
||||||
import { TokenState } from "./TokenState";
|
import { TokenState } from "./TokenState";
|
||||||
|
|
||||||
export type MapChangeEventHandler = (map?: Map, mapState?: MapState) => void;
|
export type MapChangeEventHandler = (map?: Map, mapState?: MapState) => void;
|
||||||
@ -26,6 +27,7 @@ export type TokenMenuOpenChangeEventHandler = (
|
|||||||
tokenStateId: string,
|
tokenStateId: string,
|
||||||
tokenImage: Konva.Node
|
tokenImage: Konva.Node
|
||||||
) => void;
|
) => void;
|
||||||
|
export type TokenSettingsChangeEventHandler = (change: Partial<Token>) => void;
|
||||||
|
|
||||||
export type NoteAddEventHander = (note: Note) => void;
|
export type NoteAddEventHander = (note: Note) => void;
|
||||||
export type NoteRemoveEventHander = (noteId: string) => void;
|
export type NoteRemoveEventHander = (noteId: string) => void;
|
||||||
|
@ -40,5 +40,5 @@ export type TokenMenuOptions = {
|
|||||||
export type TokenDraggingOptions = {
|
export type TokenDraggingOptions = {
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
tokenState: TokenState;
|
tokenState: TokenState;
|
||||||
tokenGroup: Konva.Node;
|
tokenNode: Konva.Node;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Color } from "../helpers/colors";
|
||||||
import { Outline } from "./Outline";
|
import { Outline } from "./Outline";
|
||||||
import { TokenCategory } from "./Token";
|
import { TokenCategory } from "./Token";
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ export type BaseTokenState = {
|
|||||||
size: number;
|
size: number;
|
||||||
category: TokenCategory;
|
category: TokenCategory;
|
||||||
label: string;
|
label: string;
|
||||||
statuses: string[];
|
statuses: Color[];
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
lastModifiedBy: string;
|
lastModifiedBy: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user