diff --git a/src/components/DragOverlay.js b/src/components/DragOverlay.js
new file mode 100644
index 0000000..18678e7
--- /dev/null
+++ b/src/components/DragOverlay.js
@@ -0,0 +1,86 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Box, IconButton } from "theme-ui";
+
+import RemoveTokenIcon from "../icons/RemoveTokenIcon";
+
+function DragOverlay({ dragging, node, onRemove }) {
+ const [isRemoveHovered, setIsRemoveHovered] = useState(false);
+ const removeTokenRef = useRef();
+
+ // Detect token hover on remove icon manually to support touch devices
+ useEffect(() => {
+ const map = document.querySelector(".map");
+ const mapRect = map.getBoundingClientRect();
+
+ function detectRemoveHover() {
+ if (!node || !dragging || !removeTokenRef.current) {
+ return;
+ }
+
+ const pointerPosition = node.getStage().getPointerPosition();
+ const screenSpacePointerPosition = {
+ x: pointerPosition.x + mapRect.left,
+ y: pointerPosition.y + mapRect.top,
+ };
+ const removeIconPosition = removeTokenRef.current.getBoundingClientRect();
+
+ if (
+ screenSpacePointerPosition.x > removeIconPosition.left &&
+ screenSpacePointerPosition.y > removeIconPosition.top &&
+ screenSpacePointerPosition.x < removeIconPosition.right &&
+ screenSpacePointerPosition.y < removeIconPosition.bottom
+ ) {
+ if (!isRemoveHovered) {
+ setIsRemoveHovered(true);
+ }
+ } else if (isRemoveHovered) {
+ setIsRemoveHovered(false);
+ }
+ }
+
+ let handler;
+ if (node && dragging) {
+ handler = setInterval(detectRemoveHover, 100);
+ }
+
+ return () => {
+ if (handler) {
+ clearInterval(handler);
+ }
+ };
+ }, [isRemoveHovered, dragging, node]);
+
+ // Detect drag end of token image and remove it if it is over the remove icon
+ useEffect(() => {
+ if (!dragging && node && isRemoveHovered) {
+ onRemove();
+ }
+ });
+
+ return (
+ dragging && (
+
+
+
+
+
+ )
+ );
+}
+
+export default DragOverlay;
diff --git a/src/components/map/Map.js b/src/components/map/Map.js
index 0bca52e..29467db 100644
--- a/src/components/map/Map.js
+++ b/src/components/map/Map.js
@@ -17,9 +17,10 @@ import SettingsContext from "../../contexts/SettingsContext";
import TokenMenu from "../token/TokenMenu";
import TokenDragOverlay from "../token/TokenDragOverlay";
+import NoteMenu from "../note/NoteMenu";
+import NoteDragOverlay from "../note/NoteDragOverlay";
import { drawActionsToShapes } from "../../helpers/drawing";
-import MapNoteMenu from "./MapNoteMenu";
function Map({
map,
@@ -35,6 +36,7 @@ function Map({
onFogDrawUndo,
onFogDrawRedo,
onMapNoteChange,
+ onMapNoteRemove,
allowMapDrawing,
allowFogDrawing,
allowMapChange,
@@ -186,7 +188,7 @@ function Map({
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState(false);
const [tokenMenuOptions, setTokenMenuOptions] = useState({});
- const [draggingTokenOptions, setDraggingTokenOptions] = useState();
+ const [tokenDraggingOptions, setTokenDraggingOptions] = useState();
function handleTokenMenuOpen(tokenStateId, tokenImage) {
setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true);
@@ -245,7 +247,7 @@ function Map({
const mapTokens = map && mapState && (
{Object.values(mapState.tokens)
- .sort((a, b) => sortMapTokenStates(a, b, draggingTokenOptions))
+ .sort((a, b) => sortMapTokenStates(a, b, tokenDraggingOptions))
.map((tokenState) => (
- setDraggingTokenOptions({
+ setTokenDraggingOptions({
dragging: true,
tokenState,
tokenGroup: e.target,
})
}
onTokenDragEnd={() =>
- setDraggingTokenOptions({
- ...draggingTokenOptions,
+ setTokenDraggingOptions({
+ ...tokenDraggingOptions,
dragging: false,
})
}
@@ -291,17 +293,17 @@ function Map({
/>
);
- const tokenDragOverlay = draggingTokenOptions && (
+ const tokenDragOverlay = tokenDraggingOptions && (
{
onMapTokenStateRemove(state);
- setDraggingTokenOptions(null);
+ setTokenDraggingOptions(null);
}}
onTokenStateChange={onMapTokenStateChange}
- tokenState={draggingTokenOptions && draggingTokenOptions.tokenState}
- tokenGroup={draggingTokenOptions && draggingTokenOptions.tokenGroup}
- dragging={draggingTokenOptions && draggingTokenOptions.dragging}
- token={tokensById[draggingTokenOptions.tokenState.tokenId]}
+ tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
+ tokenGroup={tokenDraggingOptions && tokenDraggingOptions.tokenGroup}
+ dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
+ token={tokensById[tokenDraggingOptions.tokenState.tokenId]}
mapState={mapState}
/>
);
@@ -354,6 +356,7 @@ function Map({
const [isNoteMenuOpen, setIsNoteMenuOpen] = useState(false);
const [noteMenuOptions, setNoteMenuOptions] = useState({});
+ const [noteDraggingOptions, setNoteDraggingOptions] = useState();
function handleNoteMenuOpen(noteId, noteNode) {
setNoteMenuOptions({ noteId, noteNode });
setIsNoteMenuOpen(true);
@@ -371,11 +374,17 @@ function Map({
notes={mapState ? Object.values(mapState.notes) : []}
onNoteMenuOpen={handleNoteMenuOpen}
draggable={selectedToolId === "note" || selectedToolId === "pan"}
+ onNoteDragStart={(e, noteId) =>
+ setNoteDraggingOptions({ dragging: true, noteId, noteGroup: e.target })
+ }
+ onNoteDragEnd={() =>
+ setNoteDraggingOptions({ ...noteDraggingOptions, dragging: false })
+ }
/>
);
const noteMenu = (
- setIsNoteMenuOpen(false)}
onNoteChange={onMapNoteChange}
@@ -385,6 +394,18 @@ function Map({
/>
);
+ const noteDragOverlay = (
+ {
+ onMapNoteRemove(noteId);
+ setNoteDraggingOptions(null);
+ }}
+ />
+ );
+
return (
>
}
diff --git a/src/components/map/MapNotes.js b/src/components/map/MapNotes.js
index d297cb3..fb81e21 100644
--- a/src/components/map/MapNotes.js
+++ b/src/components/map/MapNotes.js
@@ -9,7 +9,7 @@ import AuthContext from "../../contexts/AuthContext";
import { getBrushPositionForTool } from "../../helpers/drawing";
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
-import MapNote from "./MapNote";
+import Note from "../note/Note";
const defaultNoteSize = 2;
@@ -22,6 +22,8 @@ function MapNotes({
notes,
onNoteMenuOpen,
draggable,
+ onNoteDragStart,
+ onNoteDragEnd,
}) {
const { interactionEmitter } = useContext(MapInteractionContext);
const { userId } = useContext(AuthContext);
@@ -99,18 +101,20 @@ function MapNotes({
return (
{notes.map((note) => (
-
))}
{isBrushDown && noteData && (
-
+
)}
diff --git a/src/components/map/MapNote.js b/src/components/note/Note.js
similarity index 91%
rename from src/components/map/MapNote.js
rename to src/components/note/Note.js
index 0afea46..2e7de59 100644
--- a/src/components/map/MapNote.js
+++ b/src/components/note/Note.js
@@ -10,7 +10,15 @@ import colors from "../../helpers/colors";
const snappingThreshold = 1 / 5;
const textPadding = 4;
-function MapNote({ note, map, onNoteChange, onNoteMenuOpen, draggable }) {
+function Note({
+ note,
+ map,
+ onNoteChange,
+ onNoteMenuOpen,
+ draggable,
+ onNoteDragStart,
+ onNoteDragEnd,
+}) {
const { userId } = useContext(AuthContext);
const { mapWidth, mapHeight, setPreventMapInteraction } = useContext(
MapInteractionContext
@@ -26,6 +34,10 @@ function MapNote({ note, map, onNoteChange, onNoteMenuOpen, draggable }) {
}
}
+ function handleDragStart(event) {
+ onNoteDragStart && onNoteDragStart(event, note.id);
+ }
+
function handleDragMove(event) {
const noteGroup = event.target;
// Snap to corners of grid
@@ -72,15 +84,17 @@ function MapNote({ note, map, onNoteChange, onNoteMenuOpen, draggable }) {
lastModifiedBy: userId,
lastModified: Date.now(),
});
+ onNoteDragEnd && onNoteDragEnd(note.id);
+ setPreventMapInteraction(false);
}
- function handlePointerDown(event) {
+ function handlePointerDown() {
if (draggable) {
setPreventMapInteraction(true);
}
}
- function handlePointerUp(event) {
+ function handlePointerUp() {
if (draggable) {
setPreventMapInteraction(false);
}
@@ -123,6 +137,7 @@ function MapNote({ note, map, onNoteChange, onNoteMenuOpen, draggable }) {
offsetX={noteWidth / 2}
offsetY={noteHeight / 2}
draggable={draggable}
+ onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragMove={handleDragMove}
onMouseDown={handlePointerDown}
@@ -157,4 +172,4 @@ function MapNote({ note, map, onNoteChange, onNoteMenuOpen, draggable }) {
);
}
-export default MapNote;
+export default Note;
diff --git a/src/components/note/NoteDragOverlay.js b/src/components/note/NoteDragOverlay.js
new file mode 100644
index 0000000..9df0713
--- /dev/null
+++ b/src/components/note/NoteDragOverlay.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+import DragOverlay from "../DragOverlay";
+
+function NoteDragOverlay({ onNoteRemove, noteId, noteGroup, dragging }) {
+ function handleNoteRemove() {
+ onNoteRemove(noteId);
+ }
+
+ return (
+
+ );
+}
+
+export default NoteDragOverlay;
diff --git a/src/components/map/MapNoteMenu.js b/src/components/note/NoteMenu.js
similarity index 99%
rename from src/components/map/MapNoteMenu.js
rename to src/components/note/NoteMenu.js
index 7781b92..783025d 100644
--- a/src/components/map/MapNoteMenu.js
+++ b/src/components/note/NoteMenu.js
@@ -16,7 +16,7 @@ import AuthContext from "../../contexts/AuthContext";
const defaultNoteMaxSize = 6;
-function MapNoteMenu({
+function NoteMenu({
isOpen,
onRequestClose,
note,
@@ -205,4 +205,4 @@ function MapNoteMenu({
);
}
-export default MapNoteMenu;
+export default NoteMenu;
diff --git a/src/components/token/TokenDragOverlay.js b/src/components/token/TokenDragOverlay.js
index 68b9d29..4d90e9a 100644
--- a/src/components/token/TokenDragOverlay.js
+++ b/src/components/token/TokenDragOverlay.js
@@ -1,11 +1,10 @@
-import React, { useContext, useEffect, useRef, useState } from "react";
-import { Box, IconButton } from "theme-ui";
-
-import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
+import React, { useContext } from "react";
import AuthContext from "../../contexts/AuthContext";
import MapInteractionContext from "../../contexts/MapInteractionContext";
+import DragOverlay from "../DragOverlay";
+
function TokenDragOverlay({
onTokenStateRemove,
onTokenStateChange,
@@ -16,114 +15,38 @@ function TokenDragOverlay({
mapState,
}) {
const { userId } = useContext(AuthContext);
- const { setPreventMapInteraction, mapWidth, mapHeight } = useContext(
- MapInteractionContext
- );
+ const { mapWidth, mapHeight } = useContext(MapInteractionContext);
- const [isRemoveHovered, setIsRemoveHovered] = useState(false);
- const removeTokenRef = useRef();
-
- // Detect token hover on remove icon manually to support touch devices
- useEffect(() => {
- const map = document.querySelector(".map");
- const mapRect = map.getBoundingClientRect();
-
- function detectRemoveHover() {
- if (!tokenGroup) {
- return;
- }
-
- const pointerPosition = tokenGroup.getStage().getPointerPosition();
- const screenSpacePointerPosition = {
- x: pointerPosition.x + mapRect.left,
- y: pointerPosition.y + mapRect.top,
- };
- if (!removeTokenRef.current) {
- return;
- }
- const removeIconPosition = removeTokenRef.current.getBoundingClientRect();
-
- if (
- screenSpacePointerPosition.x > removeIconPosition.left &&
- screenSpacePointerPosition.y > removeIconPosition.top &&
- screenSpacePointerPosition.x < removeIconPosition.right &&
- screenSpacePointerPosition.y < removeIconPosition.bottom
- ) {
- if (!isRemoveHovered) {
- setIsRemoveHovered(true);
- }
- } else if (isRemoveHovered) {
- setIsRemoveHovered(false);
+ function handleTokenRemove() {
+ // Handle other tokens when a vehicle gets deleted
+ if (token && token.category === "vehicle") {
+ const layer = tokenGroup.getLayer();
+ const mountedTokens = tokenGroup.find(".token");
+ for (let mountedToken of mountedTokens) {
+ // Save and restore token position after moving layer
+ const position = mountedToken.absolutePosition();
+ mountedToken.moveTo(layer);
+ mountedToken.absolutePosition(position);
+ onTokenStateChange({
+ [mountedToken.id()]: {
+ ...mapState.tokens[mountedToken.id()],
+ x: mountedToken.x() / mapWidth,
+ y: mountedToken.y() / mapHeight,
+ lastModifiedBy: userId,
+ lastModified: Date.now(),
+ },
+ });
}
}
-
- let handler;
- if (tokenState && tokenGroup && dragging) {
- handler = setInterval(detectRemoveHover, 100);
- }
-
- return () => {
- if (handler) {
- clearInterval(handler);
- }
- };
- }, [tokenState, tokenGroup, isRemoveHovered, dragging]);
-
- // Detect drag end of token image and remove it if it is over the remove icon
- useEffect(() => {
- function handleTokenDragEnd() {
- // Handle other tokens when a vehicle gets deleted
- if (token && token.category === "vehicle") {
- const layer = tokenGroup.getLayer();
- const mountedTokens = tokenGroup.find(".token");
- for (let mountedToken of mountedTokens) {
- // Save and restore token position after moving layer
- const position = mountedToken.absolutePosition();
- mountedToken.moveTo(layer);
- mountedToken.absolutePosition(position);
- onTokenStateChange({
- [mountedToken.id()]: {
- ...mapState.tokens[mountedToken.id()],
- x: mountedToken.x() / mapWidth,
- y: mountedToken.y() / mapHeight,
- lastModifiedBy: userId,
- lastModified: Date.now(),
- },
- });
- }
- }
- onTokenStateRemove(tokenState);
- setPreventMapInteraction(false);
- }
-
- if (!dragging && tokenState && isRemoveHovered) {
- handleTokenDragEnd();
- }
- });
+ onTokenStateRemove(tokenState);
+ }
return (
- dragging && (
-
-
-
-
-
- )
+
);
}
diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.js
index 6090a72..9d22be7 100644
--- a/src/network/NetworkedMapAndTokens.js
+++ b/src/network/NetworkedMapAndTokens.js
@@ -178,6 +178,14 @@ function NetworkedMapAndTokens({ session }) {
session.send("mapNoteChange", note);
}
+ function handleNoteRemove(noteId) {
+ setCurrentMapState((prevMapState) => ({
+ ...prevMapState,
+ notes: omit(prevMapState.notes, [noteId]),
+ }));
+ session.send("mapNoteRemove", noteId);
+ }
+
/**
* Token state
*/
@@ -415,6 +423,12 @@ function NetworkedMapAndTokens({ session }) {
},
}));
}
+ if (id === "mapNoteRemove" && currentMapState) {
+ setCurrentMapState((prevMapState) => ({
+ ...prevMapState,
+ notes: omit(prevMapState.notes, [data]),
+ }));
+ }
}
function handlePeerDataProgress({ id, total, count }) {
@@ -481,6 +495,7 @@ function NetworkedMapAndTokens({ session }) {
onFogDrawUndo={handleFogDrawUndo}
onFogDrawRedo={handleFogDrawRedo}
onMapNoteChange={handleNoteChange}
+ onMapNoteRemove={handleNoteRemove}
allowMapDrawing={canEditMapDrawing}
allowFogDrawing={canEditFogDrawing}
allowMapChange={canChangeMap}