Refactor konva components and map tools

This commit is contained in:
Mitchell McCaffrey
2021-07-19 15:28:09 +10:00
parent fa2190dd7d
commit fa62783b9c
26 changed files with 995 additions and 894 deletions
+118
View File
@@ -0,0 +1,118 @@
import Konva from "konva";
import { KonvaEventObject } from "konva/lib/Node";
import { useState } from "react";
import NoteDragOverlay from "../components/note/NoteDragOverlay";
import NoteMenu from "../components/note/NoteMenu";
import NoteTool from "../components/tools/NoteTool";
import { NoteChangeEventHandler, NoteRemoveEventHander } from "../types/Events";
import { Map, MapToolId } from "../types/Map";
import { MapState } from "../types/MapState";
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../types/Note";
function useMapNotes(
map: Map | null,
mapState: MapState | null,
onNoteChange: NoteChangeEventHandler,
onNoteRemove: NoteRemoveEventHander,
selectedToolId: MapToolId,
allowNoteEditing: boolean
) {
const [isNoteMenuOpen, setIsNoteMenuOpen] = useState<boolean>(false);
const [noteMenuOptions, setNoteMenuOptions] = useState<NoteMenuOptions>();
const [noteDraggingOptions, setNoteDraggingOptions] =
useState<NoteDraggingOptions>();
function handleNoteMenuOpen(noteId: string, noteNode: Konva.Node) {
setNoteMenuOptions({ noteId, noteNode });
setIsNoteMenuOpen(true);
}
function handleNoteDragStart(
event: KonvaEventObject<DragEvent>,
noteId: string
) {
setNoteDraggingOptions({ dragging: true, noteId, noteGroup: event.target });
}
function handleNoteDragEnd() {
noteDraggingOptions &&
setNoteDraggingOptions({ ...noteDraggingOptions, dragging: false });
}
function handleNoteRemove(noteId: string) {
onNoteRemove(noteId);
setNoteDraggingOptions(undefined);
}
const notes = (
<NoteTool
map={map}
active={selectedToolId === "note"}
onNoteAdd={onNoteChange}
onNoteChange={onNoteChange}
notes={
mapState
? Object.values(mapState.notes).sort((a, b) =>
sortNotes(a, b, noteDraggingOptions)
)
: []
}
onNoteMenuOpen={handleNoteMenuOpen}
draggable={
allowNoteEditing &&
(selectedToolId === "note" || selectedToolId === "move")
}
onNoteDragStart={handleNoteDragStart}
onNoteDragEnd={handleNoteDragEnd}
fadeOnHover={selectedToolId === "drawing"}
/>
);
const noteMenu = (
<NoteMenu
isOpen={isNoteMenuOpen}
onRequestClose={() => setIsNoteMenuOpen(false)}
onNoteChange={onNoteChange}
note={noteMenuOptions && mapState?.notes[noteMenuOptions.noteId]}
noteNode={noteMenuOptions?.noteNode}
map={map}
/>
);
const noteDragOverlay = noteDraggingOptions ? (
<NoteDragOverlay
dragging={noteDraggingOptions.dragging}
noteGroup={noteDraggingOptions.noteGroup}
noteId={noteDraggingOptions.noteId}
onNoteRemove={handleNoteRemove}
/>
) : null;
return { notes, noteMenu, noteDragOverlay };
}
export default useMapNotes;
function sortNotes(
a: Note,
b: Note,
noteDraggingOptions?: NoteDraggingOptions
) {
if (
noteDraggingOptions &&
noteDraggingOptions.dragging &&
noteDraggingOptions.noteId === a.id
) {
// If dragging token `a` move above
return 1;
} else if (
noteDraggingOptions &&
noteDraggingOptions.dragging &&
noteDraggingOptions.noteId === b.id
) {
// If dragging token `b` move above
return -1;
} else {
// Else sort so last modified is on top
return a.lastModified - b.lastModified;
}
}
+160
View File
@@ -0,0 +1,160 @@
import { Group } from "react-konva";
import { Map, MapToolId } from "../types/Map";
import { MapState } from "../types/MapState";
import {
TokenCategory,
TokenDraggingOptions,
TokenMenuOptions,
} from "../types/Token";
import { TokenState } from "../types/TokenState";
import {
MapTokenStateRemoveHandler,
TokenStateChangeEventHandler,
} from "../types/Events";
import { useState } from "react";
import Konva from "konva";
import Token from "../components/konva/Token";
import { KonvaEventObject } from "konva/lib/Node";
import TokenMenu from "../components/token/TokenMenu";
import TokenDragOverlay from "../components/token/TokenDragOverlay";
function useMapTokens(
map: Map | null,
mapState: MapState | null,
onTokenStateChange: TokenStateChangeEventHandler,
onTokenStateRemove: MapTokenStateRemoveHandler,
selectedToolId: MapToolId,
disabledTokens: Record<string, boolean>
) {
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState<boolean>(false);
const [tokenMenuOptions, setTokenMenuOptions] = useState<TokenMenuOptions>();
const [tokenDraggingOptions, setTokenDraggingOptions] =
useState<TokenDraggingOptions>();
function handleTokenMenuOpen(tokenStateId: string, tokenImage: Konva.Node) {
setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true);
}
function handleTokenDragStart(
event: KonvaEventObject<DragEvent>,
tokenStateId: string
) {
setTokenDraggingOptions({
dragging: true,
tokenStateId,
tokenNode: event.target,
});
}
function handleTokenDragEnd() {
tokenDraggingOptions &&
setTokenDraggingOptions({
...tokenDraggingOptions,
dragging: false,
});
}
function handleTokenStateRemove(tokenState: TokenState) {
onTokenStateRemove(tokenState);
setTokenDraggingOptions(undefined);
}
const tokens = map && mapState && (
<Group>
{Object.values(mapState.tokens)
.sort((a, b) => sortMapTokenStates(a, b, tokenDraggingOptions))
.map((tokenState) => (
<Token
key={tokenState.id}
tokenState={tokenState}
onTokenStateChange={onTokenStateChange}
onTokenMenuOpen={handleTokenMenuOpen}
onTokenDragStart={handleTokenDragStart}
onTokenDragEnd={handleTokenDragEnd}
draggable={
selectedToolId === "move" &&
!(tokenState.id in disabledTokens) &&
!tokenState.locked
}
fadeOnHover={selectedToolId === "drawing"}
map={map}
/>
))}
</Group>
);
const tokenMenu = (
<TokenMenu
isOpen={isTokenMenuOpen}
onRequestClose={() => setIsTokenMenuOpen(false)}
onTokenStateChange={onTokenStateChange}
tokenState={
tokenMenuOptions && mapState?.tokens[tokenMenuOptions.tokenStateId]
}
tokenImage={tokenMenuOptions?.tokenImage}
map={map}
/>
);
const tokenDraggingState =
tokenDraggingOptions && mapState?.tokens[tokenDraggingOptions.tokenStateId];
const tokenDragOverlay = tokenDraggingOptions && tokenDraggingState && (
<TokenDragOverlay
onTokenStateRemove={handleTokenStateRemove}
tokenState={tokenDraggingState}
tokenNode={tokenDraggingOptions.tokenNode}
dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
/>
);
return { tokens, tokenMenu, tokenDragOverlay };
}
export default useMapTokens;
function getMapTokenCategoryWeight(category: TokenCategory) {
switch (category) {
case "character":
return 0;
case "vehicle":
return 1;
case "prop":
return 2;
default:
return 0;
}
}
// Sort so vehicles render below other tokens
function sortMapTokenStates(
a: TokenState,
b: TokenState,
tokenDraggingOptions?: TokenDraggingOptions
) {
// If categories are different sort in order "prop", "vehicle", "character"
if (b.category !== a.category) {
const aWeight = getMapTokenCategoryWeight(a.category);
const bWeight = getMapTokenCategoryWeight(b.category);
return bWeight - aWeight;
} else if (
tokenDraggingOptions &&
tokenDraggingOptions.dragging &&
tokenDraggingOptions.tokenStateId === a.id
) {
// If dragging token a move above
return 1;
} else if (
tokenDraggingOptions &&
tokenDraggingOptions.dragging &&
tokenDraggingOptions.tokenStateId === b.id
) {
// If dragging token b move above
return -1;
} else {
// Else sort so last modified is on top
return a.lastModified - b.lastModified;
}
}