Files
grungnet/src/components/map/Map.tsx

428 lines
11 KiB
TypeScript
Raw Normal View History

2021-07-17 12:48:04 +10:00
import { useState } from "react";
2021-05-06 15:04:53 +10:00
import { Box } from "theme-ui";
import { useToasts } from "react-toast-notifications";
import MapControls from "./MapControls";
import MapInteraction from "./MapInteraction";
import MapTokens from "./MapTokens";
2020-04-18 18:11:21 +10:00
import MapDrawing from "./MapDrawing";
import MapFog from "./MapFog";
2020-05-31 16:25:05 +10:00
import MapGrid from "./MapGrid";
2020-06-26 12:23:06 +10:00
import MapMeasure from "./MapMeasure";
2020-07-28 17:59:26 +10:00
import NetworkedMapPointer from "../../network/NetworkedMapPointer";
2020-11-03 16:21:39 +11:00
import MapNotes from "./MapNotes";
2020-03-20 13:33:12 +11:00
import { useSettings } from "../../contexts/SettingsContext";
2020-05-21 16:46:50 +10:00
import TokenMenu from "../token/TokenMenu";
2020-05-21 22:57:44 +10:00
import TokenDragOverlay from "../token/TokenDragOverlay";
2020-11-05 14:41:33 +11:00
import NoteMenu from "../note/NoteMenu";
import NoteDragOverlay from "../note/NoteDragOverlay";
import {
2021-07-16 14:55:33 +10:00
AddStatesAction,
CutFogAction,
EditStatesAction,
RemoveStatesAction,
} from "../../actions";
2021-06-02 17:49:31 +10:00
import Session from "../../network/Session";
2021-07-17 12:48:04 +10:00
import { Drawing, DrawingState } from "../../types/Drawing";
import { Fog, FogState } from "../../types/Fog";
2021-07-17 18:18:57 +10:00
import { Map as MapType, MapActions, MapToolId } from "../../types/Map";
2021-07-16 14:55:33 +10:00
import { MapState } from "../../types/MapState";
import { Settings } from "../../types/Settings";
import {
MapChangeEventHandler,
MapResetEventHandler,
2021-07-17 12:48:04 +10:00
MapTokenStateRemoveHandler,
NoteChangeEventHandler,
NoteRemoveEventHander,
TokenStateChangeEventHandler,
2021-07-16 14:55:33 +10:00
} from "../../types/Events";
2021-07-17 12:48:04 +10:00
import Action from "../../actions/Action";
import Konva from "konva";
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
2021-07-19 11:37:41 +10:00
import MapSelect from "./MapSelect";
2021-07-17 12:48:04 +10:00
type MapProps = {
2021-07-17 18:18:57 +10:00
map: MapType | null;
2021-07-17 17:25:41 +10:00
mapState: MapState | null;
2021-07-17 12:48:04 +10:00
mapActions: MapActions;
onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: MapTokenStateRemoveHandler;
onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler;
onMapDraw: (action: Action<DrawingState>) => void;
onMapDrawUndo: () => void;
onMapDrawRedo: () => void;
onFogDraw: (action: Action<FogState>) => void;
onFogDrawUndo: () => void;
onFogDrawRedo: () => void;
onMapNoteChange: NoteChangeEventHandler;
onMapNoteRemove: NoteRemoveEventHander;
allowMapDrawing: boolean;
allowFogDrawing: boolean;
allowMapChange: boolean;
allowNoteEditing: boolean;
2021-07-17 17:25:41 +10:00
disabledTokens: Record<string, boolean>;
2021-07-17 12:48:04 +10:00
session: Session;
};
2020-03-20 13:33:12 +11:00
function Map({
map,
2020-04-23 17:23:34 +10:00
mapState,
mapActions,
onMapTokenStateChange,
onMapTokenStateRemove,
onMapChange,
2021-02-22 17:14:12 +11:00
onMapReset,
2020-04-19 15:15:48 +10:00
onMapDraw,
onMapDrawUndo,
onMapDrawRedo,
onFogDraw,
onFogDrawUndo,
onFogDrawRedo,
2020-11-04 15:03:34 +11:00
onMapNoteChange,
2020-11-05 14:41:33 +11:00
onMapNoteRemove,
2020-04-29 21:12:57 +10:00
allowMapDrawing,
allowFogDrawing,
2020-07-17 15:57:52 +10:00
allowMapChange,
2020-11-05 16:21:52 +11:00
allowNoteEditing,
disabledTokens,
2020-07-28 17:59:26 +10:00
session,
2021-07-17 12:48:04 +10:00
}: MapProps) {
const { addToast } = useToasts();
2021-07-16 14:55:33 +10:00
const [selectedToolId, setSelectedToolId] = useState<MapToolId>("move");
const { settings, setSettings } = useSettings();
2020-04-29 20:55:52 +10:00
2021-07-16 14:55:33 +10:00
function handleToolSettingChange(change: Partial<Settings>) {
setSettings((prevSettings) => ({
...prevSettings,
2021-07-16 14:55:33 +10:00
...change,
}));
}
2020-04-19 00:24:06 +10:00
const drawShapes = Object.values(mapState?.drawShapes || {});
const fogShapes = Object.values(mapState?.fogShapes || {});
2021-06-02 17:49:31 +10:00
function handleToolAction(action: string) {
2020-04-29 20:55:52 +10:00
if (action === "eraseAll") {
2021-07-16 14:55:33 +10:00
onMapDraw(new RemoveStatesAction(drawShapes.map((s) => s.id)));
2020-04-29 20:55:52 +10:00
}
if (action === "mapUndo") {
onMapDrawUndo();
}
if (action === "mapRedo") {
onMapDrawRedo();
}
if (action === "fogUndo") {
onFogDrawUndo();
}
if (action === "fogRedo") {
onFogDrawRedo();
}
2020-04-29 20:55:52 +10:00
}
2021-07-16 14:55:33 +10:00
function handleMapShapeAdd(shape: Drawing) {
onMapDraw(new AddStatesAction([shape]));
}
2021-06-02 17:49:31 +10:00
function handleMapShapesRemove(shapeIds: string[]) {
2021-07-16 14:55:33 +10:00
onMapDraw(new RemoveStatesAction(shapeIds));
}
2021-07-16 14:55:33 +10:00
function handleFogShapesAdd(shapes: Fog[]) {
onFogDraw(new AddStatesAction(shapes));
}
2021-07-16 14:55:33 +10:00
function handleFogShapesCut(shapes: Fog[]) {
onFogDraw(new CutFogAction(shapes));
2020-06-09 12:45:52 +10:00
}
2021-06-02 17:49:31 +10:00
function handleFogShapesRemove(shapeIds: string[]) {
2021-07-16 14:55:33 +10:00
onFogDraw(new RemoveStatesAction(shapeIds));
}
2021-07-16 14:55:33 +10:00
function handleFogShapesEdit(shapes: Partial<Fog>[]) {
onFogDraw(new EditStatesAction(shapes));
2020-04-29 20:40:34 +10:00
}
2021-07-17 12:48:04 +10:00
const disabledControls: MapToolId[] = [];
2020-04-29 21:12:57 +10:00
if (!allowMapDrawing) {
2020-06-21 11:01:03 +10:00
disabledControls.push("drawing");
}
if (!map) {
disabledControls.push("move");
2020-06-26 12:23:06 +10:00
disabledControls.push("measure");
2020-07-28 17:59:26 +10:00
disabledControls.push("pointer");
2021-07-19 10:56:14 +10:00
disabledControls.push("select");
}
2020-04-29 21:12:57 +10:00
if (!allowFogDrawing) {
disabledControls.push("fog");
}
2020-07-17 15:57:52 +10:00
if (!allowMapChange) {
disabledControls.push("map");
}
2020-11-05 16:21:52 +11:00
if (!allowNoteEditing) {
disabledControls.push("note");
}
2021-07-16 14:55:33 +10:00
const disabledSettings: {
fog: string[];
drawing: string[];
} = {
2021-07-08 12:00:47 +10:00
fog: [],
drawing: [],
};
if (drawShapes.length === 0) {
2020-06-21 11:01:03 +10:00
disabledSettings.drawing.push("erase");
}
if (!mapState || mapActions.mapDrawActionIndex < 0) {
2020-06-21 11:01:03 +10:00
disabledSettings.drawing.push("undo");
}
if (
!mapState ||
mapActions.mapDrawActionIndex === mapActions.mapDrawActions.length - 1
) {
2020-06-21 11:01:03 +10:00
disabledSettings.drawing.push("redo");
}
if (!mapState || mapActions.fogDrawActionIndex < 0) {
disabledSettings.fog.push("undo");
}
if (
!mapState ||
mapActions.fogDrawActionIndex === mapActions.fogDrawActions.length - 1
) {
disabledSettings.fog.push("redo");
}
const mapControls = (
<MapControls
onMapChange={onMapChange}
2021-02-22 17:14:12 +11:00
onMapReset={onMapReset}
currentMap={map}
currentMapState={mapState}
onSelectedToolChange={setSelectedToolId}
selectedToolId={selectedToolId}
toolSettings={settings}
onToolSettingChange={handleToolSettingChange}
2020-04-29 20:55:52 +10:00
onToolAction={handleToolAction}
disabledControls={disabledControls}
disabledSettings={disabledSettings}
/>
);
2020-05-21 16:46:50 +10:00
2021-07-16 14:55:33 +10:00
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState<boolean>(false);
2021-07-17 12:48:04 +10:00
const [tokenMenuOptions, setTokenMenuOptions] = useState<TokenMenuOptions>();
const [tokenDraggingOptions, setTokenDraggingOptions] =
useState<TokenDraggingOptions>();
function handleTokenMenuOpen(tokenStateId: string, tokenImage: Konva.Node) {
2020-05-21 16:46:50 +10:00
setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true);
}
const mapTokens = map && mapState && (
<MapTokens
map={map}
mapState={mapState}
tokenDraggingOptions={tokenDraggingOptions}
setTokenDraggingOptions={setTokenDraggingOptions}
onMapTokenStateChange={onMapTokenStateChange}
2021-07-17 12:48:04 +10:00
onTokenMenuOpen={handleTokenMenuOpen}
selectedToolId={selectedToolId}
disabledTokens={disabledTokens}
/>
);
2020-05-22 13:46:52 +10:00
const tokenMenu = (
2020-05-21 16:46:50 +10:00
<TokenMenu
isOpen={isTokenMenuOpen}
onRequestClose={() => setIsTokenMenuOpen(false)}
onTokenStateChange={onMapTokenStateChange}
2021-07-17 12:48:04 +10:00
tokenState={
2021-07-17 17:25:41 +10:00
tokenMenuOptions && mapState?.tokens[tokenMenuOptions.tokenStateId]
2021-07-17 12:48:04 +10:00
}
tokenImage={tokenMenuOptions && tokenMenuOptions.tokenImage}
map={map}
2020-05-21 16:46:50 +10:00
/>
);
2020-11-05 14:41:33 +11:00
const tokenDragOverlay = tokenDraggingOptions && (
2020-05-21 22:57:44 +10:00
<TokenDragOverlay
2021-07-16 14:55:33 +10:00
onTokenStateRemove={(state) => {
onMapTokenStateRemove(state);
2021-07-17 12:48:04 +10:00
setTokenDraggingOptions(undefined);
}}
2020-11-05 14:41:33 +11:00
tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
2021-07-17 14:36:39 +10:00
tokenNode={tokenDraggingOptions && tokenDraggingOptions.tokenNode}
2020-11-05 14:41:33 +11:00
dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
2020-05-21 22:57:44 +10:00
/>
);
const mapDrawing = (
<MapDrawing
map={map}
2021-07-13 18:50:18 +10:00
drawings={drawShapes}
onDrawingAdd={handleMapShapeAdd}
onDrawingsRemove={handleMapShapesRemove}
active={selectedToolId === "drawing"}
toolSettings={settings.drawing}
/>
);
const mapFog = (
<MapFog
map={map}
shapes={fogShapes}
onShapesAdd={handleFogShapesAdd}
onShapesCut={handleFogShapesCut}
onShapesRemove={handleFogShapesRemove}
onShapesEdit={handleFogShapesEdit}
onShapeError={addToast}
active={selectedToolId === "fog"}
toolSettings={settings.fog}
editable={allowFogDrawing && !settings.fog.preview}
/>
);
const mapGrid = map && map.showGrid && <MapGrid map={map} />;
2020-05-31 16:25:05 +10:00
2020-06-26 12:23:06 +10:00
const mapMeasure = (
2021-07-08 12:00:47 +10:00
<MapMeasure map={map} active={selectedToolId === "measure"} />
2020-06-26 12:23:06 +10:00
);
2020-07-28 17:59:26 +10:00
const mapPointer = (
<NetworkedMapPointer
active={selectedToolId === "pointer"}
session={session}
/>
);
2021-06-02 17:49:31 +10:00
const [isNoteMenuOpen, setIsNoteMenuOpen] = useState<boolean>(false);
2021-07-17 12:48:04 +10:00
const [noteMenuOptions, setNoteMenuOptions] = useState<NoteMenuOptions>();
const [noteDraggingOptions, setNoteDraggingOptions] =
useState<NoteDraggingOptions>();
function handleNoteMenuOpen(noteId: string, noteNode: Konva.Node) {
2020-11-04 15:03:34 +11:00
setNoteMenuOptions({ noteId, noteNode });
setIsNoteMenuOpen(true);
}
2021-07-17 12:48:04 +10:00
function sortNotes(
a: Note,
b: Note,
noteDraggingOptions?: NoteDraggingOptions
) {
2020-11-05 15:39:56 +11:00
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;
}
}
2020-11-03 16:21:39 +11:00
const mapNotes = (
<MapNotes
map={map}
active={selectedToolId === "note"}
2020-11-04 15:03:34 +11:00
onNoteAdd={onMapNoteChange}
onNoteChange={onMapNoteChange}
2020-11-05 15:39:56 +11:00
notes={
mapState
? Object.values(mapState.notes).sort((a, b) =>
sortNotes(a, b, noteDraggingOptions)
)
: []
}
2020-11-04 15:03:34 +11:00
onNoteMenuOpen={handleNoteMenuOpen}
2020-11-05 16:21:52 +11:00
draggable={
allowNoteEditing &&
(selectedToolId === "note" || selectedToolId === "move")
2020-11-05 16:21:52 +11:00
}
2021-07-16 14:55:33 +10:00
onNoteDragStart={(e, noteId) =>
2020-11-05 14:41:33 +11:00
setNoteDraggingOptions({ dragging: true, noteId, noteGroup: e.target })
}
onNoteDragEnd={() =>
2021-07-17 12:48:04 +10:00
noteDraggingOptions &&
2020-11-05 14:41:33 +11:00
setNoteDraggingOptions({ ...noteDraggingOptions, dragging: false })
}
2021-01-28 16:26:28 +11:00
fadeOnHover={selectedToolId === "drawing"}
2020-11-04 15:03:34 +11:00
/>
);
const noteMenu = (
2020-11-05 14:41:33 +11:00
<NoteMenu
2020-11-04 15:03:34 +11:00
isOpen={isNoteMenuOpen}
onRequestClose={() => setIsNoteMenuOpen(false)}
onNoteChange={onMapNoteChange}
2021-07-17 17:25:41 +10:00
note={noteMenuOptions && mapState?.notes[noteMenuOptions.noteId]}
2021-07-17 12:48:04 +10:00
noteNode={noteMenuOptions?.noteNode}
2020-11-04 15:03:34 +11:00
map={map}
2020-11-03 16:21:39 +11:00
/>
);
2021-07-17 12:48:04 +10:00
const noteDragOverlay = noteDraggingOptions ? (
2020-11-05 14:41:33 +11:00
<NoteDragOverlay
2021-07-17 12:48:04 +10:00
dragging={noteDraggingOptions.dragging}
noteGroup={noteDraggingOptions.noteGroup}
noteId={noteDraggingOptions.noteId}
2021-07-16 14:55:33 +10:00
onNoteRemove={(noteId) => {
2020-11-05 14:41:33 +11:00
onMapNoteRemove(noteId);
2021-07-17 12:48:04 +10:00
setNoteDraggingOptions(undefined);
2020-11-05 14:41:33 +11:00
}}
/>
2021-07-17 12:48:04 +10:00
) : null;
2020-11-05 14:41:33 +11:00
2021-07-19 11:37:41 +10:00
const mapSelect = (
<MapSelect
active={selectedToolId === "select"}
toolSettings={settings.select}
/>
);
return (
2021-05-06 15:04:53 +10:00
<Box sx={{ flexGrow: 1 }}>
2021-05-13 16:27:07 +10:00
<MapInteraction
map={map}
mapState={mapState}
controls={
<>
{mapControls}
{tokenMenu}
{noteMenu}
{tokenDragOverlay}
{noteDragOverlay}
</>
}
selectedToolId={selectedToolId}
onSelectedToolChange={setSelectedToolId}
disabledControls={disabledControls}
>
{mapGrid}
{mapDrawing}
{mapNotes}
{mapTokens}
{mapFog}
{mapPointer}
{mapMeasure}
2021-07-19 11:37:41 +10:00
{mapSelect}
2021-05-13 16:27:07 +10:00
</MapInteraction>
2021-05-06 15:04:53 +10:00
</Box>
);
}
export default Map;