Add alt drag duplicate shortcut for tokens, notes and selection

This commit is contained in:
Mitchell McCaffrey 2021-08-06 12:04:22 +10:00
parent 27bcc127bc
commit d6d8ee2f11
6 changed files with 111 additions and 0 deletions

View File

@ -40,6 +40,7 @@ import {
SelectionItemsChangeEventHandler, SelectionItemsChangeEventHandler,
SelectionItemsRemoveEventHandler, SelectionItemsRemoveEventHandler,
SelectionItemsCreateEventHandler, SelectionItemsCreateEventHandler,
TokensStateCreateHandler,
} from "../../types/Events"; } from "../../types/Events";
import useMapTokens from "../../hooks/useMapTokens"; import useMapTokens from "../../hooks/useMapTokens";
@ -53,6 +54,7 @@ type MapProps = {
mapActions: MapActions; mapActions: MapActions;
onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: TokenStateRemoveHandler; onMapTokenStateRemove: TokenStateRemoveHandler;
onMapTokensStateCreate: TokensStateCreateHandler;
onSelectionItemsChange: SelectionItemsChangeEventHandler; onSelectionItemsChange: SelectionItemsChangeEventHandler;
onSelectionItemsRemove: SelectionItemsRemoveEventHandler; onSelectionItemsRemove: SelectionItemsRemoveEventHandler;
onSelectionItemsCreate: SelectionItemsCreateEventHandler; onSelectionItemsCreate: SelectionItemsCreateEventHandler;
@ -75,6 +77,7 @@ function Map({
mapActions, mapActions,
onMapTokenStateChange, onMapTokenStateChange,
onMapTokenStateRemove, onMapTokenStateRemove,
onMapTokensStateCreate,
onSelectionItemsChange, onSelectionItemsChange,
onSelectionItemsRemove, onSelectionItemsRemove,
onSelectionItemsCreate, onSelectionItemsCreate,
@ -142,6 +145,7 @@ function Map({
mapState, mapState,
onMapTokenStateChange, onMapTokenStateChange,
onMapTokenStateRemove, onMapTokenStateRemove,
onMapTokensStateCreate,
selectedToolId selectedToolId
); );

View File

@ -1,11 +1,15 @@
import Konva from "konva"; import Konva from "konva";
import { KonvaEventObject } from "konva/lib/Node"; import { KonvaEventObject } from "konva/lib/Node";
import { useState } from "react"; import { useState } from "react";
import { v4 as uuid } from "uuid";
import Note from "../components/konva/Note"; import Note from "../components/konva/Note";
import NoteDragOverlay from "../components/note/NoteDragOverlay"; import NoteDragOverlay from "../components/note/NoteDragOverlay";
import NoteMenu from "../components/note/NoteMenu"; import NoteMenu from "../components/note/NoteMenu";
import NoteTool from "../components/tools/NoteTool"; import NoteTool from "../components/tools/NoteTool";
import { useBlur, useKeyboard } from "../contexts/KeyboardContext";
import { useUserId } from "../contexts/UserIdContext"; import { useUserId } from "../contexts/UserIdContext";
import shortcuts from "../shortcuts";
import { import {
NoteChangeEventHandler, NoteChangeEventHandler,
NoteCreateEventHander, NoteCreateEventHander,
@ -47,6 +51,12 @@ function useMapNotes(
} }
function handleNoteDragStart(_: KonvaEventObject<DragEvent>, noteId: string) { function handleNoteDragStart(_: KonvaEventObject<DragEvent>, noteId: string) {
if (duplicateNote) {
const note = mapState?.notes[noteId];
if (note) {
onNoteCreate([{ ...note, id: uuid() }]);
}
}
setNoteDraggingOptions({ dragging: true, noteId }); setNoteDraggingOptions({ dragging: true, noteId });
} }
@ -60,6 +70,26 @@ function useMapNotes(
setNoteDraggingOptions(undefined); setNoteDraggingOptions(undefined);
} }
const [duplicateNote, setDuplicateNote] = useState(false);
function handleKeyDown(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateNote(true);
}
}
function handleKeyUp(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateNote(false);
}
}
function handleBlur() {
setDuplicateNote(false);
}
useKeyboard(handleKeyDown, handleKeyUp);
useBlur(handleBlur);
const notes = ( const notes = (
<NoteTool <NoteTool
map={map} map={map}

View File

@ -1,8 +1,12 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { v4 as uuid } from "uuid";
import SelectionDragOverlay from "../components/selection/SelectionDragOverlay"; import SelectionDragOverlay from "../components/selection/SelectionDragOverlay";
import SelectionMenu from "../components/selection/SelectionMenu"; import SelectionMenu from "../components/selection/SelectionMenu";
import SelectTool from "../components/tools/SelectTool"; import SelectTool from "../components/tools/SelectTool";
import { useBlur, useKeyboard } from "../contexts/KeyboardContext";
import { useUserId } from "../contexts/UserIdContext"; import { useUserId } from "../contexts/UserIdContext";
import shortcuts from "../shortcuts";
import { import {
SelectionItemsChangeEventHandler, SelectionItemsChangeEventHandler,
SelectionItemsCreateEventHandler, SelectionItemsCreateEventHandler,
@ -10,8 +14,10 @@ import {
} from "../types/Events"; } from "../types/Events";
import { Map, MapToolId } from "../types/Map"; import { Map, MapToolId } from "../types/Map";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
import { Note } from "../types/Note";
import { Selection } from "../types/Select"; import { Selection } from "../types/Select";
import { SelectToolSettings } from "../types/Select"; import { SelectToolSettings } from "../types/Select";
import { TokenState } from "../types/TokenState";
function useMapSelection( function useMapSelection(
map: Map | null, map: Map | null,
@ -66,6 +72,24 @@ function useMapSelection(
}, [map]); }, [map]);
function handleSelectionDragStart() { function handleSelectionDragStart() {
if (duplicateSelection && selection) {
const tokenStates: TokenState[] = [];
const notes: Note[] = [];
for (let item of selection.items) {
if (item.type === "token") {
const token = mapState?.tokens[item.id];
if (token && !token.locked) {
tokenStates.push({ ...token, id: uuid() });
}
} else {
const note = mapState?.notes[item.id];
if (note && !note.locked) {
notes.push({ ...note, id: uuid() });
}
}
}
onSelectionItemsCreate(tokenStates, notes);
}
setIsSelectionDragging(true); setIsSelectionDragging(true);
} }
@ -81,6 +105,26 @@ function useMapSelection(
onSelectionItemsRemove(tokenStateIds, noteIds); onSelectionItemsRemove(tokenStateIds, noteIds);
} }
const [duplicateSelection, setDuplicateSelection] = useState(false);
function handleKeyDown(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateSelection(true);
}
}
function handleKeyUp(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateSelection(false);
}
}
function handleBlur() {
setDuplicateSelection(false);
}
useKeyboard(handleKeyDown, handleKeyUp);
useBlur(handleBlur);
const selectionTool = map ? ( const selectionTool = map ? (
<SelectTool <SelectTool
active={active} active={active}

View File

@ -1,4 +1,5 @@
import { Group } from "react-konva"; import { Group } from "react-konva";
import { v4 as uuid } from "uuid";
import { Map, MapToolId } from "../types/Map"; import { Map, MapToolId } from "../types/Map";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
@ -11,6 +12,7 @@ import { TokenState } from "../types/TokenState";
import { import {
TokenStateRemoveHandler, TokenStateRemoveHandler,
TokenStateChangeEventHandler, TokenStateChangeEventHandler,
TokensStateCreateHandler,
} from "../types/Events"; } from "../types/Events";
import { useState } from "react"; import { useState } from "react";
import Konva from "konva"; import Konva from "konva";
@ -19,12 +21,15 @@ import { KonvaEventObject } from "konva/lib/Node";
import TokenMenu from "../components/token/TokenMenu"; import TokenMenu from "../components/token/TokenMenu";
import TokenDragOverlay from "../components/token/TokenDragOverlay"; import TokenDragOverlay from "../components/token/TokenDragOverlay";
import { useUserId } from "../contexts/UserIdContext"; import { useUserId } from "../contexts/UserIdContext";
import { useBlur, useKeyboard } from "../contexts/KeyboardContext";
import shortcuts from "../shortcuts";
function useMapTokens( function useMapTokens(
map: Map | null, map: Map | null,
mapState: MapState | null, mapState: MapState | null,
onTokenStateChange: TokenStateChangeEventHandler, onTokenStateChange: TokenStateChangeEventHandler,
onTokenStateRemove: TokenStateRemoveHandler, onTokenStateRemove: TokenStateRemoveHandler,
onTokensStateCreate: TokensStateCreateHandler,
selectedToolId: MapToolId selectedToolId: MapToolId
) { ) {
const userId = useUserId(); const userId = useUserId();
@ -57,6 +62,12 @@ function useMapTokens(
_: KonvaEventObject<DragEvent>, _: KonvaEventObject<DragEvent>,
tokenStateId: string tokenStateId: string
) { ) {
if (duplicateToken) {
const state = mapState?.tokens[tokenStateId];
if (state) {
onTokensStateCreate([{ ...state, id: uuid() }]);
}
}
setTokenDraggingOptions({ setTokenDraggingOptions({
dragging: true, dragging: true,
tokenStateId, tokenStateId,
@ -76,6 +87,26 @@ function useMapTokens(
setTokenDraggingOptions(undefined); setTokenDraggingOptions(undefined);
} }
const [duplicateToken, setDuplicateToken] = useState(false);
function handleKeyDown(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateToken(true);
}
}
function handleKeyUp(event: KeyboardEvent) {
if (shortcuts.duplicate(event)) {
setDuplicateToken(false);
}
}
function handleBlur() {
setDuplicateToken(false);
}
useKeyboard(handleKeyDown, handleKeyUp);
useBlur(handleBlur);
function tokenFromTokenState(tokenState: TokenState) { function tokenFromTokenState(tokenState: TokenState) {
return ( return (
map && ( map && (

View File

@ -417,6 +417,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
mapActions={mapActions} mapActions={mapActions}
onMapTokenStateChange={handleMapTokenStateChange} onMapTokenStateChange={handleMapTokenStateChange}
onMapTokenStateRemove={handleMapTokenStateRemove} onMapTokenStateRemove={handleMapTokenStateRemove}
onMapTokensStateCreate={handleMapTokensStateCreate}
onSelectionItemsChange={handleSelectionItemsChange} onSelectionItemsChange={handleSelectionItemsChange}
onSelectionItemsRemove={handleSelectionItemsRemove} onSelectionItemsRemove={handleSelectionItemsRemove}
onSelectionItemsCreate={handleSelectionItemsCreate} onSelectionItemsCreate={handleSelectionItemsCreate}

View File

@ -104,6 +104,7 @@ const shortcuts: Record<string, Shortcut> = {
paste, paste,
delete: ({ key }) => key === "Backspace" || key === "Delete", delete: ({ key }) => key === "Backspace" || key === "Delete",
disableSnapping: ({ key }) => key === "Control" || key === "Meta", disableSnapping: ({ key }) => key === "Control" || key === "Meta",
duplicate: ({ key }) => key === "Alt",
}; };
export default shortcuts; export default shortcuts;