From bac31018869f64cb2d0d2891cba25348012a27c8 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 28 May 2021 17:06:20 +1000 Subject: [PATCH] Add ability to drag and drop a tile onto the map and added better drag cursors --- src/components/tile/SortableTile.js | 7 +- src/components/tile/SortableTiles.js | 4 +- src/components/tile/Tile.js | 1 - src/components/tile/TilesAddDroppable.js | 54 +++++++ src/components/tile/TilesOverlay.js | 100 +++++------- src/components/tile/TilesUngroupDroppable.js | 59 +++++++ src/components/token/SelectTokensButton.js | 3 +- src/components/token/TokenBar.js | 36 +---- src/contexts/TileDragContext.js | 28 +++- src/helpers/token.js | 34 ++++ src/modals/SelectMapModal.js | 101 +++++++----- src/modals/SelectTokensModal.js | 159 +++++++++++++------ 12 files changed, 400 insertions(+), 186 deletions(-) create mode 100644 src/components/tile/TilesAddDroppable.js create mode 100644 src/components/tile/TilesUngroupDroppable.js diff --git a/src/components/tile/SortableTile.js b/src/components/tile/SortableTile.js index 1b2e15f..42e5c09 100644 --- a/src/components/tile/SortableTile.js +++ b/src/components/tile/SortableTile.js @@ -13,6 +13,7 @@ function SortableTile({ hidden, children, isDragging, + cursor, }) { const { attributes, @@ -29,7 +30,7 @@ function SortableTile({ }); const dragStyle = { - cursor: "pointer", + cursor, opacity: isDragging ? 0.25 : undefined, }; @@ -92,4 +93,8 @@ function SortableTile({ ); } +SortableTile.defaultProps = { + cursor: "pointer", +}; + export default SortableTile; diff --git a/src/components/tile/SortableTiles.js b/src/components/tile/SortableTiles.js index ad575af..d8ff37f 100644 --- a/src/components/tile/SortableTiles.js +++ b/src/components/tile/SortableTiles.js @@ -20,7 +20,7 @@ import { import { useGroup } from "../../contexts/GroupContext"; function SortableTiles({ renderTile, subgroup }) { - const { dragId, overId } = useTileDrag(); + const { dragId, overId, dragCursor } = useTileDrag(); const { groups: allGroups, selectedGroupIds: allSelectedIds, @@ -88,6 +88,7 @@ function SortableTiles({ renderTile, subgroup }) {
@@ -137,6 +138,7 @@ function SortableTiles({ renderTile, subgroup }) { disableSorting={disableSorting} hidden={group.id === openGroupId} isDragging={isDragging} + cursor={dragCursor} > {renderSortableGroup(group, selectedGroups)} diff --git a/src/components/tile/Tile.js b/src/components/tile/Tile.js index 0904eb4..8ee58a5 100644 --- a/src/components/tile/Tile.js +++ b/src/components/tile/Tile.js @@ -24,7 +24,6 @@ function Tile({ borderRadius: "4px", justifyContent: "center", alignItems: "center", - cursor: "pointer", overflow: "hidden", userSelect: "none", }} diff --git a/src/components/tile/TilesAddDroppable.js b/src/components/tile/TilesAddDroppable.js new file mode 100644 index 0000000..7bd6cde --- /dev/null +++ b/src/components/tile/TilesAddDroppable.js @@ -0,0 +1,54 @@ +import React from "react"; +import { createPortal } from "react-dom"; + +import Droppable from "../drag/Droppable"; + +import { ADD_TO_MAP_ID_PREFIX } from "../../contexts/TileDragContext"; + +function TilesAddDroppable({ containerSize }) { + return createPortal( +
+ + + + +
, + document.body + ); +} + +export default TilesAddDroppable; diff --git a/src/components/tile/TilesOverlay.js b/src/components/tile/TilesOverlay.js index cb15c8a..e280013 100644 --- a/src/components/tile/TilesOverlay.js +++ b/src/components/tile/TilesOverlay.js @@ -1,14 +1,12 @@ import React, { useState } from "react"; -import { createPortal } from "react-dom"; import { Box, Close, Grid, useThemeUI } from "theme-ui"; import { useSpring, animated, config } from "react-spring"; import ReactResizeDetector from "react-resize-detector"; import SimpleBar from "simplebar-react"; import { useGroup } from "../../contexts/GroupContext"; -import { UNGROUP_ID_PREFIX } from "../../contexts/TileDragContext"; -import Droppable from "../drag/Droppable"; +import TilesUngroupDroppable from "./TilesUngroupDroppable"; import useResponsiveLayout from "../../hooks/useResponsiveLayout"; @@ -25,73 +23,47 @@ function TilesOverlay({ children }) { config: config.gentle, }); - const [containerSize, setContinerSize] = useState(0); - function handleResize(width, height) { + const [containerSize, setContinerSize] = useState({ width: 0, height: 0 }); + function handleContainerResize(width, height) { const size = Math.min(width, height) - 16; - setContinerSize(size); + setContinerSize({ width: size, height: size }); } - function renderUngroupBoxes() { - return createPortal( -
- - - - -
, - document.body - ); + const [overlaySize, setOverlaySize] = useState({ width: 0, height: 0 }); + function handleOverlayResize(width, height) { + setOverlaySize({ width, height }); } return ( <> - {openGroupId && renderUngroupBoxes()} {openGroupId && ( - )} - + {openGroupId && ( + + + + )} + + + + + +
, + document.body + ); +} + +export default TilesUngroupDroppable; diff --git a/src/components/token/SelectTokensButton.js b/src/components/token/SelectTokensButton.js index 2792b02..503abd4 100644 --- a/src/components/token/SelectTokensButton.js +++ b/src/components/token/SelectTokensButton.js @@ -5,7 +5,7 @@ import SelectTokensIcon from "../../icons/SelectTokensIcon"; import SelectTokensModal from "../../modals/SelectTokensModal"; -function SelectTokensButton() { +function SelectTokensButton({ onMapTokenStateCreate }) { const [isModalOpen, setIsModalOpen] = useState(false); function openModal() { setIsModalOpen(true); @@ -30,6 +30,7 @@ function SelectTokensButton() { isOpen={isModalOpen} onRequestClose={closeModal} onDone={handleDone} + onMapTokenStateCreate={onMapTokenStateCreate} /> ); diff --git a/src/components/token/TokenBar.js b/src/components/token/TokenBar.js index 3a75cdb..f00fea8 100644 --- a/src/components/token/TokenBar.js +++ b/src/components/token/TokenBar.js @@ -16,7 +16,10 @@ import { useTokenData } from "../../contexts/TokenDataContext"; import { useAuth } from "../../contexts/AuthContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { createTokenState } from "../../helpers/token"; +import { + createTokenState, + clientPositionToMapPosition, +} from "../../helpers/token"; function TokenBar({ onMapTokenStateCreate }) { const { userId } = useAuth(); @@ -41,38 +44,15 @@ function TokenBar({ onMapTokenStateCreate }) { const mapStage = mapStageRef.current; const dragOverlay = dragOverlayRef.current; if (mapStage && dragOverlay) { - const mapImage = mapStage.findOne("#mapImage"); - const map = document.querySelector(".map"); - const mapRect = map.getBoundingClientRect(); const dragRect = dragOverlay.getBoundingClientRect(); - const dragPosition = { x: dragRect.left + dragRect.width / 2, y: dragRect.top + dragRect.height / 2, }; - - // Check map bounds - if (dragPosition.x < mapRect.left || dragPosition.x > mapRect.right) { - return; - } - - // Convert relative to map rect - const mapPosition = { - x: dragPosition.x - mapRect.left, - y: dragPosition.y - mapRect.top, - }; - - // Convert relative to map image - const transform = mapImage.getAbsoluteTransform().copy().invert(); - const relativePosition = transform.point(mapPosition); - const normalizedPosition = { - x: relativePosition.x / mapImage.width(), - y: relativePosition.y / mapImage.height(), - }; - + const mapPosition = clientPositionToMapPosition(mapStage, dragPosition); const token = tokensById[active.id]; - if (token) { - const tokenState = createTokenState(token, normalizedPosition, userId); + if (token && mapPosition) { + const tokenState = createTokenState(token, mapPosition, userId); onMapTokenStateCreate(tokenState); } } @@ -143,7 +123,7 @@ function TokenBar({ onMapTokenStateCreate }) { alignItems: "center", }} > - + {createPortal( group.id === over.id); @@ -105,6 +119,7 @@ export function TileDragProvider({ children }) { } function customCollisionDetection(rects, rect) { + // Handle group rects if (groupOpen) { const ungroupRects = rects.filter(([id]) => id.startsWith(UNGROUP_ID_PREFIX) @@ -115,12 +130,21 @@ export function TileDragProvider({ children }) { } } + // Handle add to map rects + const addRects = rects.filter(([id]) => + id.startsWith(ADD_TO_MAP_ID_PREFIX) + ); + const intersectingAddRect = rectIntersection(addRects, rect); + if (intersectingAddRect) { + return intersectingAddRect; + } + const otherRects = rects.filter(([id]) => id !== UNGROUP_ID_PREFIX); return closestCenter(otherRects, rect); } - const value = { dragId, overId }; + const value = { dragId, overId, dragCursor }; return ( mapRect.right) + ) { + return; + } + + // Convert relative to map rect + const mapPosition = { + x: clientPosition.x - mapRect.left, + y: clientPosition.y - mapRect.top, + }; + + // Convert relative to map image + const transform = mapImage.getAbsoluteTransform().copy().invert(); + const relativePosition = transform.point(mapPosition); + const normalizedPosition = { + x: relativePosition.x / mapImage.width(), + y: relativePosition.y / mapImage.height(), + }; + + return normalizedPosition; +} diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js index 7fbf618..65b66d3 100644 --- a/src/modals/SelectMapModal.js +++ b/src/modals/SelectMapModal.js @@ -1,6 +1,7 @@ import React, { useRef, useState } from "react"; import { Button, Flex, Label, Box } from "theme-ui"; import { useToasts } from "react-toast-notifications"; +import ReactResizeDetector from "react-resize-detector"; import EditMapModal from "./EditMapModal"; import ConfirmModal from "./ConfirmModal"; @@ -13,6 +14,7 @@ import MapTiles from "../components/map/MapTiles"; import TilesOverlay from "../components/tile/TilesOverlay"; import TilesContainer from "../components/tile/TilesContainer"; +import TilesAddDroppable from "../components/tile/TilesAddDroppable"; import { groupsFromIds, itemsFromGroups, findGroup } from "../helpers/group"; import { createMapFromFile } from "../helpers/map"; @@ -230,6 +232,11 @@ function SelectMapModal({ const layout = useResponsiveLayout(); + const [modalSize, setModalSize] = useState({ width: 0, height: 0 }); + function handleModalResize(width, height) { + setModalSize({ width, height }); + } + return ( - - - - - - - setIsEditModalOpen(true)} - onMapSelect={handleMapSelect} - /> - - - - - setIsEditModalOpen(true)} - onMapSelect={handleMapSelect} - subgroup - /> - - - - - - + + + + + + + setIsEditModalOpen(true)} + onMapSelect={handleMapSelect} + /> + + + + + + setIsEditModalOpen(true)} + onMapSelect={handleMapSelect} + subgroup + /> + + + + + + + {(isLoading || mapsLoading) && } group.id === id && group.type === "group" + ); + if (group) { + // Add all tokens of group + const items = getGroupItems(group); + for (let item of items) { + if (item.id in tokensById) { + onMapTokenStateCreate( + createTokenState(tokensById[item.id], position, userId) + ); + position = Vector2.add(position, 0.01); + } + } + } + } + } + } + /** * Shortcuts */ @@ -171,6 +227,11 @@ function SelectTokensModal({ isOpen, onRequestClose }) { const layout = useResponsiveLayout(); + const [modalSize, setModalSize] = useState({ width: 0, height: 0 }); + function handleModalResize(width, height) { + setModalSize({ width, height }); + } + return ( - - - - - - - setIsEditModalOpen(true)} - /> - - - - - setIsEditModalOpen(true)} - subgroup - /> - - - - - - - + + + + + + + setIsEditModalOpen(true)} + /> + + + + + + setIsEditModalOpen(true)} + subgroup + /> + + + + + + + + {(isLoading || tokensLoading) && }