Add ability to drag and drop a tile onto the map and added better drag cursors
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -24,7 +24,6 @@ function Tile({
|
||||
borderRadius: "4px",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
overflow: "hidden",
|
||||
userSelect: "none",
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user