Refactored keyboard shortcuts to be global and not dependent on map interaction

This commit is contained in:
Mitchell McCaffrey 2020-09-30 13:26:39 +10:00
parent 670f047049
commit b7a89a4a4a
8 changed files with 258 additions and 252 deletions

View File

@ -15,7 +15,8 @@ import { DatabaseProvider } from "./contexts/DatabaseContext";
import { MapDataProvider } from "./contexts/MapDataContext";
import { TokenDataProvider } from "./contexts/TokenDataContext";
import { MapLoadingProvider } from "./contexts/MapLoadingContext";
import { SettingsProvider } from "./contexts/SettingsContext.js";
import { SettingsProvider } from "./contexts/SettingsContext";
import { KeyboardProvider } from "./contexts/KeyboardContext";
function App() {
return (
@ -23,34 +24,36 @@ function App() {
<DatabaseProvider>
<SettingsProvider>
<AuthProvider>
<Router>
<Switch>
<Route path="/howTo">
<HowTo />
</Route>
<Route path="/releaseNotes">
<ReleaseNotes />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/faq">
<FAQ />
</Route>
<Route path="/game/:id">
<MapLoadingProvider>
<MapDataProvider>
<TokenDataProvider>
<Game />
</TokenDataProvider>
</MapDataProvider>
</MapLoadingProvider>
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
<KeyboardProvider>
<Router>
<Switch>
<Route path="/howTo">
<HowTo />
</Route>
<Route path="/releaseNotes">
<ReleaseNotes />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/faq">
<FAQ />
</Route>
<Route path="/game/:id">
<MapLoadingProvider>
<MapDataProvider>
<TokenDataProvider>
<Game />
</TokenDataProvider>
</MapDataProvider>
</MapLoadingProvider>
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</KeyboardProvider>
</AuthProvider>
</SettingsProvider>
</DatabaseProvider>

View File

@ -20,6 +20,7 @@ import {
getRelativePointerPositionNormalized,
Tick,
} from "../../helpers/konva";
import useKeyboard from "../../helpers/useKeyboard";
function MapFog({
map,
@ -248,44 +249,37 @@ function MapFog({
}, [toolSettings, drawingShape, onShapeSubtract, onShapeAdd]);
// Add keyboard shortcuts
useEffect(() => {
function handleKeyDown({ key }) {
if (key === "Enter" && toolSettings.type === "polygon" && drawingShape) {
finishDrawingPolygon();
}
if (key === "Escape" && drawingShape) {
setDrawingShape(null);
}
if (key === "Alt" && drawingShape) {
updateShapeColor();
}
function handleKeyDown({ key }) {
if (key === "Enter" && toolSettings.type === "polygon" && drawingShape) {
finishDrawingPolygon();
}
if (key === "Escape" && drawingShape) {
setDrawingShape(null);
}
if (key === "Alt" && drawingShape) {
updateShapeColor();
}
}
function handleKeyUp({ key }) {
if (key === "Alt" && drawingShape) {
updateShapeColor();
function handleKeyUp({ key }) {
if (key === "Alt" && drawingShape) {
updateShapeColor();
}
}
function updateShapeColor() {
setDrawingShape((prevShape) => {
if (!prevShape) {
return;
}
}
return {
...prevShape,
color: toolSettings.useFogSubtract ? "black" : "red",
};
});
}
function updateShapeColor() {
setDrawingShape((prevShape) => {
if (!prevShape) {
return;
}
return {
...prevShape,
color: toolSettings.useFogSubtract ? "black" : "red",
};
});
}
interactionEmitter.on("keyDown", handleKeyDown);
interactionEmitter.on("keyUp", handleKeyUp);
return () => {
interactionEmitter.off("keyDown", handleKeyDown);
interactionEmitter.off("keyUp", handleKeyUp);
};
}, [finishDrawingPolygon, interactionEmitter, drawingShape, toolSettings]);
useKeyboard(handleKeyDown, handleKeyUp);
function handleShapeOver(shape, isDown) {
if (shouldHover && isDown) {

View File

@ -9,6 +9,7 @@ import normalizeWheel from "normalize-wheel";
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
import useDataSource from "../../helpers/useDataSource";
import useKeyboard from "../../helpers/useKeyboard";
import { mapSources as defaultMapSources } from "../../maps";
@ -18,6 +19,7 @@ import MapStageContext, {
} from "../../contexts/MapStageContext";
import AuthContext from "../../contexts/AuthContext";
import SettingsContext from "../../contexts/SettingsContext";
import KeyboardContext from "../../contexts/KeyboardContext";
const wheelZoomSpeed = -0.001;
const touchZoomSpeed = 0.005;
@ -206,88 +208,49 @@ function MapInteraction({
stageHeightRef.current = height;
}
// Added key events to interaction emitter
useEffect(() => {
function handleKeyDown(event) {
// Ignore text input
if (event.target instanceof HTMLInputElement) {
return;
}
interactionEmitter.emit("keyDown", event);
function handleKeyDown(event) {
// Change to pan tool when pressing space
if (event.key === " " && selectedToolId === "pan") {
// Stop active state on pan icon from being selected
event.preventDefault();
}
if (
event.key === " " &&
selectedToolId !== "pan" &&
!disabledControls.includes("pan")
) {
event.preventDefault();
previousSelectedToolRef.current = selectedToolId;
onSelectedToolChange("pan");
}
function handleKeyUp(event) {
// Ignore text input
if (event.target instanceof HTMLInputElement) {
return;
}
interactionEmitter.emit("keyUp", event);
// Basic keyboard shortcuts
if (event.key === "w" && !disabledControls.includes("pan")) {
onSelectedToolChange("pan");
}
document.body.addEventListener("keydown", handleKeyDown);
document.body.addEventListener("keyup", handleKeyUp);
document.body.tabIndex = 1;
return () => {
document.body.removeEventListener("keydown", handleKeyDown);
document.body.removeEventListener("keyup", handleKeyUp);
document.body.tabIndex = 0;
};
}, [interactionEmitter]);
// Create default keyboard shortcuts
useEffect(() => {
function handleKeyDown(event) {
// Change to pan tool when pressing space
if (event.key === " " && selectedToolId === "pan") {
// Stop active state on pan icon from being selected
event.preventDefault();
}
if (
event.key === " " &&
selectedToolId !== "pan" &&
!disabledControls.includes("pan")
) {
event.preventDefault();
previousSelectedToolRef.current = selectedToolId;
onSelectedToolChange("pan");
}
// Basic keyboard shortcuts
if (event.key === "w" && !disabledControls.includes("pan")) {
onSelectedToolChange("pan");
}
if (event.key === "d" && !disabledControls.includes("drawing")) {
onSelectedToolChange("drawing");
}
if (event.key === "f" && !disabledControls.includes("fog")) {
onSelectedToolChange("fog");
}
if (event.key === "m" && !disabledControls.includes("measure")) {
onSelectedToolChange("measure");
}
if (event.key === "q" && !disabledControls.includes("pointer")) {
onSelectedToolChange("pointer");
}
if (event.key === "d" && !disabledControls.includes("drawing")) {
onSelectedToolChange("drawing");
}
function handleKeyUp(event) {
if (event.key === " " && selectedToolId === "pan") {
onSelectedToolChange(previousSelectedToolRef.current);
}
if (event.key === "f" && !disabledControls.includes("fog")) {
onSelectedToolChange("fog");
}
if (event.key === "m" && !disabledControls.includes("measure")) {
onSelectedToolChange("measure");
}
if (event.key === "q" && !disabledControls.includes("pointer")) {
onSelectedToolChange("pointer");
}
}
interactionEmitter.on("keyDown", handleKeyDown);
interactionEmitter.on("keyUp", handleKeyUp);
return () => {
interactionEmitter.off("keyDown", handleKeyDown);
interactionEmitter.off("keyUp", handleKeyUp);
};
}, [
interactionEmitter,
onSelectedToolChange,
disabledControls,
selectedToolId,
]);
function handleKeyUp(event) {
if (event.key === " " && selectedToolId === "pan") {
onSelectedToolChange(previousSelectedToolRef.current);
}
}
useKeyboard(handleKeyDown, handleKeyUp);
// Get keyboard context to pass to Konva
const keyboardValue = useContext(KeyboardContext);
function getCursorForTool(tool) {
switch (tool) {
@ -360,11 +323,13 @@ function MapInteraction({
{/* Forward auth context to konva elements */}
<AuthContext.Provider value={auth}>
<SettingsContext.Provider value={settings}>
<MapInteractionProvider value={mapInteraction}>
<MapStageProvider value={mapStageRef}>
{mapLoaded && children}
</MapStageProvider>
</MapInteractionProvider>
<KeyboardContext.Provider value={keyboardValue}>
<MapInteractionProvider value={mapInteraction}>
<MapStageProvider value={mapStageRef}>
{mapLoaded && children}
</MapStageProvider>
</MapInteractionProvider>
</KeyboardContext.Provider>
</SettingsContext.Provider>
</AuthContext.Provider>
</Layer>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useContext } from "react";
import React, { useEffect } from "react";
import { Flex, IconButton } from "theme-ui";
import { useMedia } from "react-media";
@ -21,7 +21,7 @@ import RedoButton from "./RedoButton";
import Divider from "../../Divider";
import MapInteractionContext from "../../../contexts/MapInteractionContext";
import useKeyboard from "../../../helpers/useKeyboard";
function DrawingToolSettings({
settings,
@ -29,49 +29,41 @@ function DrawingToolSettings({
onToolAction,
disabledActions,
}) {
const { interactionEmitter } = useContext(MapInteractionContext);
// Keyboard shotcuts
useEffect(() => {
function handleKeyDown({ key, ctrlKey, metaKey, shiftKey }) {
if (key === "b") {
onSettingChange({ type: "brush" });
} else if (key === "p") {
onSettingChange({ type: "paint" });
} else if (key === "l") {
onSettingChange({ type: "line" });
} else if (key === "r") {
onSettingChange({ type: "rectangle" });
} else if (key === "c") {
onSettingChange({ type: "circle" });
} else if (key === "t") {
onSettingChange({ type: "triangle" });
} else if (key === "e") {
onSettingChange({ type: "erase" });
} else if (key === "o") {
onSettingChange({ useBlending: !settings.useBlending });
} else if (
(key === "z" || key === "Z") &&
(ctrlKey || metaKey) &&
shiftKey &&
!disabledActions.includes("redo")
) {
onToolAction("mapRedo");
} else if (
key === "z" &&
(ctrlKey || metaKey) &&
!shiftKey &&
!disabledActions.includes("undo")
) {
onToolAction("mapUndo");
}
function handleKeyDown({ key, ctrlKey, metaKey, shiftKey }) {
if (key === "b") {
onSettingChange({ type: "brush" });
} else if (key === "p") {
onSettingChange({ type: "paint" });
} else if (key === "l") {
onSettingChange({ type: "line" });
} else if (key === "r") {
onSettingChange({ type: "rectangle" });
} else if (key === "c") {
onSettingChange({ type: "circle" });
} else if (key === "t") {
onSettingChange({ type: "triangle" });
} else if (key === "e") {
onSettingChange({ type: "erase" });
} else if (key === "o") {
onSettingChange({ useBlending: !settings.useBlending });
} else if (
(key === "z" || key === "Z") &&
(ctrlKey || metaKey) &&
shiftKey &&
!disabledActions.includes("redo")
) {
onToolAction("mapRedo");
} else if (
key === "z" &&
(ctrlKey || metaKey) &&
!shiftKey &&
!disabledActions.includes("undo")
) {
onToolAction("mapUndo");
}
interactionEmitter.on("keyDown", handleKeyDown);
return () => {
interactionEmitter.off("keyDown", handleKeyDown);
};
});
}
useKeyboard(handleKeyDown);
// Change to brush if on erase and it gets disabled
useEffect(() => {

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect } from "react";
import React from "react";
import { Flex } from "theme-ui";
import { useMedia } from "react-media";
@ -15,11 +15,11 @@ import FogSubtractIcon from "../../../icons/FogSubtractIcon";
import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
import ToolSection from "./ToolSection";
import Divider from "../../Divider";
import MapInteractionContext from "../../../contexts/MapInteractionContext";
import ToolSection from "./ToolSection";
import useKeyboard from "../../../helpers/useKeyboard";
function BrushToolSettings({
settings,
@ -27,55 +27,46 @@ function BrushToolSettings({
onToolAction,
disabledActions,
}) {
const { interactionEmitter } = useContext(MapInteractionContext);
// Keyboard shortcuts
useEffect(() => {
function handleKeyDown({ key, ctrlKey, metaKey, shiftKey }) {
if (key === "Alt") {
onSettingChange({ useFogSubtract: !settings.useFogSubtract });
} else if (key === "p") {
onSettingChange({ type: "polygon" });
} else if (key === "b") {
onSettingChange({ type: "brush" });
} else if (key === "t") {
onSettingChange({ type: "toggle" });
} else if (key === "r") {
onSettingChange({ type: "remove" });
} else if (key === "s") {
onSettingChange({ useEdgeSnapping: !settings.useEdgeSnapping });
} else if (key === "f") {
onSettingChange({ preview: !settings.preview });
} else if (
(key === "z" || key === "Z") &&
(ctrlKey || metaKey) &&
shiftKey &&
!disabledActions.includes("redo")
) {
onToolAction("fogRedo");
} else if (
key === "z" &&
(ctrlKey || metaKey) &&
!shiftKey &&
!disabledActions.includes("undo")
) {
onToolAction("fogUndo");
}
function handleKeyDown({ key, ctrlKey, metaKey, shiftKey }) {
if (key === "Alt") {
onSettingChange({ useFogSubtract: !settings.useFogSubtract });
} else if (key === "p") {
onSettingChange({ type: "polygon" });
} else if (key === "b") {
onSettingChange({ type: "brush" });
} else if (key === "t") {
onSettingChange({ type: "toggle" });
} else if (key === "r") {
onSettingChange({ type: "remove" });
} else if (key === "s") {
onSettingChange({ useEdgeSnapping: !settings.useEdgeSnapping });
} else if (key === "f") {
onSettingChange({ preview: !settings.preview });
} else if (
(key === "z" || key === "Z") &&
(ctrlKey || metaKey) &&
shiftKey &&
!disabledActions.includes("redo")
) {
onToolAction("fogRedo");
} else if (
key === "z" &&
(ctrlKey || metaKey) &&
!shiftKey &&
!disabledActions.includes("undo")
) {
onToolAction("fogUndo");
}
}
function handleKeyUp({ key }) {
if (key === "Alt") {
onSettingChange({ useFogSubtract: !settings.useFogSubtract });
}
function handleKeyUp({ key }) {
if (key === "Alt") {
onSettingChange({ useFogSubtract: !settings.useFogSubtract });
}
}
interactionEmitter.on("keyDown", handleKeyDown);
interactionEmitter.on("keyUp", handleKeyUp);
return () => {
interactionEmitter.off("keyDown", handleKeyDown);
interactionEmitter.off("keyUp", handleKeyUp);
};
});
useKeyboard(handleKeyDown, handleKeyUp);
const isSmallScreen = useMedia({ query: "(max-width: 799px)" });
const drawTools = [

View File

@ -1,4 +1,4 @@
import React, { useEffect, useContext } from "react";
import React from "react";
import { Flex, Input, Text } from "theme-ui";
import ToolSection from "./ToolSection";
@ -8,28 +8,21 @@ import MeasureManhattanIcon from "../../../icons/MeasureManhattanIcon";
import Divider from "../../Divider";
import MapInteractionContext from "../../../contexts/MapInteractionContext";
import useKeyboard from "../../../helpers/useKeyboard";
function MeasureToolSettings({ settings, onSettingChange }) {
const { interactionEmitter } = useContext(MapInteractionContext);
// Keyboard shortcuts
useEffect(() => {
function handleKeyDown({ key }) {
if (key === "g") {
onSettingChange({ type: "chebyshev" });
} else if (key === "l") {
onSettingChange({ type: "euclidean" });
} else if (key === "c") {
onSettingChange({ type: "manhattan" });
}
function handleKeyDown({ key }) {
if (key === "g") {
onSettingChange({ type: "chebyshev" });
} else if (key === "l") {
onSettingChange({ type: "euclidean" });
} else if (key === "c") {
onSettingChange({ type: "manhattan" });
}
interactionEmitter.on("keyDown", handleKeyDown);
}
return () => {
interactionEmitter.off("keyDown", handleKeyDown);
};
});
useKeyboard(handleKeyDown);
const tools = [
{

View File

@ -0,0 +1,42 @@
import React, { useEffect, useState } from "react";
import { EventEmitter } from "events";
const KeyboardContext = React.createContext({ keyEmitter: new EventEmitter() });
export function KeyboardProvider({ children }) {
const [keyEmitter] = useState(new EventEmitter());
useEffect(() => {
function handleKeyDown(event) {
// Ignore text input
if (event.target instanceof HTMLInputElement) {
return;
}
keyEmitter.emit("keyDown", event);
}
function handleKeyUp(event) {
// Ignore text input
if (event.target instanceof HTMLInputElement) {
return;
}
keyEmitter.emit("keyUp", event);
}
document.body.addEventListener("keydown", handleKeyDown);
document.body.addEventListener("keyup", handleKeyUp);
document.body.tabIndex = 1;
return () => {
document.body.removeEventListener("keydown", handleKeyDown);
document.body.removeEventListener("keyup", handleKeyUp);
document.body.tabIndex = 0;
};
}, [keyEmitter]);
return (
<KeyboardContext.Provider value={{ keyEmitter }}>
{children}
</KeyboardContext.Provider>
);
}
export default KeyboardContext;

View File

@ -0,0 +1,26 @@
import { useEffect, useContext } from "react";
import KeyboardContext from "../contexts/KeyboardContext";
function useKeyboard(onKeyDown, onKeyUp) {
const { keyEmitter } = useContext(KeyboardContext);
useEffect(() => {
if (onKeyDown) {
keyEmitter.on("keyDown", onKeyDown);
}
if (onKeyUp) {
keyEmitter.on("keyUp", onKeyUp);
}
return () => {
if (onKeyDown) {
keyEmitter.off("keyDown", onKeyDown);
}
if (onKeyUp) {
keyEmitter.off("keyUp", onKeyUp);
}
};
});
}
export default useKeyboard;