Fix tile drag cancel with modal open

This commit is contained in:
Mitchell McCaffrey 2021-06-09 10:33:47 +10:00
parent 63f77059f1
commit a8c355f251
4 changed files with 109 additions and 58 deletions

View File

@ -6,6 +6,7 @@ import {
DragOverlay, DragOverlay,
DndContext, DndContext,
PointerSensor, PointerSensor,
KeyboardSensor,
useSensor, useSensor,
useSensors, useSensors,
} from "@dnd-kit/core"; } from "@dnd-kit/core";
@ -45,7 +46,8 @@ function TokenBar({ onMapTokensStateCreate }) {
const pointerSensor = useSensor(PointerSensor, { const pointerSensor = useSensor(PointerSensor, {
activationConstraint: { distance: 5 }, activationConstraint: { distance: 5 },
}); });
const sensors = useSensors(pointerSensor); const keyboardSensor = useSensor(KeyboardSensor);
const sensors = useSensors(pointerSensor, keyboardSensor);
function handleDragStart({ active }) { function handleDragStart({ active }) {
setDragId(active.id); setDragId(active.id);
@ -93,6 +95,10 @@ function TokenBar({ onMapTokensStateCreate }) {
} }
} }
function handleDragCancel() {
setDragId(null);
}
function renderToken(group, draggable = true) { function renderToken(group, draggable = true) {
if (group.type === "item") { if (group.type === "item") {
const token = tokensById[group.id]; const token = tokensById[group.id];
@ -132,6 +138,7 @@ function TokenBar({ onMapTokensStateCreate }) {
<DndContext <DndContext
onDragStart={handleDragStart} onDragStart={handleDragStart}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
autoScroll={false} autoScroll={false}
sensors={sensors} sensors={sensors}
> >

View File

@ -1,8 +1,8 @@
import React, { useState, useContext } from "react"; import React, { useState, useContext } from "react";
import { import {
DndContext, DndContext,
MouseSensor, PointerSensor,
TouchSensor, KeyboardSensor,
useSensor, useSensor,
useSensors, useSensors,
closestCenter, closestCenter,
@ -38,7 +38,13 @@ function rectIntersection(rects, point) {
return null; return null;
} }
export function TileDragProvider({ onDragAdd, children }) { export function TileDragProvider({
onDragAdd,
onDragStart,
onDragEnd,
onDragCancel,
children,
}) {
const { const {
groups, groups,
activeGroups, activeGroups,
@ -50,29 +56,32 @@ export function TileDragProvider({ onDragAdd, children }) {
filter, filter,
} = useGroup(); } = useGroup();
const mouseSensor = useSensor(MouseSensor, { const pointerSensor = useSensor(PointerSensor, {
activationConstraint: { delay: 250, tolerance: 5 },
});
const touchSensor = useSensor(TouchSensor, {
activationConstraint: { delay: 250, tolerance: 5 }, activationConstraint: { delay: 250, tolerance: 5 },
}); });
const keyboardSensor = useSensor(KeyboardSensor);
const sensors = useSensors(mouseSensor, touchSensor); const sensors = useSensors(pointerSensor, keyboardSensor);
const [dragId, setDragId] = useState(); const [dragId, setDragId] = useState();
const [overId, setOverId] = useState(); const [overId, setOverId] = useState();
const [dragCursor, setDragCursor] = useState("pointer"); const [dragCursor, setDragCursor] = useState("pointer");
function handleDragStart({ active, over }) { function handleDragStart(event) {
const { active, over } = event;
setDragId(active.id); setDragId(active.id);
setOverId(over?.id); setOverId(over?.id);
if (!selectedGroupIds.includes(active.id)) { if (!selectedGroupIds.includes(active.id)) {
onGroupSelect(active.id); onGroupSelect(active.id);
} }
setDragCursor("grabbing"); setDragCursor("grabbing");
onDragStart && onDragStart(event);
} }
function handleDragOver({ over }) { function handleDragOver(event) {
const { over } = event;
setOverId(over?.id); setOverId(over?.id);
if (over) { if (over) {
if ( if (
@ -88,56 +97,64 @@ export function TileDragProvider({ onDragAdd, children }) {
} }
} }
function handleDragEnd({ active, over }) { function handleDragEnd(event) {
const { active, over } = event;
setDragId(); setDragId();
setOverId(); setOverId();
setDragCursor("pointer"); setDragCursor("pointer");
if (!active || !over || active.id === over.id) { if (active && over && active.id !== over.id) {
return; let selectedIndices = selectedGroupIds.map((groupId) =>
activeGroups.findIndex((group) => group.id === groupId)
);
// Maintain current group sorting
selectedIndices = selectedIndices.sort((a, b) => a - b);
if (over.id.startsWith(GROUP_ID_PREFIX)) {
onGroupSelect();
// Handle tile group
const overId = over.id.slice(9);
if (overId !== active.id) {
const overGroupIndex = activeGroups.findIndex(
(group) => group.id === overId
);
onGroupsChange(
moveGroupsInto(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
}
} else if (over.id === UNGROUP_ID) {
onGroupSelect();
// Handle tile ungroup
const newGroups = ungroup(groups, openGroupId, selectedIndices);
// Close group if it was removed
if (!newGroups.find((group) => group.id === openGroupId)) {
onGroupClose();
}
onGroupsChange(newGroups);
} else if (over.id === ADD_TO_MAP_ID) {
onDragAdd && onDragAdd(selectedGroupIds, over.rect);
} else if (!filter) {
// Hanlde tile move only if we have no filter
const overGroupIndex = activeGroups.findIndex(
(group) => group.id === over.id
);
onGroupsChange(
moveGroups(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
}
} }
let selectedIndices = selectedGroupIds.map((groupId) => onDragEnd && onDragEnd(event);
activeGroups.findIndex((group) => group.id === groupId) }
);
// Maintain current group sorting
selectedIndices = selectedIndices.sort((a, b) => a - b);
if (over.id.startsWith(GROUP_ID_PREFIX)) { function handleDragCancel(event) {
onGroupSelect(); setDragId();
// Handle tile group setOverId();
const overId = over.id.slice(9); setDragCursor("pointer");
if (overId === active.id) {
return;
}
const overGroupIndex = activeGroups.findIndex( onDragCancel && onDragCancel(event);
(group) => group.id === overId
);
onGroupsChange(
moveGroupsInto(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
} else if (over.id === UNGROUP_ID) {
onGroupSelect();
// Handle tile ungroup
const newGroups = ungroup(groups, openGroupId, selectedIndices);
// Close group if it was removed
if (!newGroups.find((group) => group.id === openGroupId)) {
onGroupClose();
}
onGroupsChange(newGroups);
} else if (over.id === ADD_TO_MAP_ID) {
onDragAdd && onDragAdd(selectedGroupIds, over.rect);
} else if (!filter) {
// Hanlde tile move only if we have no filter
const overGroupIndex = activeGroups.findIndex(
(group) => group.id === over.id
);
onGroupsChange(
moveGroups(activeGroups, overGroupIndex, selectedIndices),
openGroupId
);
}
} }
function customCollisionDetection(rects, rect) { function customCollisionDetection(rects, rect) {
@ -183,6 +200,7 @@ export function TileDragProvider({ onDragAdd, children }) {
onDragStart={handleDragStart} onDragStart={handleDragStart}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragCancel={handleDragCancel}
sensors={sensors} sensors={sensors}
collisionDetection={customCollisionDetection} collisionDetection={customCollisionDetection}
> >

View File

@ -168,6 +168,8 @@ function SelectMapModal({
const [editingMapId, setEditingMapId] = useState(); const [editingMapId, setEditingMapId] = useState();
const [isDraggingMap, setIsDraggingMap] = useState(false);
const [canAddDraggedMap, setCanAddDraggedMap] = useState(false); const [canAddDraggedMap, setCanAddDraggedMap] = useState(false);
function handleGroupsSelect(groupIds) { function handleGroupsSelect(groupIds) {
if (groupIds.length === 1) { if (groupIds.length === 1) {
@ -197,6 +199,7 @@ function SelectMapModal({
isOpen={isOpen} isOpen={isOpen}
onRequestClose={handleClose} onRequestClose={handleClose}
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }} style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
shouldCloseOnEsc={!isDraggingMap}
> >
<ImageDrop onDrop={handleImagesUpload} dropText="Drop map to import"> <ImageDrop onDrop={handleImagesUpload} dropText="Drop map to import">
<input <input
@ -229,7 +232,12 @@ function SelectMapModal({
</Label> </Label>
<TileActionBar onAdd={openImageDialog} addTitle="Import Map(s)" /> <TileActionBar onAdd={openImageDialog} addTitle="Import Map(s)" />
<Box sx={{ position: "relative" }}> <Box sx={{ position: "relative" }}>
<TileDragProvider onDragAdd={canAddDraggedMap && handleDragAdd}> <TileDragProvider
onDragAdd={canAddDraggedMap && handleDragAdd}
onDragStart={() => setIsDraggingMap(true)}
onDragEnd={() => setIsDraggingMap(false)}
onDragCancel={() => setIsDraggingMap(false)}
>
<TilesContainer> <TilesContainer>
<MapTiles <MapTiles
maps={maps} maps={maps}
@ -238,7 +246,12 @@ function SelectMapModal({
/> />
</TilesContainer> </TilesContainer>
</TileDragProvider> </TileDragProvider>
<TileDragProvider onDragAdd={canAddDraggedMap && handleDragAdd}> <TileDragProvider
onDragAdd={canAddDraggedMap && handleDragAdd}
onDragStart={() => setIsDraggingMap(true)}
onDragEnd={() => setIsDraggingMap(false)}
onDragCancel={() => setIsDraggingMap(false)}
>
<TilesOverlay modalSize={modalSize}> <TilesOverlay modalSize={modalSize}>
<MapTiles <MapTiles
maps={maps} maps={maps}

View File

@ -139,6 +139,8 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
*/ */
const [editingTokenId, setEditingTokenId] = useState(); const [editingTokenId, setEditingTokenId] = useState();
const [isDraggingToken, setIsDraggingToken] = useState(false);
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
function handleTokensAddToMap(groupIds, rect) { function handleTokensAddToMap(groupIds, rect) {
let clientPosition = new Vector2( let clientPosition = new Vector2(
@ -198,6 +200,7 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
isOpen={isOpen} isOpen={isOpen}
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }} style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
shouldCloseOnEsc={!isDraggingToken}
> >
<ImageDrop onDrop={handleImagesUpload} dropText="Drop token to import"> <ImageDrop onDrop={handleImagesUpload} dropText="Drop token to import">
<input <input
@ -232,7 +235,12 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
addTitle="Import Token(s)" addTitle="Import Token(s)"
/> />
<Box sx={{ position: "relative" }}> <Box sx={{ position: "relative" }}>
<TileDragProvider onDragAdd={handleTokensAddToMap}> <TileDragProvider
onDragAdd={handleTokensAddToMap}
onDragStart={() => setIsDraggingToken(true)}
onDragEnd={() => setIsDraggingToken(false)}
onDragCancel={() => setIsDraggingToken(false)}
>
<TilesContainer> <TilesContainer>
<TokenTiles <TokenTiles
tokens={tokens} tokens={tokens}
@ -240,7 +248,12 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
/> />
</TilesContainer> </TilesContainer>
</TileDragProvider> </TileDragProvider>
<TileDragProvider onDragAdd={handleTokensAddToMap}> <TileDragProvider
onDragAdd={handleTokensAddToMap}
onDragStart={() => setIsDraggingToken(true)}
onDragEnd={() => setIsDraggingToken(false)}
onDragCancel={() => setIsDraggingToken(false)}
>
<TilesOverlay modalSize={modalSize}> <TilesOverlay modalSize={modalSize}>
<TokenTiles <TokenTiles
tokens={tokens} tokens={tokens}