Add ability to drag and drop a tile onto the map and added better drag cursors

This commit is contained in:
Mitchell McCaffrey
2021-05-28 17:06:20 +10:00
parent f84baab6fb
commit bac3101886
12 changed files with 400 additions and 186 deletions
+6 -1
View File
@@ -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;
+3 -1
View File
@@ -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 }) {
<div
style={{
transform: `translate(${coords[index].x}%, ${coords[index].y}%)`,
cursor: dragCursor,
}}
>
<animated.div style={dragBounce}>
@@ -137,6 +138,7 @@ function SortableTiles({ renderTile, subgroup }) {
disableSorting={disableSorting}
hidden={group.id === openGroupId}
isDragging={isDragging}
cursor={dragCursor}
>
{renderSortableGroup(group, selectedGroups)}
</SortableTile>
-1
View File
@@ -24,7 +24,6 @@ function Tile({
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
overflow: "hidden",
userSelect: "none",
}}
+54
View File
@@ -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(
<div>
<Droppable
id={`${ADD_TO_MAP_ID_PREFIX}-1`}
style={{
width: "100vw",
height: `calc(50vh - ${containerSize.height / 2}px)`,
position: "absolute",
top: 0,
}}
/>
<Droppable
id={`${ADD_TO_MAP_ID_PREFIX}-2`}
style={{
width: "100vw",
height: `calc(50vh - ${containerSize.height / 2}px)`,
position: "absolute",
bottom: 0,
}}
/>
<Droppable
id={`${ADD_TO_MAP_ID_PREFIX}-3`}
style={{
width: `calc(50vw - ${containerSize.width / 2}px)`,
height: "100vh",
position: "absolute",
top: 0,
left: 0,
}}
/>
<Droppable
id={`${ADD_TO_MAP_ID_PREFIX}-4`}
style={{
width: `calc(50vw - ${containerSize.width / 2}px)`,
height: "100vh",
position: "absolute",
top: 0,
right: 0,
}}
/>
</div>,
document.body
);
}
export default TilesAddDroppable;
+36 -64
View File
@@ -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(
<div>
<Droppable
id={`${UNGROUP_ID_PREFIX}-1`}
style={{
width: "100vw",
height: `calc(50vh - ${containerSize / 2}px)`,
position: "absolute",
top: 0,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-2`}
style={{
width: "100vw",
height: `calc(50vh - ${containerSize / 2}px)`,
position: "absolute",
bottom: 0,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-3`}
style={{
width: `calc(50vw - ${containerSize / 2}px)`,
height: "100vh",
position: "absolute",
top: 0,
left: 0,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-4`}
style={{
width: `calc(50vw - ${containerSize / 2}px)`,
height: "100vh",
position: "absolute",
top: 0,
right: 0,
}}
/>
</div>,
document.body
);
const [overlaySize, setOverlaySize] = useState({ width: 0, height: 0 });
function handleOverlayResize(width, height) {
setOverlaySize({ width, height });
}
return (
<>
{openGroupId && renderUngroupBoxes()}
{openGroupId && (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
top: 0,
}}
bg="overlay"
<TilesUngroupDroppable
innerContainerSize={containerSize}
outerContainerSize={overlaySize}
/>
)}
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
{openGroupId && (
<ReactResizeDetector
handleWidth
handleHeight
onResize={handleOverlayResize}
>
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
top: 0,
}}
bg="overlay"
/>
</ReactResizeDetector>
)}
<ReactResizeDetector
handleWidth
handleHeight
onResize={handleContainerResize}
>
<animated.div
style={{
...openAnimation,
@@ -109,8 +81,8 @@ function TilesOverlay({ children }) {
>
<Box
sx={{
width: containerSize,
height: containerSize,
width: containerSize.width,
height: containerSize.height,
borderRadius: "8px",
border: "1px solid",
borderColor: "border",
@@ -125,8 +97,8 @@ function TilesOverlay({ children }) {
>
<SimpleBar
style={{
width: containerSize - 16,
height: containerSize - 48,
width: containerSize.width - 16,
height: containerSize.height - 48,
marginBottom: "8px",
backgroundColor: theme.colors.muted,
}}
@@ -0,0 +1,59 @@
import React from "react";
import { createPortal } from "react-dom";
import Droppable from "../drag/Droppable";
import { UNGROUP_ID_PREFIX } from "../../contexts/TileDragContext";
function TilesUngroupDroppable({ outerContainerSize, innerContainerSize }) {
const width = (outerContainerSize.width - innerContainerSize.width) / 2;
const height = (outerContainerSize.height - innerContainerSize.height) / 2;
return createPortal(
<div>
<Droppable
id={`${UNGROUP_ID_PREFIX}-1`}
style={{
width: outerContainerSize.width,
height,
position: "absolute",
top: `calc(50% - ${innerContainerSize.height / 2 + height}px)`,
left: `calc(50% - ${outerContainerSize.width / 2}px)`,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-2`}
style={{
width: outerContainerSize.width,
height,
position: "absolute",
top: `calc(50% + ${innerContainerSize.height / 2}px)`,
left: `calc(50% - ${outerContainerSize.width / 2}px)`,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-3`}
style={{
width,
height: outerContainerSize.height,
position: "absolute",
top: `calc(50% - ${outerContainerSize.height / 2}px)`,
left: `calc(50% - ${innerContainerSize.width / 2 + width}px)`,
}}
/>
<Droppable
id={`${UNGROUP_ID_PREFIX}-4`}
style={{
width,
height: outerContainerSize.height,
position: "absolute",
top: `calc(50% - ${outerContainerSize.height / 2}px)`,
left: `calc(50% + ${innerContainerSize.width / 2}px)`,
}}
/>
</div>,
document.body
);
}
export default TilesUngroupDroppable;
+2 -1
View File
@@ -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}
/>
</>
);
+8 -28
View File
@@ -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",
}}
>
<SelectTokensButton />
<SelectTokensButton onMapTokenStateCreate={onMapTokenStateCreate} />
</Flex>
{createPortal(
<DragOverlay