Move interaction to left click and added middle click move right click pointer

This commit is contained in:
Mitchell McCaffrey 2021-09-02 09:28:45 +10:00
parent 659d3dd9e1
commit 5e407ba72d
14 changed files with 236 additions and 64 deletions

View File

@ -67,6 +67,7 @@
"socket.io-msgpack-parser": "^3.0.1",
"source-map-explorer": "^2.5.2",
"theme-ui": "^0.10.0",
"tiny-typed-emitter": "^2.1.0",
"use-image": "^1.0.8",
"uuid": "^8.3.2",
"webrtc-adapter": "^8.1.0"

View File

@ -102,6 +102,9 @@ function Note({
}
function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (event.evt.button !== 0) {
return;
}
if (draggable) {
const noteNode = event.target;
onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode, true);
@ -111,6 +114,9 @@ function Note({
// Store note pointer down time to check for a click when note is locked
const notePointerDownTimeRef = useRef<number>(0);
function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
if (event.evt.button !== 0) {
return;
}
if (draggable) {
setPreventMapInteraction(true);
}
@ -120,6 +126,9 @@ function Note({
}
function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
if (event.evt.button !== 0) {
return;
}
if (draggable) {
setPreventMapInteraction(false);
}

View File

@ -197,7 +197,10 @@ function Token({
setAttachmentOverCharacter(false);
}
function handleClick() {
function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (event.evt.button !== 0) {
return;
}
if (selectable && draggable && transformRootRef.current) {
onTokenMenuOpen(tokenState.id, transformRootRef.current, true);
}
@ -207,6 +210,9 @@ function Token({
// Store token pointer down time to check for a click when token is locked
const tokenPointerDownTimeRef = useRef<number>(0);
function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
if (event.evt.button !== 0) {
return;
}
if (draggable) {
setPreventMapInteraction(true);
}
@ -216,6 +222,9 @@ function Token({
}
function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
if (event.evt.button !== 0) {
return;
}
if (draggable) {
setPreventMapInteraction(false);
}

View File

@ -3,17 +3,20 @@ import { Box } from "theme-ui";
import ReactResizeDetector from "react-resize-detector";
import { Stage, Layer, Image, Group } from "react-konva";
import Konva from "konva";
import { EventEmitter } from "events";
import useMapImage from "../../hooks/useMapImage";
import usePreventOverscroll from "../../hooks/usePreventOverscroll";
import useStageInteraction from "../../hooks/useStageInteraction";
import useImageCenter from "../../hooks/useImageCenter";
import usePreventContextMenu from "../../hooks/usePreventContextMenu";
import { getGridMaxZoom } from "../../helpers/grid";
import KonvaBridge from "../../helpers/KonvaBridge";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
import {
MapInteractionEmitter,
MapInteractionProvider,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import { GridProvider } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext";
@ -72,6 +75,7 @@ function MapInteraction({
const containerRef = useRef<HTMLDivElement>(null);
usePreventOverscroll(containerRef);
usePreventContextMenu(containerRef);
const [mapWidth, mapHeight] = useImageCenter(
map,
@ -85,8 +89,9 @@ function MapInteraction({
);
const previousSelectedToolRef = useRef(selectedToolId);
const [currentMouseButtons, setCurentMouseButtons] = useState(0);
const [interactionEmitter] = useState(new EventEmitter());
const [interactionEmitter] = useState(new MapInteractionEmitter());
useStageInteraction(
mapStageRef,
@ -106,13 +111,17 @@ function MapInteraction({
onPinchEnd: () => {
onSelectedToolChange(previousSelectedToolRef.current);
},
onDrag: ({ first, last }) => {
onDrag: (props) => {
const { first, last, buttons } = props;
if (buttons !== currentMouseButtons) {
setCurentMouseButtons(buttons);
}
if (first) {
interactionEmitter.emit("dragStart");
interactionEmitter.emit("dragStart", props);
} else if (last) {
interactionEmitter.emit("dragEnd");
interactionEmitter.emit("dragEnd", props);
} else {
interactionEmitter.emit("drag");
interactionEmitter.emit("drag", props);
}
},
}
@ -143,6 +152,11 @@ function MapInteraction({
useKeyboard(handleKeyDown, handleKeyUp);
function getCursorForTool(tool: MapToolId) {
if (currentMouseButtons === 2) {
return "crosshair";
} else if (currentMouseButtons > 2) {
return "move";
}
switch (tool) {
case "move":
return "move";

View File

@ -7,6 +7,8 @@ import {
useMapWidth,
useMapHeight,
useInteractionEmitter,
leftMouseButton,
MapDragEvent,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import {
@ -103,7 +105,10 @@ function DrawingTool({
});
}
function handleBrushDown() {
function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition) {
return;
@ -135,7 +140,10 @@ function DrawingTool({
setIsBrushDown(true);
}
function handleBrushMove() {
function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition) {
return;
@ -186,7 +194,10 @@ function DrawingTool({
}
}
function handleBrushUp() {
function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (isBrush && drawing && drawing.type === "path") {
if (drawing.data.points.length > 1) {
onShapeAdd(drawing);

View File

@ -3,6 +3,7 @@ import shortid from "shortid";
import { Group, Line } from "react-konva";
import useImage from "use-image";
import Color from "color";
import Konva from "konva";
import diagonalPattern from "../../images/DiagonalPattern.png";
@ -11,6 +12,8 @@ import {
useMapWidth,
useMapHeight,
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import {
@ -160,7 +163,10 @@ function FogTool({
});
}
function handleBrushDown() {
function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (toolSettings.type === "brush") {
const brushPosition = getBrushPosition();
if (!brushPosition) {
@ -203,7 +209,10 @@ function FogTool({
setIsBrushDown(true);
}
function handleBrushMove() {
function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (toolSettings.type === "brush" && isBrushDown && drawingShape) {
const brushPosition = getBrushPosition();
if (!brushPosition) {
@ -258,7 +267,10 @@ function FogTool({
}
}
function handleBrushUp() {
function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (
(toolSettings.type === "brush" || toolSettings.type === "rectangle") &&
drawingShape
@ -318,7 +330,13 @@ function FogTool({
setIsBrushDown(false);
}
function handlePointerClick() {
function handlePointerClick(
event: Konva.KonvaEventObject<MouseEvent | TouchEvent>
) {
// Left click only
if (event.evt instanceof MouseEvent && event.evt.button !== 0) {
return;
}
if (toolSettings.type === "polygon") {
const brushPosition = getBrushPosition();
if (brushPosition) {

View File

@ -1,7 +1,11 @@
import { useState, useEffect } from "react";
import { Group } from "react-konva";
import { useInteractionEmitter } from "../../contexts/MapInteractionContext";
import {
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import {
useGrid,
@ -40,8 +44,9 @@ function MeasureTool({ map, active }: MapMeasureProps) {
const gridOffset = useGridOffset();
const mapStageRef = useMapStage();
const [drawingShapeData, setDrawingShapeData] =
useState<MeasureData | null>(null);
const [drawingShapeData, setDrawingShapeData] = useState<MeasureData | null>(
null
);
const [isBrushDown, setIsBrushDown] = useState(false);
const gridScale = parseGridScale(active ? grid.measurement.scale : null);
@ -75,7 +80,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
});
}
function handleBrushDown() {
function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition) {
return;
@ -89,7 +97,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
setIsBrushDown(true);
}
function handleBrushMove() {
function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (isBrushDown && drawingShapeData && brushPosition && mapImage) {
const { points } = getUpdatedShapeData(
@ -123,7 +134,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
}
}
function handleBrushUp() {
function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
setDrawingShapeData(null);
setIsBrushDown(false);
}

View File

@ -3,7 +3,11 @@ import shortid from "shortid";
import { Group } from "react-konva";
import Konva from "konva";
import { useInteractionEmitter } from "../../contexts/MapInteractionContext";
import {
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import { useUserId } from "../../contexts/UserIdContext";
@ -72,7 +76,10 @@ function NoteTool({
});
}
function handleBrushDown() {
function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition || !userId) {
return;
@ -94,7 +101,10 @@ function NoteTool({
setIsBrushDown(true);
}
function handleBrushMove() {
function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (noteData) {
const brushPosition = getBrushPosition();
if (!brushPosition) {
@ -114,7 +124,10 @@ function NoteTool({
}
}
function handleBrushUp() {
function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (noteData && creatingNoteRef.current) {
onNoteCreate([noteData]);
onNoteMenuOpen(noteData.id, creatingNoteRef.current, true);

View File

@ -1,10 +1,13 @@
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { Group } from "react-konva";
import {
useMapWidth,
useMapHeight,
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
rightMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import { useGridStrokeWidth } from "../../contexts/GridContext";
@ -41,11 +44,9 @@ function PointerTool({
const gridStrokeWidth = useGridStrokeWidth();
const mapStageRef = useMapStage();
useEffect(() => {
if (!active) {
return;
}
const brushDownRef = useRef(false);
useEffect(() => {
const mapStage = mapStageRef.current;
function getBrushPosition() {
@ -56,19 +57,27 @@ function PointerTool({
return getRelativePointerPositionNormalized(mapImage);
}
function handleBrushDown() {
const brushPosition = getBrushPosition();
brushPosition && onPointerDown?.(brushPosition);
function handleBrushDown(props: MapDragEvent) {
if ((leftMouseButton(props) && active) || rightMouseButton(props)) {
const brushPosition = getBrushPosition();
brushPosition && onPointerDown?.(brushPosition);
brushDownRef.current = true;
}
}
function handleBrushMove() {
const brushPosition = getBrushPosition();
brushPosition && visible && onPointerMove?.(brushPosition);
if (brushDownRef.current) {
const brushPosition = getBrushPosition();
brushPosition && visible && onPointerMove?.(brushPosition);
}
}
function handleBrushUp() {
const brushPosition = getBrushPosition();
brushPosition && onPointerUp?.(brushPosition);
if (brushDownRef.current) {
const brushPosition = getBrushPosition();
brushPosition && onPointerUp?.(brushPosition);
brushDownRef.current = false;
}
}
interactionEmitter?.on("dragStart", handleBrushDown);

View File

@ -7,6 +7,8 @@ import {
useMapWidth,
useMapHeight,
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
@ -96,7 +98,10 @@ function SelectTool({
});
}
function handleBrushDown() {
function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition || preventSelectionRef.current) {
return;
@ -121,7 +126,10 @@ function SelectTool({
setIsBrushDown(true);
}
function handleBrushMove() {
function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition();
if (!brushPosition || preventSelectionRef.current) {
return;
@ -172,7 +180,10 @@ function SelectTool({
}
}
function handleBrushUp() {
function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (preventSelectionRef.current) {
return;
}

View File

@ -1,6 +1,21 @@
import React, { useContext } from "react";
import { EventEmitter } from "stream";
import { FullGestureState } from "react-use-gesture/dist/types";
import useDebounce from "../hooks/useDebounce";
import { TypedEmitter } from "tiny-typed-emitter";
export type MapDragEvent = Omit<FullGestureState<"drag">, "event"> & {
event: React.PointerEvent<Element> | PointerEvent;
};
export type MapDragEventHandler = (props: MapDragEvent) => void;
export interface MapInteractionEvents {
dragStart: MapDragEventHandler;
drag: MapDragEventHandler;
dragEnd: MapDragEventHandler;
}
export class MapInteractionEmitter extends TypedEmitter<MapInteractionEvents> {}
type MapInteraction = {
stageScale: number;
@ -9,29 +24,33 @@ type MapInteraction = {
setPreventMapInteraction: React.Dispatch<React.SetStateAction<boolean>>;
mapWidth: number;
mapHeight: number;
interactionEmitter: EventEmitter | null;
interactionEmitter: MapInteractionEmitter | null;
};
export const StageScaleContext =
React.createContext<MapInteraction["stageScale"] | undefined>(undefined);
export const DebouncedStageScaleContext =
React.createContext<MapInteraction["stageScale"] | undefined>(undefined);
export const StageWidthContext =
React.createContext<MapInteraction["stageWidth"] | undefined>(undefined);
export const StageHeightContext =
React.createContext<MapInteraction["stageHeight"] | undefined>(undefined);
export const SetPreventMapInteractionContext =
React.createContext<MapInteraction["setPreventMapInteraction"] | undefined>(
undefined
);
export const MapWidthContext =
React.createContext<MapInteraction["mapWidth"] | undefined>(undefined);
export const MapHeightContext =
React.createContext<MapInteraction["mapHeight"] | undefined>(undefined);
export const InteractionEmitterContext =
React.createContext<MapInteraction["interactionEmitter"] | undefined>(
undefined
);
export const StageScaleContext = React.createContext<
MapInteraction["stageScale"] | undefined
>(undefined);
export const DebouncedStageScaleContext = React.createContext<
MapInteraction["stageScale"] | undefined
>(undefined);
export const StageWidthContext = React.createContext<
MapInteraction["stageWidth"] | undefined
>(undefined);
export const StageHeightContext = React.createContext<
MapInteraction["stageHeight"] | undefined
>(undefined);
export const SetPreventMapInteractionContext = React.createContext<
MapInteraction["setPreventMapInteraction"] | undefined
>(undefined);
export const MapWidthContext = React.createContext<
MapInteraction["mapWidth"] | undefined
>(undefined);
export const MapHeightContext = React.createContext<
MapInteraction["mapHeight"] | undefined
>(undefined);
export const InteractionEmitterContext = React.createContext<
MapInteraction["interactionEmitter"] | undefined
>(undefined);
export function MapInteractionProvider({
value,
@ -152,3 +171,15 @@ export function useDebouncedStageScale() {
}
return context;
}
export function leftMouseButton(event: MapDragEvent) {
return event.buttons <= 1;
}
export function middleMouseButton(event: MapDragEvent) {
return event.buttons === 4;
}
export function rightMouseButton(event: MapDragEvent) {
return event.buttons === 2;
}

View File

@ -0,0 +1,25 @@
import React, { useEffect } from "react";
function usePreventContextMenu(elementRef: React.RefObject<HTMLElement>) {
useEffect(() => {
// Stop conext menu i.e. right click dialog
function preventContextMenu(event: MouseEvent) {
event.preventDefault();
return false;
}
const element = elementRef.current;
if (element) {
element.addEventListener("contextmenu", preventContextMenu, {
passive: false,
});
}
return () => {
if (element) {
element.removeEventListener("contextmenu", preventContextMenu);
}
};
}, [elementRef]);
}
export default usePreventContextMenu;

View File

@ -67,6 +67,7 @@ function useStageInteraction(
return;
}
const { event, last } = props;
// Prevent double zoom on wheel end
if (!last) {
const { pixelY } = normalizeWheel(event);
@ -178,7 +179,7 @@ function useStageInteraction(
gesture.onDragStart && gesture.onDragStart(props);
},
onDrag: (props) => {
const { delta, pinching } = props;
const { delta, pinching, buttons } = props;
const stage = stageRef.current;
if (
preventInteraction ||
@ -191,7 +192,8 @@ function useStageInteraction(
const [dx, dy] = delta;
const stageTranslate = stageTranslateRef.current;
if (tool === "move") {
// Move with move tool and left click or any mouse button but right click
if ((tool === "move" && buttons < 2) || buttons > 2) {
const newTranslate = {
x: stageTranslate.x + dx,
y: stageTranslate.y + dy,

View File

@ -13181,6 +13181,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-typed-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5"
integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"