Typescript
This commit is contained in:
parent
e48d19a817
commit
fecf8090ea
@ -3,7 +3,7 @@ import Creatable from "react-select/creatable";
|
||||
import { useThemeUI } from "theme-ui";
|
||||
|
||||
type SelectProps = {
|
||||
creatable: boolean;
|
||||
creatable?: boolean;
|
||||
} & Props;
|
||||
|
||||
function Select({ creatable, ...props }: SelectProps) {
|
||||
@ -76,4 +76,8 @@ function Select({ creatable, ...props }: SelectProps) {
|
||||
);
|
||||
}
|
||||
|
||||
Select.defaultProps = {
|
||||
creatable: false,
|
||||
};
|
||||
|
||||
export default Select;
|
||||
|
@ -72,29 +72,31 @@ function DragOverlay({ dragging, node, onRemove }: DragOverlayProps) {
|
||||
}
|
||||
});
|
||||
|
||||
if (!dragging) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
dragging && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "32px",
|
||||
left: "50%",
|
||||
borderRadius: "50%",
|
||||
transform: isRemoveHovered
|
||||
? "translateX(-50%) scale(2.0)"
|
||||
: "translateX(-50%) scale(1.5)",
|
||||
transition: "transform 250ms ease",
|
||||
color: isRemoveHovered ? "primary" : "text",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
bg="overlay"
|
||||
ref={removeTokenRef}
|
||||
>
|
||||
<IconButton>
|
||||
<RemoveTokenIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "32px",
|
||||
left: "50%",
|
||||
borderRadius: "50%",
|
||||
transform: isRemoveHovered
|
||||
? "translateX(-50%) scale(2.0)"
|
||||
: "translateX(-50%) scale(1.5)",
|
||||
transition: "transform 250ms ease",
|
||||
color: isRemoveHovered ? "primary" : "text",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
bg="overlay"
|
||||
ref={removeTokenRef}
|
||||
>
|
||||
<IconButton>
|
||||
<RemoveTokenIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Box } from "theme-ui";
|
||||
import { useToasts } from "react-toast-notifications";
|
||||
|
||||
@ -27,15 +27,48 @@ import {
|
||||
RemoveStatesAction,
|
||||
} from "../../actions";
|
||||
import Session from "../../network/Session";
|
||||
import { Drawing } from "../../types/Drawing";
|
||||
import { Fog } from "../../types/Fog";
|
||||
import { Map, MapToolId } from "../../types/Map";
|
||||
import { Drawing, DrawingState } from "../../types/Drawing";
|
||||
import { Fog, FogState } from "../../types/Fog";
|
||||
import { Map, MapActions, MapToolId } from "../../types/Map";
|
||||
import { MapState } from "../../types/MapState";
|
||||
import { Settings } from "../../types/Settings";
|
||||
import {
|
||||
MapChangeEventHandler,
|
||||
MapResetEventHandler,
|
||||
MapTokensStateCreateHandler,
|
||||
MapTokenStateRemoveHandler,
|
||||
NoteChangeEventHandler,
|
||||
NoteRemoveEventHander,
|
||||
TokenStateChangeEventHandler,
|
||||
} from "../../types/Events";
|
||||
import Action from "../../actions/Action";
|
||||
import Konva from "konva";
|
||||
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
|
||||
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
|
||||
|
||||
type MapProps = {
|
||||
map: Map;
|
||||
mapState: MapState;
|
||||
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;
|
||||
disabledTokens: string[];
|
||||
session: Session;
|
||||
};
|
||||
|
||||
function Map({
|
||||
map,
|
||||
@ -59,29 +92,7 @@ function Map({
|
||||
allowNoteEditing,
|
||||
disabledTokens,
|
||||
session,
|
||||
}: {
|
||||
map: Map;
|
||||
mapState: MapState;
|
||||
mapActions: ;
|
||||
onMapTokenStateChange: ;
|
||||
onMapTokenStateRemove: ;
|
||||
onMapChange: MapChangeEventHandler;
|
||||
onMapReset: MapResetEventHandler;
|
||||
onMapDraw: ;
|
||||
onMapDrawUndo: ;
|
||||
onMapDrawRedo: ;
|
||||
onFogDraw: ;
|
||||
onFogDrawUndo: ;
|
||||
onFogDrawRedo: ;
|
||||
onMapNoteChange: ;
|
||||
onMapNoteRemove: ;
|
||||
allowMapDrawing: boolean;
|
||||
allowFogDrawing: boolean;
|
||||
allowMapChange: boolean;
|
||||
allowNoteEditing: boolean;
|
||||
disabledTokens: ;
|
||||
session: Session;
|
||||
}) {
|
||||
}: MapProps) {
|
||||
const { addToast } = useToasts();
|
||||
|
||||
const { tokensById } = useTokenData();
|
||||
@ -141,7 +152,7 @@ function Map({
|
||||
onFogDraw(new EditStatesAction(shapes));
|
||||
}
|
||||
|
||||
const disabledControls = [];
|
||||
const disabledControls: MapToolId[] = [];
|
||||
if (!allowMapDrawing) {
|
||||
disabledControls.push("drawing");
|
||||
}
|
||||
@ -206,9 +217,10 @@ function Map({
|
||||
);
|
||||
|
||||
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState<boolean>(false);
|
||||
const [tokenMenuOptions, setTokenMenuOptions] = useState({});
|
||||
const [tokenDraggingOptions, setTokenDraggingOptions] = useState();
|
||||
function handleTokenMenuOpen(tokenStateId: string, tokenImage) {
|
||||
const [tokenMenuOptions, setTokenMenuOptions] = useState<TokenMenuOptions>();
|
||||
const [tokenDraggingOptions, setTokenDraggingOptions] =
|
||||
useState<TokenDraggingOptions>();
|
||||
function handleTokenMenuOpen(tokenStateId: string, tokenImage: Konva.Node) {
|
||||
setTokenMenuOptions({ tokenStateId, tokenImage });
|
||||
setIsTokenMenuOpen(true);
|
||||
}
|
||||
@ -220,7 +232,7 @@ function Map({
|
||||
tokenDraggingOptions={tokenDraggingOptions}
|
||||
setTokenDraggingOptions={setTokenDraggingOptions}
|
||||
onMapTokenStateChange={onMapTokenStateChange}
|
||||
handleTokenMenuOpen={handleTokenMenuOpen}
|
||||
onTokenMenuOpen={handleTokenMenuOpen}
|
||||
selectedToolId={selectedToolId}
|
||||
disabledTokens={disabledTokens}
|
||||
/>
|
||||
@ -231,8 +243,12 @@ function Map({
|
||||
isOpen={isTokenMenuOpen}
|
||||
onRequestClose={() => setIsTokenMenuOpen(false)}
|
||||
onTokenStateChange={onMapTokenStateChange}
|
||||
tokenState={mapState && mapState.tokens[tokenMenuOptions.tokenStateId]}
|
||||
tokenImage={tokenMenuOptions.tokenImage}
|
||||
tokenState={
|
||||
tokenMenuOptions &&
|
||||
mapState &&
|
||||
mapState.tokens[tokenMenuOptions.tokenStateId]
|
||||
}
|
||||
tokenImage={tokenMenuOptions && tokenMenuOptions.tokenImage}
|
||||
map={map}
|
||||
/>
|
||||
);
|
||||
@ -241,7 +257,7 @@ function Map({
|
||||
<TokenDragOverlay
|
||||
onTokenStateRemove={(state) => {
|
||||
onMapTokenStateRemove(state);
|
||||
setTokenDraggingOptions(null);
|
||||
setTokenDraggingOptions(undefined);
|
||||
}}
|
||||
onTokenStateChange={onMapTokenStateChange}
|
||||
tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
|
||||
@ -291,14 +307,19 @@ function Map({
|
||||
);
|
||||
|
||||
const [isNoteMenuOpen, setIsNoteMenuOpen] = useState<boolean>(false);
|
||||
const [noteMenuOptions, setNoteMenuOptions] = useState({});
|
||||
const [noteDraggingOptions, setNoteDraggingOptions] = useState();
|
||||
function handleNoteMenuOpen(noteId: string, noteNode) {
|
||||
const [noteMenuOptions, setNoteMenuOptions] = useState<NoteMenuOptions>();
|
||||
const [noteDraggingOptions, setNoteDraggingOptions] =
|
||||
useState<NoteDraggingOptions>();
|
||||
function handleNoteMenuOpen(noteId: string, noteNode: Konva.Node) {
|
||||
setNoteMenuOptions({ noteId, noteNode });
|
||||
setIsNoteMenuOpen(true);
|
||||
}
|
||||
|
||||
function sortNotes(a, b, noteDraggingOptions) {
|
||||
function sortNotes(
|
||||
a: Note,
|
||||
b: Note,
|
||||
noteDraggingOptions?: NoteDraggingOptions
|
||||
) {
|
||||
if (
|
||||
noteDraggingOptions &&
|
||||
noteDraggingOptions.dragging &&
|
||||
@ -341,6 +362,7 @@ function Map({
|
||||
setNoteDraggingOptions({ dragging: true, noteId, noteGroup: e.target })
|
||||
}
|
||||
onNoteDragEnd={() =>
|
||||
noteDraggingOptions &&
|
||||
setNoteDraggingOptions({ ...noteDraggingOptions, dragging: false })
|
||||
}
|
||||
fadeOnHover={selectedToolId === "drawing"}
|
||||
@ -352,23 +374,25 @@ function Map({
|
||||
isOpen={isNoteMenuOpen}
|
||||
onRequestClose={() => setIsNoteMenuOpen(false)}
|
||||
onNoteChange={onMapNoteChange}
|
||||
note={mapState && mapState.notes[noteMenuOptions.noteId]}
|
||||
noteNode={noteMenuOptions.noteNode}
|
||||
note={
|
||||
noteMenuOptions && mapState && mapState.notes[noteMenuOptions.noteId]
|
||||
}
|
||||
noteNode={noteMenuOptions?.noteNode}
|
||||
map={map}
|
||||
/>
|
||||
);
|
||||
|
||||
const noteDragOverlay = (
|
||||
const noteDragOverlay = noteDraggingOptions ? (
|
||||
<NoteDragOverlay
|
||||
dragging={!!(noteDraggingOptions && noteDraggingOptions.dragging)}
|
||||
noteGroup={noteDraggingOptions && noteDraggingOptions.noteGroup}
|
||||
noteId={noteDraggingOptions && noteDraggingOptions.noteId}
|
||||
dragging={noteDraggingOptions.dragging}
|
||||
noteGroup={noteDraggingOptions.noteGroup}
|
||||
noteId={noteDraggingOptions.noteId}
|
||||
onNoteRemove={(noteId) => {
|
||||
onMapNoteRemove(noteId);
|
||||
setNoteDraggingOptions(null);
|
||||
setNoteDraggingOptions(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
|
@ -24,8 +24,7 @@ import MapGrid from "./MapGrid";
|
||||
import MapGridEditor from "./MapGridEditor";
|
||||
import { Map } from "../../types/Map";
|
||||
import { GridInset } from "../../types/Grid";
|
||||
|
||||
type MapSettingsChangeEventHandler = (change: Partial<Map>) => void;
|
||||
import { MapSettingsChangeEventHandler } from "../../types/Events";
|
||||
|
||||
type MapEditorProps = {
|
||||
map: Map;
|
||||
|
@ -9,10 +9,10 @@ type MapMenuProps = {
|
||||
isOpen: boolean;
|
||||
onRequestClose: RequestCloseEventHandler;
|
||||
onModalContent: (instance: HTMLDivElement) => void;
|
||||
top: number;
|
||||
left: number;
|
||||
bottom: number;
|
||||
right: number;
|
||||
top: number | string;
|
||||
left: number | string;
|
||||
bottom: number | string;
|
||||
right: number | string;
|
||||
children: React.ReactNode;
|
||||
style: React.CSSProperties;
|
||||
excludeNode: Node | null;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import shortid from "shortid";
|
||||
import { Group } from "react-konva";
|
||||
import Konva from "konva";
|
||||
|
||||
import { useInteractionEmitter } from "../../contexts/MapInteractionContext";
|
||||
import { useMapStage } from "../../contexts/MapStageContext";
|
||||
@ -13,8 +14,30 @@ import useGridSnapping from "../../hooks/useGridSnapping";
|
||||
|
||||
import Note from "../note/Note";
|
||||
|
||||
import { Map } from "../../types/Map";
|
||||
import { Note as NoteType } from "../../types/Note";
|
||||
import {
|
||||
NoteAddEventHander,
|
||||
NoteChangeEventHandler,
|
||||
NoteDragEventHandler,
|
||||
NoteMenuOpenEventHandler,
|
||||
} from "../../types/Events";
|
||||
|
||||
const defaultNoteSize = 2;
|
||||
|
||||
type MapNoteProps = {
|
||||
map: Map;
|
||||
active: boolean;
|
||||
onNoteAdd: NoteAddEventHander;
|
||||
onNoteChange: NoteChangeEventHandler;
|
||||
notes: NoteType[];
|
||||
onNoteMenuOpen: NoteMenuOpenEventHandler;
|
||||
draggable: boolean;
|
||||
onNoteDragStart: NoteDragEventHandler;
|
||||
onNoteDragEnd: NoteDragEventHandler;
|
||||
fadeOnHover: boolean;
|
||||
};
|
||||
|
||||
function MapNotes({
|
||||
map,
|
||||
active,
|
||||
@ -26,14 +49,14 @@ function MapNotes({
|
||||
onNoteDragStart,
|
||||
onNoteDragEnd,
|
||||
fadeOnHover,
|
||||
}) {
|
||||
}: MapNoteProps) {
|
||||
const interactionEmitter = useInteractionEmitter();
|
||||
const userId = useUserId();
|
||||
const mapStageRef = useMapStage();
|
||||
const [isBrushDown, setIsBrushDown] = useState(false);
|
||||
const [noteData, setNoteData] = useState(null);
|
||||
const [noteData, setNoteData] = useState<NoteType | null>(null);
|
||||
|
||||
const creatingNoteRef = useRef();
|
||||
const creatingNoteRef = useRef<Konva.Group>(null);
|
||||
|
||||
const snapPositionToGrid = useGridSnapping();
|
||||
|
||||
@ -44,8 +67,14 @@ function MapNotes({
|
||||
const mapStage = mapStageRef.current;
|
||||
|
||||
function getBrushPosition() {
|
||||
if (!mapStage) {
|
||||
return;
|
||||
}
|
||||
const mapImage = mapStage.findOne("#mapImage");
|
||||
let position = getRelativePointerPosition(mapImage);
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
if (map.snapToGrid) {
|
||||
position = snapPositionToGrid(position);
|
||||
}
|
||||
@ -57,6 +86,9 @@ function MapNotes({
|
||||
|
||||
function handleBrushDown() {
|
||||
const brushPosition = getBrushPosition();
|
||||
if (!brushPosition || !userId) {
|
||||
return;
|
||||
}
|
||||
setNoteData({
|
||||
x: brushPosition.x,
|
||||
y: brushPosition.y,
|
||||
@ -76,17 +108,25 @@ function MapNotes({
|
||||
function handleBrushMove() {
|
||||
if (noteData) {
|
||||
const brushPosition = getBrushPosition();
|
||||
setNoteData((prev) => ({
|
||||
...prev,
|
||||
x: brushPosition.x,
|
||||
y: brushPosition.y,
|
||||
}));
|
||||
if (!brushPosition) {
|
||||
return;
|
||||
}
|
||||
setNoteData((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
x: brushPosition.x,
|
||||
y: brushPosition.y,
|
||||
};
|
||||
});
|
||||
setIsBrushDown(true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleBrushUp() {
|
||||
if (noteData) {
|
||||
if (noteData && creatingNoteRef.current) {
|
||||
onNoteAdd(noteData);
|
||||
onNoteMenuOpen(noteData.id, creatingNoteRef.current);
|
||||
}
|
||||
@ -94,14 +134,14 @@ function MapNotes({
|
||||
setIsBrushDown(false);
|
||||
}
|
||||
|
||||
interactionEmitter.on("dragStart", handleBrushDown);
|
||||
interactionEmitter.on("drag", handleBrushMove);
|
||||
interactionEmitter.on("dragEnd", handleBrushUp);
|
||||
interactionEmitter?.on("dragStart", handleBrushDown);
|
||||
interactionEmitter?.on("drag", handleBrushMove);
|
||||
interactionEmitter?.on("dragEnd", handleBrushUp);
|
||||
|
||||
return () => {
|
||||
interactionEmitter.off("dragStart", handleBrushDown);
|
||||
interactionEmitter.off("drag", handleBrushMove);
|
||||
interactionEmitter.off("dragEnd", handleBrushUp);
|
||||
interactionEmitter?.off("dragStart", handleBrushDown);
|
||||
interactionEmitter?.off("drag", handleBrushMove);
|
||||
interactionEmitter?.off("dragEnd", handleBrushUp);
|
||||
};
|
||||
});
|
||||
|
||||
@ -121,9 +161,7 @@ function MapNotes({
|
||||
/>
|
||||
))}
|
||||
<Group ref={creatingNoteRef}>
|
||||
{isBrushDown && noteData && (
|
||||
<Note note={noteData} map={map} draggable={false} />
|
||||
)}
|
||||
{isBrushDown && noteData && <Note note={noteData} map={map} />}
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Group } from "react-konva";
|
||||
|
||||
import {
|
||||
@ -15,7 +15,17 @@ import {
|
||||
} from "../../helpers/konva";
|
||||
import Vector2 from "../../helpers/Vector2";
|
||||
|
||||
import colors from "../../helpers/colors";
|
||||
import colors, { Color } from "../../helpers/colors";
|
||||
|
||||
type MapPointerProps = {
|
||||
active: boolean;
|
||||
position: Vector2;
|
||||
onPointerDown?: (position: Vector2) => void;
|
||||
onPointerMove?: (position: Vector2) => void;
|
||||
onPointerUp?: (position: Vector2) => void;
|
||||
visible: boolean;
|
||||
color: Color;
|
||||
};
|
||||
|
||||
function MapPointer({
|
||||
active,
|
||||
@ -25,7 +35,7 @@ function MapPointer({
|
||||
onPointerUp,
|
||||
visible,
|
||||
color,
|
||||
}) {
|
||||
}: MapPointerProps) {
|
||||
const mapWidth = useMapWidth();
|
||||
const mapHeight = useMapHeight();
|
||||
const interactionEmitter = useInteractionEmitter();
|
||||
@ -40,30 +50,36 @@ function MapPointer({
|
||||
const mapStage = mapStageRef.current;
|
||||
|
||||
function getBrushPosition() {
|
||||
if (!mapStage) {
|
||||
return;
|
||||
}
|
||||
const mapImage = mapStage.findOne("#mapImage");
|
||||
return getRelativePointerPositionNormalized(mapImage);
|
||||
}
|
||||
|
||||
function handleBrushDown() {
|
||||
onPointerDown && onPointerDown(getBrushPosition());
|
||||
const brushPosition = getBrushPosition();
|
||||
brushPosition && onPointerDown?.(brushPosition);
|
||||
}
|
||||
|
||||
function handleBrushMove() {
|
||||
onPointerMove && visible && onPointerMove(getBrushPosition());
|
||||
const brushPosition = getBrushPosition();
|
||||
brushPosition && visible && onPointerMove?.(brushPosition);
|
||||
}
|
||||
|
||||
function handleBrushUp() {
|
||||
onPointerMove && onPointerUp(getBrushPosition());
|
||||
const brushPosition = getBrushPosition();
|
||||
brushPosition && onPointerUp?.(brushPosition);
|
||||
}
|
||||
|
||||
interactionEmitter.on("dragStart", handleBrushDown);
|
||||
interactionEmitter.on("drag", handleBrushMove);
|
||||
interactionEmitter.on("dragEnd", handleBrushUp);
|
||||
interactionEmitter?.on("dragStart", handleBrushDown);
|
||||
interactionEmitter?.on("drag", handleBrushMove);
|
||||
interactionEmitter?.on("dragEnd", handleBrushUp);
|
||||
|
||||
return () => {
|
||||
interactionEmitter.off("dragStart", handleBrushDown);
|
||||
interactionEmitter.off("drag", handleBrushMove);
|
||||
interactionEmitter.off("dragEnd", handleBrushUp);
|
||||
interactionEmitter?.off("dragStart", handleBrushDown);
|
||||
interactionEmitter?.off("drag", handleBrushMove);
|
||||
interactionEmitter?.off("dragEnd", handleBrushUp);
|
||||
};
|
||||
});
|
||||
|
@ -9,8 +9,16 @@ import { mapSources as defaultMapSources } from "../../maps";
|
||||
|
||||
import Divider from "../Divider";
|
||||
import Select from "../Select";
|
||||
import { Map, MapQuality } from "../../types/Map";
|
||||
import { EditFlag, MapState } from "../../types/MapState";
|
||||
import {
|
||||
MapSettingsChangeEventHandler,
|
||||
MapStateSettingsChangeEventHandler,
|
||||
} from "../../types/Events";
|
||||
import { Grid, GridMeasurementType, GridType } from "../../types/Grid";
|
||||
|
||||
const qualitySettings = [
|
||||
type QualityTypeSetting = { value: MapQuality; label: string };
|
||||
const qualitySettings: QualityTypeSetting[] = [
|
||||
{ value: "low", label: "Low" },
|
||||
{ value: "medium", label: "Medium" },
|
||||
{ value: "high", label: "High" },
|
||||
@ -18,42 +26,53 @@ const qualitySettings = [
|
||||
{ value: "original", label: "Original" },
|
||||
];
|
||||
|
||||
const gridTypeSettings = [
|
||||
type GridTypeSetting = { value: GridType; label: string };
|
||||
const gridTypeSettings: GridTypeSetting[] = [
|
||||
{ value: "square", label: "Square" },
|
||||
{ value: "hexVertical", label: "Hex Vertical" },
|
||||
{ value: "hexHorizontal", label: "Hex Horizontal" },
|
||||
];
|
||||
|
||||
const gridSquareMeasurementTypeSettings = [
|
||||
type GridMeasurementTypeSetting = { value: GridMeasurementType; label: string };
|
||||
const gridSquareMeasurementTypeSettings: GridMeasurementTypeSetting[] = [
|
||||
{ value: "chebyshev", label: "Chessboard (D&D 5e)" },
|
||||
{ value: "alternating", label: "Alternating Diagonal (D&D 3.5e)" },
|
||||
{ value: "euclidean", label: "Euclidean" },
|
||||
{ value: "manhattan", label: "Manhattan" },
|
||||
];
|
||||
|
||||
const gridHexMeasurementTypeSettings = [
|
||||
const gridHexMeasurementTypeSettings: GridMeasurementTypeSetting[] = [
|
||||
{ value: "manhattan", label: "Manhattan" },
|
||||
{ value: "euclidean", label: "Euclidean" },
|
||||
];
|
||||
|
||||
type MapSettingsProps = {
|
||||
map: Map;
|
||||
mapState: MapState;
|
||||
onSettingsChange: MapSettingsChangeEventHandler;
|
||||
onStateSettingsChange: MapStateSettingsChangeEventHandler;
|
||||
};
|
||||
|
||||
function MapSettings({
|
||||
map,
|
||||
mapState,
|
||||
onSettingsChange,
|
||||
onStateSettingsChange,
|
||||
}) {
|
||||
function handleFlagChange(event, flag) {
|
||||
}: MapSettingsProps) {
|
||||
function handleFlagChange(
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
flag: EditFlag
|
||||
) {
|
||||
if (event.target.checked) {
|
||||
onStateSettingsChange("editFlags", [...mapState.editFlags, flag]);
|
||||
onStateSettingsChange({ editFlags: [...mapState.editFlags, flag] });
|
||||
} else {
|
||||
onStateSettingsChange(
|
||||
"editFlags",
|
||||
mapState.editFlags.filter((f) => f !== flag)
|
||||
);
|
||||
onStateSettingsChange({
|
||||
editFlags: mapState.editFlags.filter((f) => f !== flag),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleGridSizeXChange(event) {
|
||||
function handleGridSizeXChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseInt(event.target.value) || 0;
|
||||
let grid = {
|
||||
...map.grid,
|
||||
@ -63,10 +82,10 @@ function MapSettings({
|
||||
},
|
||||
};
|
||||
grid.inset = getGridUpdatedInset(grid, map.width, map.height);
|
||||
onSettingsChange("grid", grid);
|
||||
onSettingsChange({ grid });
|
||||
}
|
||||
|
||||
function handleGridSizeYChange(event) {
|
||||
function handleGridSizeYChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseInt(event.target.value) || 0;
|
||||
let grid = {
|
||||
...map.grid,
|
||||
@ -76,12 +95,15 @@ function MapSettings({
|
||||
},
|
||||
};
|
||||
grid.inset = getGridUpdatedInset(grid, map.width, map.height);
|
||||
onSettingsChange("grid", grid);
|
||||
onSettingsChange({ grid });
|
||||
}
|
||||
|
||||
function handleGridTypeChange(option) {
|
||||
function handleGridTypeChange(option: GridTypeSetting | null) {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
const type = option.value;
|
||||
let grid = {
|
||||
let grid: Grid = {
|
||||
...map.grid,
|
||||
type,
|
||||
measurement: {
|
||||
@ -90,10 +112,15 @@ function MapSettings({
|
||||
},
|
||||
};
|
||||
grid.inset = getGridUpdatedInset(grid, map.width, map.height);
|
||||
onSettingsChange("grid", grid);
|
||||
onSettingsChange({ grid });
|
||||
}
|
||||
|
||||
function handleGridMeasurementTypeChange(option) {
|
||||
function handleGridMeasurementTypeChange(
|
||||
option: GridMeasurementTypeSetting | null
|
||||
) {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
const grid = {
|
||||
...map.grid,
|
||||
measurement: {
|
||||
@ -101,10 +128,19 @@ function MapSettings({
|
||||
type: option.value,
|
||||
},
|
||||
};
|
||||
onSettingsChange("grid", grid);
|
||||
onSettingsChange({ grid });
|
||||
}
|
||||
|
||||
function handleGridMeasurementScaleChange(event) {
|
||||
function handleQualityChange(option: QualityTypeSetting | null) {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
onSettingsChange({ quality: option.value });
|
||||
}
|
||||
|
||||
function handleGridMeasurementScaleChange(
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) {
|
||||
const grid = {
|
||||
...map.grid,
|
||||
measurement: {
|
||||
@ -112,7 +148,7 @@ function MapSettings({
|
||||
scale: event.target.value,
|
||||
},
|
||||
};
|
||||
onSettingsChange("grid", grid);
|
||||
onSettingsChange({ grid });
|
||||
}
|
||||
|
||||
const mapURL = useDataURL(map, defaultMapSources);
|
||||
@ -124,7 +160,7 @@ function MapSettings({
|
||||
const blob = await response.blob();
|
||||
let size = blob.size;
|
||||
size /= 1000000; // Bytes to Megabytes
|
||||
setMapSize(size.toFixed(2));
|
||||
setMapSize(parseFloat(size.toFixed(2)));
|
||||
} else {
|
||||
setMapSize(0);
|
||||
}
|
||||
@ -168,7 +204,7 @@ function MapSettings({
|
||||
<Input
|
||||
name="name"
|
||||
value={(map && map.name) || ""}
|
||||
onChange={(e) => onSettingsChange("name", e.target.value)}
|
||||
onChange={(e) => onSettingsChange({ name: e.target.value })}
|
||||
disabled={mapEmpty}
|
||||
my={1}
|
||||
/>
|
||||
@ -185,10 +221,11 @@ function MapSettings({
|
||||
isDisabled={mapEmpty}
|
||||
options={gridTypeSettings}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
gridTypeSettings.find((s) => s.value === map.grid.type)
|
||||
mapEmpty
|
||||
? undefined
|
||||
: gridTypeSettings.find((s) => s.value === map.grid.type)
|
||||
}
|
||||
onChange={handleGridTypeChange}
|
||||
onChange={handleGridTypeChange as any}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</Box>
|
||||
@ -197,7 +234,9 @@ function MapSettings({
|
||||
<Checkbox
|
||||
checked={!mapEmpty && map.showGrid}
|
||||
disabled={mapEmpty}
|
||||
onChange={(e) => onSettingsChange("showGrid", e.target.checked)}
|
||||
onChange={(e) =>
|
||||
onSettingsChange({ showGrid: e.target.checked })
|
||||
}
|
||||
/>
|
||||
Draw Grid
|
||||
</Label>
|
||||
@ -206,7 +245,7 @@ function MapSettings({
|
||||
checked={!mapEmpty && map.snapToGrid}
|
||||
disabled={mapEmpty}
|
||||
onChange={(e) =>
|
||||
onSettingsChange("snapToGrid", e.target.checked)
|
||||
onSettingsChange({ snapToGrid: e.target.checked })
|
||||
}
|
||||
/>
|
||||
Snap to Grid
|
||||
@ -224,12 +263,13 @@ function MapSettings({
|
||||
: gridHexMeasurementTypeSettings
|
||||
}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
gridSquareMeasurementTypeSettings.find(
|
||||
(s) => s.value === map.grid.measurement.type
|
||||
)
|
||||
mapEmpty
|
||||
? undefined
|
||||
: gridSquareMeasurementTypeSettings.find(
|
||||
(s) => s.value === map.grid.measurement.type
|
||||
)
|
||||
}
|
||||
onChange={handleGridMeasurementTypeChange}
|
||||
onChange={handleGridMeasurementTypeChange as any}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</Box>
|
||||
@ -254,14 +294,17 @@ function MapSettings({
|
||||
<Select
|
||||
options={qualitySettings}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
qualitySettings.find((s) => s.value === map.quality)
|
||||
mapEmpty
|
||||
? undefined
|
||||
: qualitySettings.find((s) => s.value === map.quality)
|
||||
}
|
||||
isDisabled={mapEmpty}
|
||||
onChange={(option) => onSettingsChange("quality", option.value)}
|
||||
isOptionDisabled={(option) =>
|
||||
mapEmpty ||
|
||||
(option.value !== "original" && !map.resolutions[option.value])
|
||||
onChange={handleQualityChange as any}
|
||||
isOptionDisabled={
|
||||
((option: QualityTypeSetting) =>
|
||||
mapEmpty ||
|
||||
(option.value !== "original" &&
|
||||
!map.resolutions[option.value])) as any
|
||||
}
|
||||
isSearchable={false}
|
||||
/>
|
@ -1,8 +1,19 @@
|
||||
import React from "react";
|
||||
import { Map } from "../../types/Map";
|
||||
|
||||
import Tile from "../tile/Tile";
|
||||
import MapImage from "./MapTileImage";
|
||||
|
||||
type MapTileProps = {
|
||||
map: Map;
|
||||
isSelected: boolean;
|
||||
onSelect: (mapId: string) => void;
|
||||
onEdit: (mapId: string) => void;
|
||||
onDoubleClick: () => void;
|
||||
canEdit: boolean;
|
||||
badges: React.ReactChild[];
|
||||
};
|
||||
|
||||
function MapTile({
|
||||
map,
|
||||
isSelected,
|
||||
@ -11,7 +22,7 @@ function MapTile({
|
||||
onDoubleClick,
|
||||
canEdit,
|
||||
badges,
|
||||
}) {
|
||||
}: MapTileProps) {
|
||||
return (
|
||||
<Tile
|
||||
title={map.name}
|
@ -6,7 +6,24 @@ import MapImage from "./MapTileImage";
|
||||
|
||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||
|
||||
function MapTileGroup({ group, maps, isSelected, onSelect, onDoubleClick }) {
|
||||
import { Map } from "../../types/Map";
|
||||
import { GroupContainer } from "../../types/Group";
|
||||
|
||||
type MapTileGroupProps = {
|
||||
group: GroupContainer;
|
||||
maps: Map[];
|
||||
isSelected: boolean;
|
||||
onSelect: (groupId: string) => void;
|
||||
onDoubleClick: () => void;
|
||||
};
|
||||
|
||||
function MapTileGroup({
|
||||
group,
|
||||
maps,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDoubleClick,
|
||||
}: MapTileGroupProps) {
|
||||
const layout = useResponsiveLayout();
|
||||
|
||||
return (
|
@ -10,11 +10,26 @@ import { getGroupItems } from "../../helpers/group";
|
||||
|
||||
import { useGroup } from "../../contexts/GroupContext";
|
||||
|
||||
function MapTiles({ mapsById, onMapEdit, onMapSelect, subgroup }) {
|
||||
import { Map } from "../../types/Map";
|
||||
import { Group } from "../../types/Group";
|
||||
|
||||
type MapTileProps = {
|
||||
mapsById: Record<string, Map>;
|
||||
onMapEdit: (mapId: string) => void;
|
||||
onMapSelect: (groupId: string) => void;
|
||||
subgroup: boolean;
|
||||
};
|
||||
|
||||
function MapTiles({
|
||||
mapsById,
|
||||
onMapEdit,
|
||||
onMapSelect,
|
||||
subgroup,
|
||||
}: MapTileProps) {
|
||||
const { selectedGroupIds, selectMode, onGroupOpen, onGroupSelect } =
|
||||
useGroup();
|
||||
|
||||
function renderTile(group) {
|
||||
function renderTile(group: Group) {
|
||||
if (group.type === "item") {
|
||||
const map = mapsById[group.id];
|
||||
if (map) {
|
@ -31,7 +31,7 @@ import {
|
||||
TokenStateChangeEventHandler,
|
||||
} from "../../types/Events";
|
||||
|
||||
type MapTokenStateProps = {
|
||||
type MapTokenProps = {
|
||||
tokenState: TokenState;
|
||||
onTokenStateChange: TokenStateChangeEventHandler;
|
||||
onTokenMenuOpen: TokenMenuOpenChangeEventHandler;
|
||||
@ -51,7 +51,7 @@ function MapToken({
|
||||
draggable,
|
||||
fadeOnHover,
|
||||
map,
|
||||
}: MapTokenStateProps) {
|
||||
}: MapTokenProps) {
|
||||
const userId = useUserId();
|
||||
|
||||
const mapWidth = useMapWidth();
|
||||
|
@ -1,19 +1,37 @@
|
||||
import React from "react";
|
||||
import { Group } from "react-konva";
|
||||
import {
|
||||
TokenMenuOpenChangeEventHandler,
|
||||
TokenStateChangeEventHandler,
|
||||
} from "../../types/Events";
|
||||
import { Map, MapToolId } from "../../types/Map";
|
||||
import { MapState } from "../../types/MapState";
|
||||
import { TokenCategory, TokenDraggingOptions } from "../../types/Token";
|
||||
import { TokenState } from "../../types/TokenState";
|
||||
|
||||
import MapToken from "./MapToken";
|
||||
|
||||
type MapTokensProps = {
|
||||
map: Map;
|
||||
mapState: MapState;
|
||||
tokenDraggingOptions?: TokenDraggingOptions;
|
||||
setTokenDraggingOptions: (options: TokenDraggingOptions) => void;
|
||||
onMapTokenStateChange: TokenStateChangeEventHandler;
|
||||
onTokenMenuOpen: TokenMenuOpenChangeEventHandler;
|
||||
selectedToolId: MapToolId;
|
||||
disabledTokens: string[];
|
||||
};
|
||||
|
||||
function MapTokens({
|
||||
map,
|
||||
mapState,
|
||||
tokenDraggingOptions,
|
||||
setTokenDraggingOptions,
|
||||
onMapTokenStateChange,
|
||||
handleTokenMenuOpen,
|
||||
onTokenMenuOpen,
|
||||
selectedToolId,
|
||||
disabledTokens,
|
||||
}) {
|
||||
function getMapTokenCategoryWeight(category) {
|
||||
}: MapTokensProps) {
|
||||
function getMapTokenCategoryWeight(category: TokenCategory) {
|
||||
switch (category) {
|
||||
case "character":
|
||||
return 0;
|
||||
@ -27,7 +45,11 @@ function MapTokens({
|
||||
}
|
||||
|
||||
// Sort so vehicles render below other tokens
|
||||
function sortMapTokenStates(a, b, tokenDraggingOptions) {
|
||||
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);
|
||||
@ -62,7 +84,7 @@ function MapTokens({
|
||||
key={tokenState.id}
|
||||
tokenState={tokenState}
|
||||
onTokenStateChange={onMapTokenStateChange}
|
||||
onTokenMenuOpen={handleTokenMenuOpen}
|
||||
onTokenMenuOpen={onTokenMenuOpen}
|
||||
onTokenDragStart={(e) =>
|
||||
setTokenDraggingOptions({
|
||||
dragging: true,
|
||||
@ -71,6 +93,7 @@ function MapTokens({
|
||||
})
|
||||
}
|
||||
onTokenDragEnd={() =>
|
||||
tokenDraggingOptions &&
|
||||
setTokenDraggingOptions({
|
||||
...tokenDraggingOptions,
|
||||
dragging: false,
|
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { Rect, Text } from "react-konva";
|
||||
import Konva from "konva";
|
||||
import { useSpring, animated } from "@react-spring/konva";
|
||||
|
||||
import { useUserId } from "../../contexts/UserIdContext";
|
||||
@ -15,8 +16,27 @@ import colors from "../../helpers/colors";
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
import useGridSnapping from "../../hooks/useGridSnapping";
|
||||
|
||||
import { Note as NoteType } from "../../types/Note";
|
||||
import {
|
||||
NoteChangeEventHandler,
|
||||
NoteDragEventHandler,
|
||||
NoteMenuOpenEventHandler,
|
||||
} from "../../types/Events";
|
||||
import { Map } from "../../types/Map";
|
||||
|
||||
const defaultFontSize = 16;
|
||||
|
||||
type NoteProps = {
|
||||
note: NoteType;
|
||||
map: Map;
|
||||
onNoteChange?: NoteChangeEventHandler;
|
||||
onNoteMenuOpen?: NoteMenuOpenEventHandler;
|
||||
draggable: boolean;
|
||||
onNoteDragStart?: NoteDragEventHandler;
|
||||
onNoteDragEnd?: NoteDragEventHandler;
|
||||
fadeOnHover: boolean;
|
||||
};
|
||||
|
||||
function Note({
|
||||
note,
|
||||
map,
|
||||
@ -26,7 +46,7 @@ function Note({
|
||||
onNoteDragStart,
|
||||
onNoteDragEnd,
|
||||
fadeOnHover,
|
||||
}) {
|
||||
}: NoteProps) {
|
||||
const userId = useUserId();
|
||||
|
||||
const mapWidth = useMapWidth();
|
||||
@ -45,11 +65,11 @@ function Note({
|
||||
|
||||
const snapPositionToGrid = useGridSnapping();
|
||||
|
||||
function handleDragStart(event) {
|
||||
onNoteDragStart && onNoteDragStart(event, note.id);
|
||||
function handleDragStart(event: Konva.KonvaEventObject<DragEvent>) {
|
||||
onNoteDragStart?.(event, note.id);
|
||||
}
|
||||
|
||||
function handleDragMove(event) {
|
||||
function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) {
|
||||
const noteGroup = event.target;
|
||||
// Snap to corners of grid
|
||||
if (map.snapToGrid) {
|
||||
@ -57,21 +77,20 @@ function Note({
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragEnd(event) {
|
||||
function handleDragEnd(event: Konva.KonvaEventObject<DragEvent>) {
|
||||
const noteGroup = event.target;
|
||||
onNoteChange &&
|
||||
onNoteChange({
|
||||
...note,
|
||||
x: noteGroup.x() / mapWidth,
|
||||
y: noteGroup.y() / mapHeight,
|
||||
lastModifiedBy: userId,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
onNoteDragEnd && onNoteDragEnd(note.id);
|
||||
onNoteChange?.({
|
||||
...note,
|
||||
x: noteGroup.x() / mapWidth,
|
||||
y: noteGroup.y() / mapHeight,
|
||||
lastModifiedBy: userId,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
onNoteDragEnd?.(event, note.id);
|
||||
setPreventMapInteraction(false);
|
||||
}
|
||||
|
||||
function handleClick(event) {
|
||||
function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
|
||||
if (draggable) {
|
||||
const noteNode = event.target;
|
||||
onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode);
|
||||
@ -79,8 +98,8 @@ function Note({
|
||||
}
|
||||
|
||||
// Store note pointer down time to check for a click when note is locked
|
||||
const notePointerDownTimeRef = useRef();
|
||||
function handlePointerDown(event) {
|
||||
const notePointerDownTimeRef = useRef<number>(0);
|
||||
function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
|
||||
if (draggable) {
|
||||
setPreventMapInteraction(true);
|
||||
}
|
||||
@ -89,7 +108,7 @@ function Note({
|
||||
}
|
||||
}
|
||||
|
||||
function handlePointerUp(event) {
|
||||
function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
|
||||
if (draggable) {
|
||||
setPreventMapInteraction(false);
|
||||
}
|
||||
@ -100,7 +119,7 @@ function Note({
|
||||
const delta = event.evt.timeStamp - notePointerDownTimeRef.current;
|
||||
if (delta < 300) {
|
||||
const noteNode = event.target;
|
||||
onNoteMenuOpen(note.id, noteNode);
|
||||
onNoteMenuOpen?.(note.id, noteNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,12 +140,10 @@ function Note({
|
||||
const [fontScale, setFontScale] = useState(1);
|
||||
useEffect(() => {
|
||||
const text = textRef.current;
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
function findFontSize() {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
// Create an array from 1 / defaultFontSize of the note height to the full note height
|
||||
let sizes = Array.from(
|
||||
{ length: Math.ceil(noteHeight - notePadding * 2) },
|
||||
@ -151,7 +168,7 @@ function Note({
|
||||
findFontSize();
|
||||
}, [note, note.text, note.visible, noteWidth, noteHeight, notePadding]);
|
||||
|
||||
const textRef = useRef();
|
||||
const textRef = useRef<Konva.Text>(null);
|
||||
|
||||
// Animate to new note positions if edited by others
|
||||
const noteX = note.x * mapWidth;
|
||||
@ -229,4 +246,9 @@ function Note({
|
||||
);
|
||||
}
|
||||
|
||||
Note.defaultProps = {
|
||||
fadeOnHover: false,
|
||||
draggable: false,
|
||||
};
|
||||
|
||||
export default Note;
|
@ -1,19 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import DragOverlay from "../map/DragOverlay";
|
||||
|
||||
function NoteDragOverlay({ onNoteRemove, noteId, noteGroup, dragging }) {
|
||||
function handleNoteRemove() {
|
||||
onNoteRemove(noteId);
|
||||
}
|
||||
|
||||
return (
|
||||
<DragOverlay
|
||||
dragging={dragging}
|
||||
onRemove={handleNoteRemove}
|
||||
node={noteGroup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoteDragOverlay;
|
31
src/components/note/NoteDragOverlay.tsx
Normal file
31
src/components/note/NoteDragOverlay.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import Konva from "konva";
|
||||
|
||||
import DragOverlay from "../map/DragOverlay";
|
||||
|
||||
type NoteDragOverlayProps = {
|
||||
onNoteRemove: (noteId: string) => void;
|
||||
noteId: string;
|
||||
noteGroup: Konva.Node;
|
||||
dragging: boolean;
|
||||
};
|
||||
|
||||
function NoteDragOverlay({
|
||||
onNoteRemove,
|
||||
noteId,
|
||||
noteGroup,
|
||||
dragging,
|
||||
}: NoteDragOverlayProps) {
|
||||
function handleNoteRemove() {
|
||||
onNoteRemove(noteId);
|
||||
}
|
||||
|
||||
return (
|
||||
<DragOverlay
|
||||
dragging={dragging}
|
||||
onRemove={handleNoteRemove}
|
||||
node={noteGroup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoteDragOverlay;
|
@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Flex, Text, IconButton } from "theme-ui";
|
||||
import Konva from "konva";
|
||||
|
||||
import Slider from "../Slider";
|
||||
import TextareaAutosize from "../TextareaAutoSize";
|
||||
|
||||
import MapMenu from "../map/MapMenu";
|
||||
|
||||
import colors, { colorOptions } from "../../helpers/colors";
|
||||
import colors, { Color, colorOptions } from "../../helpers/colors";
|
||||
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
|
||||
@ -19,8 +20,24 @@ import TextIcon from "../../icons/NoteTextIcon";
|
||||
|
||||
import { useUserId } from "../../contexts/UserIdContext";
|
||||
|
||||
import {
|
||||
NoteChangeEventHandler,
|
||||
RequestCloseEventHandler,
|
||||
} from "../../types/Events";
|
||||
import { Note } from "../../types/Note";
|
||||
import { Map } from "../../types/Map";
|
||||
|
||||
const defaultNoteMaxSize = 6;
|
||||
|
||||
type NoteMenuProps = {
|
||||
isOpen: boolean;
|
||||
onRequestClose: RequestCloseEventHandler;
|
||||
note?: Note;
|
||||
noteNode?: Konva.Node;
|
||||
onNoteChange: NoteChangeEventHandler;
|
||||
map: Map;
|
||||
};
|
||||
|
||||
function NoteMenu({
|
||||
isOpen,
|
||||
onRequestClose,
|
||||
@ -28,7 +45,7 @@ function NoteMenu({
|
||||
noteNode,
|
||||
onNoteChange,
|
||||
map,
|
||||
}) {
|
||||
}: NoteMenuProps) {
|
||||
const userId = useUserId();
|
||||
|
||||
const wasOpen = usePrevious(isOpen);
|
||||
@ -43,29 +60,30 @@ function NoteMenu({
|
||||
if (noteNode) {
|
||||
const nodeRect = noteNode.getClientRect();
|
||||
const mapElement = document.querySelector(".map");
|
||||
const mapRect = mapElement.getBoundingClientRect();
|
||||
|
||||
// Center X for the menu which is 156px wide
|
||||
setMenuLeft(mapRect.left + nodeRect.x + nodeRect.width / 2 - 156 / 2);
|
||||
// Y 12px from the bottom
|
||||
setMenuTop(mapRect.top + nodeRect.y + nodeRect.height + 12);
|
||||
if (mapElement) {
|
||||
const mapRect = mapElement.getBoundingClientRect();
|
||||
// Center X for the menu which is 156px wide
|
||||
setMenuLeft(mapRect.left + nodeRect.x + nodeRect.width / 2 - 156 / 2);
|
||||
// Y 12px from the bottom
|
||||
setMenuTop(mapRect.top + nodeRect.y + nodeRect.height + 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isOpen, note, wasOpen, noteNode]);
|
||||
|
||||
function handleTextChange(event) {
|
||||
function handleTextChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
const text = event.target.value.substring(0, 1024);
|
||||
note && onNoteChange({ ...note, text: text });
|
||||
}
|
||||
|
||||
function handleColorChange(color) {
|
||||
function handleColorChange(color: Color) {
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
onNoteChange({ ...note, color: color });
|
||||
}
|
||||
|
||||
function handleSizeChange(event) {
|
||||
function handleSizeChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const newSize = parseFloat(event.target.value);
|
||||
note && onNoteChange({ ...note, size: newSize });
|
||||
}
|
||||
@ -82,30 +100,35 @@ function NoteMenu({
|
||||
note && onNoteChange({ ...note, textOnly: !note.textOnly });
|
||||
}
|
||||
|
||||
function handleModalContent(node) {
|
||||
function handleModalContent(node: HTMLElement) {
|
||||
if (node) {
|
||||
// Focus input
|
||||
const tokenLabelInput = node.querySelector("#changeNoteText");
|
||||
tokenLabelInput.focus();
|
||||
tokenLabelInput.select();
|
||||
const tokenLabelInput =
|
||||
node.querySelector<HTMLInputElement>("#changeNoteText");
|
||||
if (tokenLabelInput) {
|
||||
tokenLabelInput.focus();
|
||||
tokenLabelInput.select();
|
||||
}
|
||||
|
||||
// Ensure menu is in bounds
|
||||
const nodeRect = node.getBoundingClientRect();
|
||||
const mapElement = document.querySelector(".map");
|
||||
const mapRect = mapElement.getBoundingClientRect();
|
||||
setMenuLeft((prevLeft) =>
|
||||
Math.min(
|
||||
mapRect.right - nodeRect.width,
|
||||
Math.max(mapRect.left, prevLeft)
|
||||
)
|
||||
);
|
||||
setMenuTop((prevTop) =>
|
||||
Math.min(mapRect.bottom - nodeRect.height, prevTop)
|
||||
);
|
||||
if (mapElement) {
|
||||
const mapRect = mapElement.getBoundingClientRect();
|
||||
setMenuLeft((prevLeft) =>
|
||||
Math.min(
|
||||
mapRect.right - nodeRect.width,
|
||||
Math.max(mapRect.left, prevLeft)
|
||||
)
|
||||
);
|
||||
setMenuTop((prevTop) =>
|
||||
Math.min(mapRect.bottom - nodeRect.height, prevTop)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleTextKeyPress(e) {
|
||||
function handleTextKeyPress(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
onRequestClose();
|
@ -23,33 +23,21 @@ import TokenBar from "../components/token/TokenBar";
|
||||
|
||||
import GlobalImageDrop from "../components/image/GlobalImageDrop";
|
||||
|
||||
import { Map as MapType } from "../types/Map";
|
||||
import {
|
||||
Map as MapType,
|
||||
MapActions,
|
||||
MapActionsIndexKey,
|
||||
MapActionsKey,
|
||||
} from "../types/Map";
|
||||
import { MapState } from "../types/MapState";
|
||||
import {
|
||||
Asset,
|
||||
AssetManifest,
|
||||
AssetManifestAsset,
|
||||
AssetManifestAssets,
|
||||
} from "../types/Asset";
|
||||
import { TokenState } from "../types/TokenState";
|
||||
import { Drawing, DrawingState } from "../types/Drawing";
|
||||
import { Fog, FogState } from "../types/Fog";
|
||||
|
||||
type MapActions = {
|
||||
mapDrawActions: Action<Drawing>[];
|
||||
mapDrawActionIndex: number;
|
||||
fogDrawActions: Action<Fog>[];
|
||||
fogDrawActionIndex: number;
|
||||
};
|
||||
|
||||
type MapActionsKey = keyof Pick<
|
||||
MapActions,
|
||||
"mapDrawActions" | "fogDrawActions"
|
||||
>;
|
||||
type MapActionsIndexKey = keyof Pick<
|
||||
MapActions,
|
||||
"mapDrawActionIndex" | "fogDrawActionIndex"
|
||||
>;
|
||||
import { DrawingState } from "../types/Drawing";
|
||||
import { FogState } from "../types/Fog";
|
||||
|
||||
const defaultMapActions: MapActions = {
|
||||
mapDrawActions: [],
|
||||
|
@ -2,22 +2,39 @@ import Konva from "konva";
|
||||
import { DefaultDice } from "./Dice";
|
||||
import { Map } from "./Map";
|
||||
import { MapState } from "./MapState";
|
||||
import { Note } from "./Note";
|
||||
import { TokenState } from "./TokenState";
|
||||
|
||||
export type MapChangeEventHandler = (map?: Map, mapState?: MapState) => void;
|
||||
|
||||
export type MapResetEventHandler = (newState: MapState) => void;
|
||||
export type MapSettingsChangeEventHandler = (change: Partial<Map>) => void;
|
||||
export type MapStateSettingsChangeEventHandler = (
|
||||
change: Partial<MapState>
|
||||
) => void;
|
||||
|
||||
export type DiceSelectEventHandler = (dice: DefaultDice) => void;
|
||||
|
||||
export type RequestCloseEventHandler = () => void;
|
||||
|
||||
export type MapTokensStateCreateHandler = (states: TokenState[]) => void;
|
||||
export type MapTokenStateRemoveHandler = (state: TokenState) => void;
|
||||
|
||||
export type TokenStateChangeEventHandler = (
|
||||
change: Partial<Record<string, Partial<TokenState>>>
|
||||
changes: Record<string, Partial<TokenState>>
|
||||
) => void;
|
||||
export type TokenMenuOpenChangeEventHandler = (
|
||||
tokenStateId: string,
|
||||
tokenImage: Konva.Node
|
||||
) => void;
|
||||
|
||||
export type NoteAddEventHander = (note: Note) => void;
|
||||
export type NoteRemoveEventHander = (noteId: string) => void;
|
||||
export type NoteChangeEventHandler = (change: Partial<Note>) => void;
|
||||
export type NoteMenuOpenEventHandler = (
|
||||
noteId: string,
|
||||
noteNode: Konva.Node
|
||||
) => void;
|
||||
export type NoteDragEventHandler = (
|
||||
event: Konva.KonvaEventObject<DragEvent>,
|
||||
noteId: string
|
||||
) => void;
|
||||
|
@ -1,7 +1,11 @@
|
||||
import React from "react";
|
||||
import Action from "../actions/Action";
|
||||
import { Drawing } from "./Drawing";
|
||||
import { Fog } from "./Fog";
|
||||
import { Grid } from "./Grid";
|
||||
|
||||
export type MapToolId =
|
||||
| "map"
|
||||
| "move"
|
||||
| "fog"
|
||||
| "drawing"
|
||||
@ -42,12 +46,30 @@ export type FileMapResolutions = {
|
||||
ultra?: string;
|
||||
};
|
||||
|
||||
export type MapQuality = keyof FileMapResolutions | "original";
|
||||
|
||||
export type FileMap = BaseMap & {
|
||||
type: "file";
|
||||
file: string;
|
||||
resolutions: FileMapResolutions;
|
||||
thumbnail: string;
|
||||
quality: keyof FileMapResolutions | "original";
|
||||
quality: MapQuality;
|
||||
};
|
||||
|
||||
export type Map = DefaultMap | FileMap;
|
||||
|
||||
export type MapActions = {
|
||||
mapDrawActions: Action<Drawing>[];
|
||||
mapDrawActionIndex: number;
|
||||
fogDrawActions: Action<Fog>[];
|
||||
fogDrawActionIndex: number;
|
||||
};
|
||||
|
||||
export type MapActionsKey = keyof Pick<
|
||||
MapActions,
|
||||
"mapDrawActions" | "fogDrawActions"
|
||||
>;
|
||||
export type MapActionsIndexKey = keyof Pick<
|
||||
MapActions,
|
||||
"mapDrawActionIndex" | "fogDrawActionIndex"
|
||||
>;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Konva from "konva";
|
||||
import { Color } from "../helpers/colors";
|
||||
|
||||
export type Note = {
|
||||
@ -13,3 +14,14 @@ export type Note = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type NoteMenuOptions = {
|
||||
noteId: string;
|
||||
noteNode: Konva.Node;
|
||||
};
|
||||
|
||||
export type NoteDraggingOptions = {
|
||||
dragging: boolean;
|
||||
noteId: string;
|
||||
noteGroup: Konva.Node;
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
import Konva from "konva";
|
||||
import { Outline } from "./Outline";
|
||||
import { TokenState } from "./TokenState";
|
||||
|
||||
export type TokenCategory = "character" | "vehicle" | "prop";
|
||||
|
||||
@ -29,3 +31,14 @@ export type FileToken = BaseToken & {
|
||||
};
|
||||
|
||||
export type Token = DefaultToken | FileToken;
|
||||
|
||||
export type TokenMenuOptions = {
|
||||
tokenStateId: string;
|
||||
tokenImage: Konva.Node;
|
||||
};
|
||||
|
||||
export type TokenDraggingOptions = {
|
||||
dragging: boolean;
|
||||
tokenState: TokenState;
|
||||
tokenGroup: Konva.Node;
|
||||
};
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Outline } from "./Outline";
|
||||
import { TokenCategory } from "./Token";
|
||||
|
||||
export type BaseTokenState = {
|
||||
id: string;
|
||||
tokenId: string;
|
||||
owner: string;
|
||||
size: number;
|
||||
category: string;
|
||||
category: TokenCategory;
|
||||
label: string;
|
||||
statuses: string[];
|
||||
x: number;
|
||||
|
Loading…
Reference in New Issue
Block a user