import { Group } from "react-konva"; import { v4 as uuid } from "uuid"; import { Map, MapToolId } from "../types/Map"; import { MapState } from "../types/MapState"; import { TokenCategory, TokenDraggingOptions, TokenMenuOptions, } from "../types/Token"; import { TokenState } from "../types/TokenState"; import { TokenStateRemoveHandler, TokenStateChangeEventHandler, TokensStateCreateHandler, } from "../types/Events"; import { useState } from "react"; import Konva from "konva"; import Token from "../components/konva/Token"; import { KonvaEventObject } from "konva/lib/Node"; import TokenMenu from "../components/token/TokenMenu"; import TokenDragOverlay from "../components/token/TokenDragOverlay"; import { useUserId } from "../contexts/UserIdContext"; import { useBlur, useKeyboard } from "../contexts/KeyboardContext"; import shortcuts from "../shortcuts"; function useMapTokens( map: Map | null, mapState: MapState | null, onTokenStateChange: TokenStateChangeEventHandler, onTokenStateRemove: TokenStateRemoveHandler, onTokensStateCreate: TokensStateCreateHandler, selectedToolId: MapToolId ) { const userId = useUserId(); const disabledTokens: Record = {}; if (mapState && map) { if (!mapState.editFlags.includes("tokens") && map.owner !== userId) { for (let token of Object.values(mapState.tokens)) { if (token.owner !== userId) { disabledTokens[token.id] = true; } } } } const [isTokenMenuOpen, setIsTokenMenuOpen] = useState(false); const [tokenMenuOptions, setTokenMenuOptions] = useState(); const [tokenDraggingOptions, setTokenDraggingOptions] = useState(); function handleTokenMenuOpen( tokenStateId: string, tokenImage: Konva.Node, focus: boolean ) { setTokenMenuOptions({ tokenStateId, tokenImage, focus }); setIsTokenMenuOpen(true); } function handleTokenMenuClose() { setIsTokenMenuOpen(false); } function handleTokenDragStart( _: KonvaEventObject, tokenStateId: string, attachedTokenStateIds: string[] ) { if (duplicateToken) { let newStates: TokenState[] = []; for (let id of [tokenStateId, ...attachedTokenStateIds]) { const state = mapState?.tokens[id]; if (state) { newStates.push({ ...state, id: uuid() }); } } onTokensStateCreate(newStates); } setTokenDraggingOptions({ dragging: true, tokenStateId, attachedTokenStateIds, }); } function handleTokenDragEnd() { tokenDraggingOptions && setTokenDraggingOptions({ ...tokenDraggingOptions, dragging: false, }); } function handleTokenStateRemove(tokenStateIds: string[]) { onTokenStateRemove(tokenStateIds); setTokenDraggingOptions(undefined); } const [duplicateToken, setDuplicateToken] = useState(false); function handleKeyDown(event: KeyboardEvent) { if (shortcuts.duplicate(event)) { setDuplicateToken(true); } } function handleKeyUp(event: KeyboardEvent) { if (shortcuts.duplicate(event)) { setDuplicateToken(false); } } function handleBlur() { setDuplicateToken(false); } useKeyboard(handleKeyDown, handleKeyUp); useBlur(handleBlur); const [transformingTokensIds, setTransformingTokenIds] = useState( [] ); function handleTokenTransformStart( event: Konva.KonvaEventObject, attachments: Konva.Node[] ) { const transformer = event.currentTarget as Konva.Transformer; const nodes = transformer.nodes(); setTransformingTokenIds( [...nodes, ...attachments].map((node) => node.id()) ); } function handleTokenTransformEnd() { setTransformingTokenIds([]); } function tokenFromTokenState(tokenState: TokenState) { return ( map && mapState && ( ) ); } const tokens = map && mapState && ( {Object.values(mapState.tokens) .filter((tokenState) => tokenState.category !== "prop") .sort((a, b) => sortMapTokenStates(a, b, tokenDraggingOptions)) .map(tokenFromTokenState)} ); const propTokens = map && mapState && ( {Object.values(mapState.tokens) .filter((tokenState) => tokenState.category === "prop") .sort((a, b) => sortMapTokenStates(a, b, tokenDraggingOptions)) .map(tokenFromTokenState)} ); const tokenMenu = ( ); const tokenDragOverlay = tokenDraggingOptions && ( ); return { tokens, propTokens, tokenMenu, tokenDragOverlay }; } export default useMapTokens; function getMapTokenCategoryWeight(category: TokenCategory) { switch (category) { case "attachment": return 0; case "character": return 1; case "vehicle": return 2; case "prop": return 3; default: return 0; } } // Sort so vehicles render below other tokens function sortMapTokenStates( a: TokenState, b: TokenState, tokenDraggingOptions?: TokenDraggingOptions ) { // If categories are different sort in order "prop", "vehicle", "character", "attachment" if (b.category !== a.category) { const aWeight = getMapTokenCategoryWeight(a.category); const bWeight = getMapTokenCategoryWeight(b.category); return bWeight - aWeight; } else if ( tokenDraggingOptions && tokenDraggingOptions.dragging && tokenDraggingOptions.tokenStateId === a.id ) { // If dragging token a move above return 1; } else if ( tokenDraggingOptions && tokenDraggingOptions.dragging && tokenDraggingOptions.tokenStateId === b.id ) { // If dragging token b move above return -1; } else { // Else sort so last modified is on top return a.lastModified - b.lastModified; } }