Typescript
This commit is contained in:
parent
2ce9d2dd08
commit
a1fb67df7b
@ -3,13 +3,12 @@ import { Box } from "theme-ui";
|
|||||||
|
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
|
|
||||||
function LoadingOverlay({
|
type LoadingOverlayProps = {
|
||||||
bg,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
bg: string;
|
bg: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
};
|
||||||
|
|
||||||
|
function LoadingOverlay({ bg, children }: LoadingOverlayProps) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -2,6 +2,7 @@ import React, { ReactChild } from "react";
|
|||||||
import Modal, { Props } from "react-modal";
|
import Modal, { Props } from "react-modal";
|
||||||
import { useThemeUI, Close } from "theme-ui";
|
import { useThemeUI, Close } from "theme-ui";
|
||||||
import { useSpring, animated, config } from "react-spring";
|
import { useSpring, animated, config } from "react-spring";
|
||||||
|
import CSS from "csstype";
|
||||||
|
|
||||||
type ModalProps = Props & {
|
type ModalProps = Props & {
|
||||||
children: ReactChild | ReactChild[];
|
children: ReactChild | ReactChild[];
|
||||||
@ -38,7 +39,7 @@ function StyledModal({
|
|||||||
...(style?.overlay || {}),
|
...(style?.overlay || {}),
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
backgroundColor: theme.colors.background,
|
backgroundColor: theme.colors?.background as CSS.Property.Color,
|
||||||
top: "initial",
|
top: "initial",
|
||||||
left: "initial",
|
left: "initial",
|
||||||
bottom: "initial",
|
bottom: "initial",
|
||||||
|
@ -9,7 +9,7 @@ type SelectProps = {
|
|||||||
function Select({ creatable, ...props }: SelectProps) {
|
function Select({ creatable, ...props }: SelectProps) {
|
||||||
const { theme } = useThemeUI();
|
const { theme } = useThemeUI();
|
||||||
|
|
||||||
const Component = creatable ? Creatable : (ReactSelect as any);
|
const Component: any = creatable ? Creatable : ReactSelect;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
|
@ -3,19 +3,21 @@ import { useThemeUI, Close } from "theme-ui";
|
|||||||
import { RequestCloseEventHandler } from "../../types/Events";
|
import { RequestCloseEventHandler } from "../../types/Events";
|
||||||
import CSS from "csstype";
|
import CSS from "csstype";
|
||||||
|
|
||||||
|
type BannerProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onRequestClose: RequestCloseEventHandler;
|
||||||
|
children: React.ReactNode;
|
||||||
|
allowClose: boolean;
|
||||||
|
backgroundColor?: CSS.Property.Color;
|
||||||
|
};
|
||||||
|
|
||||||
function Banner({
|
function Banner({
|
||||||
isOpen,
|
isOpen,
|
||||||
onRequestClose,
|
onRequestClose,
|
||||||
children,
|
children,
|
||||||
allowClose,
|
allowClose,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
}: {
|
}: BannerProps) {
|
||||||
isOpen: boolean;
|
|
||||||
onRequestClose: RequestCloseEventHandler;
|
|
||||||
children: React.ReactNode;
|
|
||||||
allowClose: boolean;
|
|
||||||
backgroundColor?: CSS.Property.Color;
|
|
||||||
}) {
|
|
||||||
const { theme } = useThemeUI();
|
const { theme } = useThemeUI();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,13 +2,14 @@ import { Box, Text } from "theme-ui";
|
|||||||
|
|
||||||
import Banner from "./Banner";
|
import Banner from "./Banner";
|
||||||
|
|
||||||
function ErrorBanner({
|
import { RequestCloseEventHandler } from "../../types/Events";
|
||||||
error,
|
|
||||||
onRequestClose,
|
type ErrorBannerProps = {
|
||||||
}: {
|
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
onRequestClose;
|
onRequestClose: RequestCloseEventHandler;
|
||||||
}) {
|
};
|
||||||
|
|
||||||
|
function ErrorBanner({ error, onRequestClose }: ErrorBannerProps) {
|
||||||
return (
|
return (
|
||||||
<Banner isOpen={!!error} onRequestClose={onRequestClose}>
|
<Banner isOpen={!!error} onRequestClose={onRequestClose}>
|
||||||
<Box p={1}>
|
<Box p={1}>
|
||||||
|
@ -24,16 +24,16 @@ import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
|
|||||||
|
|
||||||
const diceThrowSpeed = 2;
|
const diceThrowSpeed = 2;
|
||||||
|
|
||||||
|
type SceneMountEvent = {
|
||||||
|
scene: Scene;
|
||||||
|
engine: Engine;
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SceneMountEventHandler = (event: SceneMountEvent) => void;
|
||||||
|
|
||||||
type DiceInteractionProps = {
|
type DiceInteractionProps = {
|
||||||
onSceneMount?: ({
|
onSceneMount?: SceneMountEventHandler;
|
||||||
scene,
|
|
||||||
engine,
|
|
||||||
canvas,
|
|
||||||
}: {
|
|
||||||
scene: Scene;
|
|
||||||
engine: Engine;
|
|
||||||
canvas: HTMLCanvasElement | WebGLRenderingContext;
|
|
||||||
}) => void;
|
|
||||||
onPointerDown: () => void;
|
onPointerDown: () => void;
|
||||||
onPointerUp: () => void;
|
onPointerUp: () => void;
|
||||||
};
|
};
|
||||||
|
@ -112,7 +112,9 @@ function GlobalImageDrop({
|
|||||||
// Change map if only 1 dropped
|
// Change map if only 1 dropped
|
||||||
if (maps.length === 1) {
|
if (maps.length === 1) {
|
||||||
const mapState = await getMapState(maps[0].id);
|
const mapState = await getMapState(maps[0].id);
|
||||||
onMapChange(maps[0], mapState);
|
if (mapState) {
|
||||||
|
onMapChange(maps[0], mapState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
@ -208,7 +208,9 @@ function MapContols({
|
|||||||
>
|
>
|
||||||
<Settings
|
<Settings
|
||||||
settings={toolSettings[selectedToolId]}
|
settings={toolSettings[selectedToolId]}
|
||||||
onSettingChange={(change) =>
|
onSettingChange={(
|
||||||
|
change: Partial<Settings["fog" | "drawing" | "pointer"]>
|
||||||
|
) =>
|
||||||
onToolSettingChange({
|
onToolSettingChange({
|
||||||
[selectedToolId]: {
|
[selectedToolId]: {
|
||||||
...toolSettings[selectedToolId],
|
...toolSettings[selectedToolId],
|
||||||
|
@ -38,7 +38,7 @@ function MapEditBar({
|
|||||||
|
|
||||||
const { maps, mapStates, removeMaps, resetMap } = useMapData();
|
const { maps, mapStates, removeMaps, resetMap } = useMapData();
|
||||||
|
|
||||||
const { activeGroups, selectedGroupIds, onGroupSelect } = useGroup();
|
const { activeGroups, selectedGroupIds, onClearSelection } = useGroup();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedGroups = groupsFromIds(selectedGroupIds, activeGroups);
|
const selectedGroups = groupsFromIds(selectedGroupIds, activeGroups);
|
||||||
@ -75,7 +75,7 @@ function MapEditBar({
|
|||||||
setIsMapsRemoveModalOpen(false);
|
setIsMapsRemoveModalOpen(false);
|
||||||
const selectedMaps = getSelectedMaps();
|
const selectedMaps = getSelectedMaps();
|
||||||
const selectedMapIds = selectedMaps.map((map) => map.id);
|
const selectedMapIds = selectedMaps.map((map) => map.id);
|
||||||
onGroupSelect(undefined);
|
onClearSelection();
|
||||||
await removeMaps(selectedMapIds);
|
await removeMaps(selectedMapIds);
|
||||||
// Removed the map from the map screen if needed
|
// Removed the map from the map screen if needed
|
||||||
if (currentMap && selectedMapIds.includes(currentMap.id)) {
|
if (currentMap && selectedMapIds.includes(currentMap.id)) {
|
||||||
@ -136,7 +136,7 @@ function MapEditBar({
|
|||||||
<Close
|
<Close
|
||||||
title="Clear Selection"
|
title="Clear Selection"
|
||||||
aria-label="Clear Selection"
|
aria-label="Clear Selection"
|
||||||
onClick={() => onGroupSelect(undefined)}
|
onClick={() => onClearSelection()}
|
||||||
/>
|
/>
|
||||||
<Flex>
|
<Flex>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -73,11 +73,11 @@ function MapEditor({ map, onSettingsChange }: MapEditorProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useStageInteraction(
|
useStageInteraction(
|
||||||
mapStageRef.current,
|
mapStageRef,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
mapLayerRef.current,
|
mapLayerRef,
|
||||||
getGridMaxZoom(map.grid),
|
getGridMaxZoom(map.grid),
|
||||||
"move",
|
"move",
|
||||||
preventMapInteraction
|
preventMapInteraction
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
import { Group, Line } from "react-konva";
|
import { Group, Line } from "react-konva";
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
} from "../../contexts/GridContext";
|
} from "../../contexts/GridContext";
|
||||||
import { useKeyboard } from "../../contexts/KeyboardContext";
|
import { useKeyboard } from "../../contexts/KeyboardContext";
|
||||||
|
|
||||||
import Vector2 from "../../helpers/Vector2";
|
import Vector2, { BoundingBox } from "../../helpers/Vector2";
|
||||||
import {
|
import {
|
||||||
simplifyPoints,
|
simplifyPoints,
|
||||||
mergeFogShapes,
|
mergeFogShapes,
|
||||||
@ -94,19 +94,23 @@ function MapFog({
|
|||||||
const gridCellPixelOffset = useGridCellPixelOffset();
|
const gridCellPixelOffset = useGridCellPixelOffset();
|
||||||
const gridOffset = useGridOffset();
|
const gridOffset = useGridOffset();
|
||||||
|
|
||||||
const [gridSnappingSensitivity] = useSetting("map.gridSnappingSensitivity");
|
const [gridSnappingSensitivity] = useSetting<number>(
|
||||||
const [showFogGuides] = useSetting("fog.showGuides");
|
"map.gridSnappingSensitivity"
|
||||||
const [editOpacity] = useSetting("fog.editOpacity");
|
);
|
||||||
|
const [showFogGuides] = useSetting<boolean>("fog.showGuides");
|
||||||
|
const [editOpacity] = useSetting<number>("fog.editOpacity");
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
|
|
||||||
const [drawingShape, setDrawingShape] = useState<Fog | null>(null);
|
const [drawingShape, setDrawingShape] = useState<Fog | null>(null);
|
||||||
const [isBrushDown, setIsBrushDown] = useState(false);
|
const [isBrushDown, setIsBrushDown] = useState(false);
|
||||||
const [editingShapes, setEditingShapes] = useState([]);
|
const [editingShapes, setEditingShapes] = useState<Fog[]>([]);
|
||||||
|
|
||||||
// Shapes that have been merged for fog
|
// Shapes that have been merged for fog
|
||||||
const [fogShapes, setFogShapes] = useState(shapes);
|
const [fogShapes, setFogShapes] = useState(shapes);
|
||||||
// Bounding boxes for guides
|
// Bounding boxes for guides
|
||||||
const [fogShapeBoundingBoxes, setFogShapeBoundingBoxes] = useState([]);
|
const [fogShapeBoundingBoxes, setFogShapeBoundingBoxes] = useState<
|
||||||
|
BoundingBox[]
|
||||||
|
>([]);
|
||||||
const [guides, setGuides] = useState<Guide[]>([]);
|
const [guides, setGuides] = useState<Guide[]>([]);
|
||||||
|
|
||||||
const shouldHover =
|
const shouldHover =
|
||||||
@ -288,13 +292,7 @@ function MapFog({
|
|||||||
if (Object.keys(state).length === shapes.length) {
|
if (Object.keys(state).length === shapes.length) {
|
||||||
onShapeError("No fog to cut");
|
onShapeError("No fog to cut");
|
||||||
} else {
|
} else {
|
||||||
onShapesCut(
|
onShapesCut(drawingShapes);
|
||||||
drawingShapes.map((shape) => ({
|
|
||||||
id: shape.id,
|
|
||||||
type: shape.type,
|
|
||||||
data: shape.data,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onShapesAdd(
|
onShapesAdd(
|
||||||
@ -319,29 +317,31 @@ function MapFog({
|
|||||||
function handlePointerClick() {
|
function handlePointerClick() {
|
||||||
if (toolSettings.type === "polygon") {
|
if (toolSettings.type === "polygon") {
|
||||||
const brushPosition = getBrushPosition();
|
const brushPosition = getBrushPosition();
|
||||||
setDrawingShape((prevDrawingShape) => {
|
if (brushPosition) {
|
||||||
if (prevDrawingShape) {
|
setDrawingShape((prevDrawingShape) => {
|
||||||
return {
|
if (prevDrawingShape) {
|
||||||
...prevDrawingShape,
|
return {
|
||||||
data: {
|
...prevDrawingShape,
|
||||||
...prevDrawingShape.data,
|
data: {
|
||||||
points: [...prevDrawingShape.data.points, brushPosition],
|
...prevDrawingShape.data,
|
||||||
},
|
points: [...prevDrawingShape.data.points, brushPosition],
|
||||||
};
|
},
|
||||||
} else {
|
};
|
||||||
return {
|
} else {
|
||||||
type: "fog",
|
return {
|
||||||
data: {
|
type: "fog",
|
||||||
points: [brushPosition, brushPosition],
|
data: {
|
||||||
holes: [],
|
points: [brushPosition, brushPosition],
|
||||||
},
|
holes: [],
|
||||||
strokeWidth: 0.5,
|
},
|
||||||
color: toolSettings.useFogCut ? "red" : "black",
|
strokeWidth: 0.5,
|
||||||
id: shortid.generate(),
|
color: toolSettings.useFogCut ? "red" : "black",
|
||||||
visible: true,
|
id: shortid.generate(),
|
||||||
};
|
visible: true,
|
||||||
}
|
};
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,41 +349,43 @@ function MapFog({
|
|||||||
if (shouldUseGuides) {
|
if (shouldUseGuides) {
|
||||||
let guides: Guide[] = [];
|
let guides: Guide[] = [];
|
||||||
const brushPosition = getBrushPosition(false);
|
const brushPosition = getBrushPosition(false);
|
||||||
const absoluteBrushPosition = Vector2.multiply(brushPosition, {
|
if (brushPosition) {
|
||||||
x: mapWidth,
|
const absoluteBrushPosition = Vector2.multiply(brushPosition, {
|
||||||
y: mapHeight,
|
x: mapWidth,
|
||||||
});
|
y: mapHeight,
|
||||||
if (map.snapToGrid) {
|
});
|
||||||
|
if (map.snapToGrid) {
|
||||||
|
guides.push(
|
||||||
|
...getGuidesFromGridCell(
|
||||||
|
absoluteBrushPosition,
|
||||||
|
grid,
|
||||||
|
gridCellPixelSize,
|
||||||
|
gridOffset,
|
||||||
|
gridCellPixelOffset,
|
||||||
|
gridSnappingSensitivity,
|
||||||
|
{ x: mapWidth, y: mapHeight }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
guides.push(
|
guides.push(
|
||||||
...getGuidesFromGridCell(
|
...getGuidesFromBoundingBoxes(
|
||||||
absoluteBrushPosition,
|
brushPosition,
|
||||||
grid,
|
fogShapeBoundingBoxes,
|
||||||
gridCellPixelSize,
|
gridCellNormalizedSize,
|
||||||
gridOffset,
|
gridSnappingSensitivity
|
||||||
gridCellPixelOffset,
|
|
||||||
gridSnappingSensitivity,
|
|
||||||
{ x: mapWidth, y: mapHeight }
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setGuides(findBestGuides(brushPosition, guides));
|
||||||
}
|
}
|
||||||
|
|
||||||
guides.push(
|
|
||||||
...getGuidesFromBoundingBoxes(
|
|
||||||
brushPosition,
|
|
||||||
fogShapeBoundingBoxes,
|
|
||||||
gridCellNormalizedSize,
|
|
||||||
gridSnappingSensitivity
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
setGuides(findBestGuides(brushPosition, guides));
|
|
||||||
}
|
}
|
||||||
if (toolSettings.type === "polygon") {
|
if (toolSettings.type === "polygon") {
|
||||||
const brushPosition = getBrushPosition();
|
const brushPosition = getBrushPosition();
|
||||||
if (toolSettings.type === "polygon" && drawingShape) {
|
if (toolSettings.type === "polygon" && drawingShape && brushPosition) {
|
||||||
setDrawingShape((prevShape) => {
|
setDrawingShape((prevShape) => {
|
||||||
if (!prevShape) {
|
if (!prevShape) {
|
||||||
return;
|
return prevShape;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...prevShape,
|
...prevShape,
|
||||||
@ -401,32 +403,33 @@ function MapFog({
|
|||||||
setGuides([]);
|
setGuides([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
interactionEmitter.on("dragStart", handleBrushDown);
|
interactionEmitter?.on("dragStart", handleBrushDown);
|
||||||
interactionEmitter.on("drag", handleBrushMove);
|
interactionEmitter?.on("drag", handleBrushMove);
|
||||||
interactionEmitter.on("dragEnd", handleBrushUp);
|
interactionEmitter?.on("dragEnd", handleBrushUp);
|
||||||
// Use mouse events for polygon and erase to allow for single clicks
|
// Use mouse events for polygon and erase to allow for single clicks
|
||||||
mapStage.on("mousedown touchstart", handlePointerMove);
|
mapStage?.on("mousedown touchstart", handlePointerMove);
|
||||||
mapStage.on("mousemove touchmove", handlePointerMove);
|
mapStage?.on("mousemove touchmove", handlePointerMove);
|
||||||
mapStage.on("click tap", handlePointerClick);
|
mapStage?.on("click tap", handlePointerClick);
|
||||||
mapStage.on("touchend", handelTouchEnd);
|
mapStage?.on("touchend", handelTouchEnd);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
interactionEmitter.off("dragStart", handleBrushDown);
|
interactionEmitter?.off("dragStart", handleBrushDown);
|
||||||
interactionEmitter.off("drag", handleBrushMove);
|
interactionEmitter?.off("drag", handleBrushMove);
|
||||||
interactionEmitter.off("dragEnd", handleBrushUp);
|
interactionEmitter?.off("dragEnd", handleBrushUp);
|
||||||
mapStage.off("mousedown touchstart", handlePointerMove);
|
mapStage?.off("mousedown touchstart", handlePointerMove);
|
||||||
mapStage.off("mousemove touchmove", handlePointerMove);
|
mapStage?.off("mousemove touchmove", handlePointerMove);
|
||||||
mapStage.off("click tap", handlePointerClick);
|
mapStage?.off("click tap", handlePointerClick);
|
||||||
mapStage.off("touchend", handelTouchEnd);
|
mapStage?.off("touchend", handelTouchEnd);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const finishDrawingPolygon = useCallback(() => {
|
const finishDrawingPolygon = useCallback(() => {
|
||||||
const cut = toolSettings.useFogCut;
|
const cut = toolSettings.useFogCut;
|
||||||
|
if (!drawingShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let polygonShape = {
|
let polygonShape = {
|
||||||
id: drawingShape.id,
|
...drawingShape,
|
||||||
type: drawingShape.type,
|
|
||||||
data: {
|
data: {
|
||||||
...drawingShape.data,
|
...drawingShape.data,
|
||||||
// Remove the last point as it hasn't been placed yet
|
// Remove the last point as it hasn't been placed yet
|
||||||
@ -489,7 +492,7 @@ function MapFog({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Add keyboard shortcuts
|
// Add keyboard shortcuts
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
shortcuts.fogFinishPolygon(event) &&
|
shortcuts.fogFinishPolygon(event) &&
|
||||||
toolSettings.type === "polygon" &&
|
toolSettings.type === "polygon" &&
|
||||||
@ -507,17 +510,22 @@ function MapFog({
|
|||||||
toolSettings.type === "polygon"
|
toolSettings.type === "polygon"
|
||||||
) {
|
) {
|
||||||
if (drawingShape.data.points.length > 2) {
|
if (drawingShape.data.points.length > 2) {
|
||||||
setDrawingShape((drawingShape) => ({
|
setDrawingShape((prevShape) => {
|
||||||
...drawingShape,
|
if (!prevShape) {
|
||||||
data: {
|
return prevShape;
|
||||||
...drawingShape.data,
|
}
|
||||||
points: [
|
return {
|
||||||
// Shift last point to previous point
|
...prevShape,
|
||||||
...drawingShape.data.points.slice(0, -2),
|
data: {
|
||||||
...drawingShape.data.points.slice(-1),
|
...prevShape.data,
|
||||||
],
|
points: [
|
||||||
},
|
// Shift last point to previous point
|
||||||
}));
|
...prevShape.data.points.slice(0, -2),
|
||||||
|
...prevShape.data.points.slice(-1),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setDrawingShape(null);
|
setDrawingShape(null);
|
||||||
}
|
}
|
||||||
@ -530,7 +538,7 @@ function MapFog({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDrawingShape((prevShape) => {
|
setDrawingShape((prevShape) => {
|
||||||
if (!prevShape) {
|
if (!prevShape) {
|
||||||
return;
|
return prevShape;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...prevShape,
|
...prevShape,
|
||||||
@ -556,7 +564,7 @@ function MapFog({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleShapeOver(shape, isDown) {
|
function handleShapeOver(shape: Fog, isDown: boolean) {
|
||||||
if (shouldHover && isDown) {
|
if (shouldHover && isDown) {
|
||||||
if (editingShapes.findIndex((s) => s.id === shape.id) === -1) {
|
if (editingShapes.findIndex((s) => s.id === shape.id) === -1) {
|
||||||
setEditingShapes((prevShapes) => [...prevShapes, shape]);
|
setEditingShapes((prevShapes) => [...prevShapes, shape]);
|
||||||
@ -564,11 +572,11 @@ function MapFog({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reducePoints(acc, point) {
|
function reducePoints(acc: number[], point: Vector2) {
|
||||||
return [...acc, point.x * mapWidth, point.y * mapHeight];
|
return [...acc, point.x * mapWidth, point.y * mapHeight];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderShape(shape) {
|
function renderShape(shape: Fog) {
|
||||||
const points = shape.data.points.reduce(reducePoints, []);
|
const points = shape.data.points.reduce(reducePoints, []);
|
||||||
const holes =
|
const holes =
|
||||||
shape.data.holes &&
|
shape.data.holes &&
|
||||||
@ -608,15 +616,15 @@ function MapFog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEditingShape(shape) {
|
function renderEditingShape(shape: Fog) {
|
||||||
const editingShape = {
|
const editingShape: Fog = {
|
||||||
...shape,
|
...shape,
|
||||||
color: "#BB99FF",
|
color: "primary",
|
||||||
};
|
};
|
||||||
return renderShape(editingShape);
|
return renderShape(editingShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPolygonAcceptTick(shape) {
|
function renderPolygonAcceptTick(shape: Fog) {
|
||||||
if (shape.data.points.length === 0) {
|
if (shape.data.points.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -658,7 +666,7 @@ function MapFog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function shapeVisible(shape) {
|
function shapeVisible(shape: Fog) {
|
||||||
return (active && !toolSettings.preview) || shape.visible;
|
return (active && !toolSettings.preview) || shape.visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Image } from "theme-ui";
|
|
||||||
|
|
||||||
import { useDataURL } from "../../contexts/AssetsContext";
|
|
||||||
import { mapSources as defaultMapSources } from "../../maps";
|
|
||||||
|
|
||||||
const MapTileImage = React.forwardRef(({ map, ...props }, ref) => {
|
|
||||||
const mapURL = useDataURL(
|
|
||||||
map,
|
|
||||||
defaultMapSources,
|
|
||||||
undefined,
|
|
||||||
map.type === "file"
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Image src={mapURL} ref={ref} {...props} />;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default MapTileImage;
|
|
@ -18,6 +18,22 @@ import { GridProvider } from "../../contexts/GridContext";
|
|||||||
import { useKeyboard } from "../../contexts/KeyboardContext";
|
import { useKeyboard } from "../../contexts/KeyboardContext";
|
||||||
|
|
||||||
import shortcuts from "../../shortcuts";
|
import shortcuts from "../../shortcuts";
|
||||||
|
import { Layer as LayerType } from "konva/types/Layer";
|
||||||
|
import { Image as ImageType } from "konva/types/shapes/Image";
|
||||||
|
import { Map, MapToolId } from "../../types/Map";
|
||||||
|
import { MapState } from "../../types/MapState";
|
||||||
|
|
||||||
|
type SelectedToolChangeEventHanlder = (tool: MapToolId) => void;
|
||||||
|
|
||||||
|
type MapInteractionProps = {
|
||||||
|
map: Map;
|
||||||
|
mapState: MapState;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
controls: React.ReactNode;
|
||||||
|
selectedToolId: MapToolId;
|
||||||
|
onSelectedToolChange: SelectedToolChangeEventHanlder;
|
||||||
|
disabledControls: MapToolId[];
|
||||||
|
};
|
||||||
|
|
||||||
function MapInteraction({
|
function MapInteraction({
|
||||||
map,
|
map,
|
||||||
@ -27,7 +43,7 @@ function MapInteraction({
|
|||||||
selectedToolId,
|
selectedToolId,
|
||||||
onSelectedToolChange,
|
onSelectedToolChange,
|
||||||
disabledControls,
|
disabledControls,
|
||||||
}) {
|
}: MapInteractionProps) {
|
||||||
const [mapImage, mapImageStatus] = useMapImage(map);
|
const [mapImage, mapImageStatus] = useMapImage(map);
|
||||||
|
|
||||||
const [mapLoaded, setMapLoaded] = useState(false);
|
const [mapLoaded, setMapLoaded] = useState(false);
|
||||||
@ -47,17 +63,17 @@ function MapInteraction({
|
|||||||
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
const mapLayerRef = useRef();
|
const mapLayerRef = useRef<LayerType>(null);
|
||||||
const mapImageRef = useRef();
|
const mapImageRef = useRef<ImageType>(null);
|
||||||
|
|
||||||
function handleResize(width, height) {
|
function handleResize(width?: number, height?: number) {
|
||||||
if (width > 0 && height > 0) {
|
if (width && height && width > 0 && height > 0) {
|
||||||
setStageWidth(width);
|
setStageWidth(width);
|
||||||
setStageHeight(height);
|
setStageHeight(height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerRef = useRef();
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
usePreventOverscroll(containerRef);
|
usePreventOverscroll(containerRef);
|
||||||
|
|
||||||
const [mapWidth, mapHeight] = useImageCenter(
|
const [mapWidth, mapHeight] = useImageCenter(
|
||||||
@ -76,11 +92,11 @@ function MapInteraction({
|
|||||||
const [interactionEmitter] = useState(new EventEmitter());
|
const [interactionEmitter] = useState(new EventEmitter());
|
||||||
|
|
||||||
useStageInteraction(
|
useStageInteraction(
|
||||||
mapStageRef.current,
|
mapStageRef,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
mapLayerRef.current,
|
mapLayerRef,
|
||||||
getGridMaxZoom(map?.grid),
|
getGridMaxZoom(map?.grid),
|
||||||
selectedToolId,
|
selectedToolId,
|
||||||
preventMapInteraction,
|
preventMapInteraction,
|
||||||
@ -105,7 +121,7 @@ function MapInteraction({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
// Change to move tool when pressing space
|
// Change to move tool when pressing space
|
||||||
if (shortcuts.move(event) && selectedToolId === "move") {
|
if (shortcuts.move(event) && selectedToolId === "move") {
|
||||||
// Stop active state on move icon from being selected
|
// Stop active state on move icon from being selected
|
||||||
@ -142,7 +158,7 @@ function MapInteraction({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyUp(event) {
|
function handleKeyUp(event: KeyboardEvent) {
|
||||||
if (shortcuts.move(event) && selectedToolId === "move") {
|
if (shortcuts.move(event) && selectedToolId === "move") {
|
||||||
onSelectedToolChange(previousSelectedToolRef.current);
|
onSelectedToolChange(previousSelectedToolRef.current);
|
||||||
}
|
}
|
||||||
@ -150,7 +166,7 @@ function MapInteraction({
|
|||||||
|
|
||||||
useKeyboard(handleKeyDown, handleKeyUp);
|
useKeyboard(handleKeyDown, handleKeyUp);
|
||||||
|
|
||||||
function getCursorForTool(tool) {
|
function getCursorForTool(tool: MapToolId) {
|
||||||
switch (tool) {
|
switch (tool) {
|
||||||
case "move":
|
case "move":
|
||||||
return "move";
|
return "move";
|
||||||
@ -195,6 +211,7 @@ function MapInteraction({
|
|||||||
<KonvaBridge
|
<KonvaBridge
|
||||||
stageRender={(children) => (
|
stageRender={(children) => (
|
||||||
<Stage
|
<Stage
|
||||||
|
// @ts-ignore
|
||||||
width={stageWidth}
|
width={stageWidth}
|
||||||
height={stageHeight}
|
height={stageHeight}
|
||||||
scale={{ x: stageScale, y: stageScale }}
|
scale={{ x: stageScale, y: stageScale }}
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Group, Line, Text, Label, Tag } from "react-konva";
|
import { Group, Line, Text, Label, Tag } from "react-konva";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,8 +25,17 @@ import { getRelativePointerPosition } from "../../helpers/konva";
|
|||||||
import { parseGridScale, gridDistance } from "../../helpers/grid";
|
import { parseGridScale, gridDistance } from "../../helpers/grid";
|
||||||
|
|
||||||
import useGridSnapping from "../../hooks/useGridSnapping";
|
import useGridSnapping from "../../hooks/useGridSnapping";
|
||||||
|
import { Map } from "../../types/Map";
|
||||||
|
import { PointsData } from "../../types/Drawing";
|
||||||
|
|
||||||
function MapMeasure({ map, active }) {
|
type MapMeasureProps = {
|
||||||
|
map: Map;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MeasureData = { length: number; points: Vector2[] };
|
||||||
|
|
||||||
|
function MapMeasure({ map, active }: MapMeasureProps) {
|
||||||
const stageScale = useDebouncedStageScale();
|
const stageScale = useDebouncedStageScale();
|
||||||
const mapWidth = useMapWidth();
|
const mapWidth = useMapWidth();
|
||||||
const mapHeight = useMapHeight();
|
const mapHeight = useMapHeight();
|
||||||
@ -39,7 +48,8 @@ function MapMeasure({ map, active }) {
|
|||||||
const gridOffset = useGridOffset();
|
const gridOffset = useGridOffset();
|
||||||
|
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
const [drawingShapeData, setDrawingShapeData] = useState(null);
|
const [drawingShapeData, setDrawingShapeData] =
|
||||||
|
useState<MeasureData | null>(null);
|
||||||
const [isBrushDown, setIsBrushDown] = useState(false);
|
const [isBrushDown, setIsBrushDown] = useState(false);
|
||||||
|
|
||||||
const gridScale = parseGridScale(active && grid.measurement.scale);
|
const gridScale = parseGridScale(active && grid.measurement.scale);
|
||||||
@ -57,8 +67,13 @@ function MapMeasure({ map, active }) {
|
|||||||
const mapImage = mapStage?.findOne("#mapImage");
|
const mapImage = mapStage?.findOne("#mapImage");
|
||||||
|
|
||||||
function getBrushPosition() {
|
function getBrushPosition() {
|
||||||
const mapImage = mapStage.findOne("#mapImage");
|
if (!mapImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let position = getRelativePointerPosition(mapImage);
|
let position = getRelativePointerPosition(mapImage);
|
||||||
|
if (!position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (map.snapToGrid) {
|
if (map.snapToGrid) {
|
||||||
position = snapPositionToGrid(position);
|
position = snapPositionToGrid(position);
|
||||||
}
|
}
|
||||||
@ -70,7 +85,13 @@ function MapMeasure({ map, active }) {
|
|||||||
|
|
||||||
function handleBrushDown() {
|
function handleBrushDown() {
|
||||||
const brushPosition = getBrushPosition();
|
const brushPosition = getBrushPosition();
|
||||||
const { points } = getDefaultShapeData("line", brushPosition);
|
if (!brushPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { points } = getDefaultShapeData(
|
||||||
|
"line",
|
||||||
|
brushPosition
|
||||||
|
) as PointsData;
|
||||||
const length = 0;
|
const length = 0;
|
||||||
setDrawingShapeData({ length, points });
|
setDrawingShapeData({ length, points });
|
||||||
setIsBrushDown(true);
|
setIsBrushDown(true);
|
||||||
@ -78,13 +99,15 @@ function MapMeasure({ map, active }) {
|
|||||||
|
|
||||||
function handleBrushMove() {
|
function handleBrushMove() {
|
||||||
const brushPosition = getBrushPosition();
|
const brushPosition = getBrushPosition();
|
||||||
if (isBrushDown && drawingShapeData) {
|
if (isBrushDown && drawingShapeData && brushPosition && mapImage) {
|
||||||
const { points } = getUpdatedShapeData(
|
const { points } = getUpdatedShapeData(
|
||||||
"line",
|
"line",
|
||||||
drawingShapeData,
|
drawingShapeData,
|
||||||
brushPosition,
|
brushPosition,
|
||||||
gridCellNormalizedSize
|
gridCellNormalizedSize,
|
||||||
);
|
1,
|
||||||
|
1
|
||||||
|
) as PointsData;
|
||||||
// Convert back to pixel values
|
// Convert back to pixel values
|
||||||
const a = Vector2.subtract(
|
const a = Vector2.subtract(
|
||||||
Vector2.multiply(points[0], {
|
Vector2.multiply(points[0], {
|
||||||
@ -113,20 +136,24 @@ function MapMeasure({ map, active }) {
|
|||||||
setIsBrushDown(false);
|
setIsBrushDown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
interactionEmitter.on("dragStart", handleBrushDown);
|
interactionEmitter?.on("dragStart", handleBrushDown);
|
||||||
interactionEmitter.on("drag", handleBrushMove);
|
interactionEmitter?.on("drag", handleBrushMove);
|
||||||
interactionEmitter.on("dragEnd", handleBrushUp);
|
interactionEmitter?.on("dragEnd", handleBrushUp);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
interactionEmitter.off("dragStart", handleBrushDown);
|
interactionEmitter?.off("dragStart", handleBrushDown);
|
||||||
interactionEmitter.off("drag", handleBrushMove);
|
interactionEmitter?.off("drag", handleBrushMove);
|
||||||
interactionEmitter.off("dragEnd", handleBrushUp);
|
interactionEmitter?.off("dragEnd", handleBrushUp);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderShape(shapeData) {
|
function renderShape(shapeData: MeasureData) {
|
||||||
const linePoints = shapeData.points.reduce(
|
const linePoints = shapeData.points.reduce(
|
||||||
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
|
(acc: number[], point) => [
|
||||||
|
...acc,
|
||||||
|
point.x * mapWidth,
|
||||||
|
point.y * mapHeight,
|
||||||
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
@ -1,6 +1,22 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Modal from "react-modal";
|
import Modal from "react-modal";
|
||||||
import { useThemeUI } from "theme-ui";
|
import { useThemeUI } from "theme-ui";
|
||||||
|
import CSS from "csstype";
|
||||||
|
|
||||||
|
import { RequestCloseEventHandler } from "../../types/Events";
|
||||||
|
|
||||||
|
type MapMenuProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onRequestClose: RequestCloseEventHandler;
|
||||||
|
onModalContent: (instance: HTMLDivElement) => void;
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
bottom: number;
|
||||||
|
right: number;
|
||||||
|
children: React.ReactNode;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
excludeNode: Node | null;
|
||||||
|
};
|
||||||
|
|
||||||
function MapMenu({
|
function MapMenu({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -14,17 +30,18 @@ function MapMenu({
|
|||||||
style,
|
style,
|
||||||
// A node to exclude from the pointer event for closing
|
// A node to exclude from the pointer event for closing
|
||||||
excludeNode,
|
excludeNode,
|
||||||
}) {
|
}: MapMenuProps) {
|
||||||
// Save modal node in state to ensure that the pointer listeners
|
// Save modal node in state to ensure that the pointer listeners
|
||||||
// are removed if the open state changed not from the onRequestClose
|
// are removed if the open state changed not from the onRequestClose
|
||||||
// callback
|
// callback
|
||||||
const [modalContentNode, setModalContentNode] = useState(null);
|
const [modalContentNode, setModalContentNode] = useState<Node | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Close modal if interacting with any other element
|
// Close modal if interacting with any other element
|
||||||
function handleInteraction(event) {
|
function handleInteraction(event: Event) {
|
||||||
const path = event.composedPath();
|
const path = event.composedPath();
|
||||||
if (
|
if (
|
||||||
|
modalContentNode &&
|
||||||
!path.includes(modalContentNode) &&
|
!path.includes(modalContentNode) &&
|
||||||
!(excludeNode && path.includes(excludeNode)) &&
|
!(excludeNode && path.includes(excludeNode)) &&
|
||||||
!(event.target instanceof HTMLTextAreaElement)
|
!(event.target instanceof HTMLTextAreaElement)
|
||||||
@ -48,7 +65,7 @@ function MapMenu({
|
|||||||
};
|
};
|
||||||
}, [modalContentNode, excludeNode, onRequestClose]);
|
}, [modalContentNode, excludeNode, onRequestClose]);
|
||||||
|
|
||||||
function handleModalContent(node) {
|
function handleModalContent(node: HTMLDivElement) {
|
||||||
setModalContentNode(node);
|
setModalContentNode(node);
|
||||||
onModalContent(node);
|
onModalContent(node);
|
||||||
}
|
}
|
||||||
@ -62,7 +79,7 @@ function MapMenu({
|
|||||||
style={{
|
style={{
|
||||||
overlay: { top: "0", bottom: "initial" },
|
overlay: { top: "0", bottom: "initial" },
|
||||||
content: {
|
content: {
|
||||||
backgroundColor: theme.colors.overlay,
|
backgroundColor: theme.colors?.overlay as CSS.Property.Color,
|
||||||
top,
|
top,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Tile from "../tile/Tile";
|
import Tile from "../tile/Tile";
|
||||||
import MapImage from "./MapImage";
|
import MapImage from "./MapTileImage";
|
||||||
|
|
||||||
function MapTile({
|
function MapTile({
|
||||||
map,
|
map,
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { Grid } from "theme-ui";
|
import { Grid } from "theme-ui";
|
||||||
|
|
||||||
import Tile from "../tile/Tile";
|
import Tile from "../tile/Tile";
|
||||||
import MapImage from "./MapImage";
|
import MapImage from "./MapTileImage";
|
||||||
|
|
||||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||||
|
|
||||||
|
23
src/components/map/MapTileImage.tsx
Normal file
23
src/components/map/MapTileImage.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Image, ImageProps } from "theme-ui";
|
||||||
|
|
||||||
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
import { mapSources as defaultMapSources } from "../../maps";
|
||||||
|
import { Map } from "../../types/Map";
|
||||||
|
|
||||||
|
type MapTileImageProps = {
|
||||||
|
map: Map;
|
||||||
|
} & ImageProps;
|
||||||
|
|
||||||
|
function MapTileImage({ map, ...props }: MapTileImageProps) {
|
||||||
|
const mapURL = useDataURL(
|
||||||
|
map,
|
||||||
|
defaultMapSources,
|
||||||
|
undefined,
|
||||||
|
map.type === "file"
|
||||||
|
);
|
||||||
|
|
||||||
|
return <Image src={mapURL} {...props} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MapTileImage;
|
@ -13,7 +13,7 @@ type MapLoadingContext = {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
assetLoadStart: (id: string) => void;
|
assetLoadStart: (id: string) => void;
|
||||||
assetProgressUpdate: (update: MapLoadingProgressUpdate) => void;
|
assetProgressUpdate: (update: MapLoadingProgressUpdate) => void;
|
||||||
loadingProgressRef: React.MutableRefObject<number | null>;
|
loadingProgressRef: React.MutableRefObject<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MapLoadingContext =
|
const MapLoadingContext =
|
||||||
@ -28,7 +28,7 @@ export function MapLoadingProvider({
|
|||||||
// Mapping from asset id to the count and total number of pieces loaded
|
// Mapping from asset id to the count and total number of pieces loaded
|
||||||
const assetProgressRef = useRef<Record<string, MapLoadingProgress>>({});
|
const assetProgressRef = useRef<Record<string, MapLoadingProgress>>({});
|
||||||
// Loading progress of all assets between 0 and 1
|
// Loading progress of all assets between 0 and 1
|
||||||
const loadingProgressRef = useRef<number | null>(null);
|
const loadingProgressRef = useRef<number>(0);
|
||||||
|
|
||||||
const assetLoadStart = useCallback((id) => {
|
const assetLoadStart = useCallback((id) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InteractionEmitterContext,
|
InteractionEmitterContext,
|
||||||
@ -45,10 +45,20 @@ import {
|
|||||||
} from "../contexts/GridContext";
|
} from "../contexts/GridContext";
|
||||||
import DatabaseContext, { useDatabase } from "../contexts/DatabaseContext";
|
import DatabaseContext, { useDatabase } from "../contexts/DatabaseContext";
|
||||||
|
|
||||||
|
type StageRender = (wrapped: React.ReactNode) => React.ReactElement;
|
||||||
|
|
||||||
|
type KonvaBridgeProps = {
|
||||||
|
stageRender: StageRender;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a bridge for konva that forwards our contexts
|
* Provide a bridge for konva that forwards our contexts
|
||||||
*/
|
*/
|
||||||
function KonvaBridge({ stageRender, children }: { stageRender: any, children: any}) {
|
function KonvaBridge({
|
||||||
|
stageRender,
|
||||||
|
children,
|
||||||
|
}: KonvaBridgeProps): React.ReactElement {
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
const userId = useUserId();
|
const userId = useUserId();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
@ -350,6 +350,7 @@ export function gridDistance(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,10 +2,10 @@ import { useRef, useEffect, useState } from "react";
|
|||||||
import { useGesture } from "react-use-gesture";
|
import { useGesture } from "react-use-gesture";
|
||||||
import { Handlers } from "react-use-gesture/dist/types";
|
import { Handlers } from "react-use-gesture/dist/types";
|
||||||
import normalizeWheel from "normalize-wheel";
|
import normalizeWheel from "normalize-wheel";
|
||||||
import { Stage } from "konva/types/Stage";
|
|
||||||
import { Layer } from "konva/types/Layer";
|
import { Layer } from "konva/types/Layer";
|
||||||
|
|
||||||
import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
|
import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
|
||||||
|
import { MapStage } from "../contexts/MapStageContext";
|
||||||
|
|
||||||
import shortcuts from "../shortcuts";
|
import shortcuts from "../shortcuts";
|
||||||
|
|
||||||
@ -18,11 +18,11 @@ const minZoom = 0.1;
|
|||||||
type StageScaleChangeEventHandler = (newScale: number) => void;
|
type StageScaleChangeEventHandler = (newScale: number) => void;
|
||||||
|
|
||||||
function useStageInteraction(
|
function useStageInteraction(
|
||||||
stage: Stage,
|
stageRef: MapStage,
|
||||||
stageScale: number,
|
stageScale: number,
|
||||||
onStageScaleChange: StageScaleChangeEventHandler,
|
onStageScaleChange: StageScaleChangeEventHandler,
|
||||||
stageTranslateRef: React.MutableRefObject<Vector2>,
|
stageTranslateRef: React.MutableRefObject<Vector2>,
|
||||||
layer: Layer,
|
layerRef: React.RefObject<Layer>,
|
||||||
maxZoom = 10,
|
maxZoom = 10,
|
||||||
tool = "move",
|
tool = "move",
|
||||||
preventInteraction = false,
|
preventInteraction = false,
|
||||||
@ -52,12 +52,18 @@ function useStageInteraction(
|
|||||||
...gesture,
|
...gesture,
|
||||||
onWheelStart: (props) => {
|
onWheelStart: (props) => {
|
||||||
const { event } = props;
|
const { event } = props;
|
||||||
|
const layer = layerRef.current;
|
||||||
isInteractingWithCanvas.current =
|
isInteractingWithCanvas.current =
|
||||||
layer && event.target === layer.getCanvas()._canvas;
|
layer !== null && event.target === layer.getCanvas()._canvas;
|
||||||
gesture.onWheelStart && gesture.onWheelStart(props);
|
gesture.onWheelStart && gesture.onWheelStart(props);
|
||||||
},
|
},
|
||||||
onWheel: (props) => {
|
onWheel: (props) => {
|
||||||
if (preventInteraction || !isInteractingWithCanvas.current) {
|
const stage = stageRef.current;
|
||||||
|
if (
|
||||||
|
preventInteraction ||
|
||||||
|
!isInteractingWithCanvas.current ||
|
||||||
|
stage === null
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { event, last } = props;
|
const { event, last } = props;
|
||||||
@ -94,8 +100,9 @@ function useStageInteraction(
|
|||||||
},
|
},
|
||||||
onPinchStart: (props) => {
|
onPinchStart: (props) => {
|
||||||
const { event } = props;
|
const { event } = props;
|
||||||
|
const layer = layerRef.current;
|
||||||
isInteractingWithCanvas.current =
|
isInteractingWithCanvas.current =
|
||||||
layer && event.target === layer.getCanvas()._canvas;
|
layer !== null && event.target === layer.getCanvas()._canvas;
|
||||||
const { da, origin } = props;
|
const { da, origin } = props;
|
||||||
const [distance] = da;
|
const [distance] = da;
|
||||||
const [originX, originY] = origin;
|
const [originX, originY] = origin;
|
||||||
@ -104,9 +111,17 @@ function useStageInteraction(
|
|||||||
gesture.onPinchStart && gesture.onPinchStart(props);
|
gesture.onPinchStart && gesture.onPinchStart(props);
|
||||||
},
|
},
|
||||||
onPinch: (props) => {
|
onPinch: (props) => {
|
||||||
if (preventInteraction || !isInteractingWithCanvas.current) {
|
const layer = layerRef.current;
|
||||||
|
const stage = stageRef.current;
|
||||||
|
if (
|
||||||
|
preventInteraction ||
|
||||||
|
!isInteractingWithCanvas.current ||
|
||||||
|
layer === null ||
|
||||||
|
stage === null
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { da, origin } = props;
|
const { da, origin } = props;
|
||||||
const [distance] = da;
|
const [distance] = da;
|
||||||
const [originX, originY] = origin;
|
const [originX, originY] = origin;
|
||||||
@ -157,16 +172,19 @@ function useStageInteraction(
|
|||||||
},
|
},
|
||||||
onDragStart: (props) => {
|
onDragStart: (props) => {
|
||||||
const { event } = props;
|
const { event } = props;
|
||||||
|
const layer = layerRef.current;
|
||||||
isInteractingWithCanvas.current =
|
isInteractingWithCanvas.current =
|
||||||
layer && event.target === layer.getCanvas()._canvas;
|
layer !== null && event.target === layer.getCanvas()._canvas;
|
||||||
gesture.onDragStart && gesture.onDragStart(props);
|
gesture.onDragStart && gesture.onDragStart(props);
|
||||||
},
|
},
|
||||||
onDrag: (props) => {
|
onDrag: (props) => {
|
||||||
const { delta, pinching } = props;
|
const { delta, pinching } = props;
|
||||||
|
const stage = stageRef.current;
|
||||||
if (
|
if (
|
||||||
preventInteraction ||
|
preventInteraction ||
|
||||||
pinching ||
|
pinching ||
|
||||||
!isInteractingWithCanvas.current
|
!isInteractingWithCanvas.current ||
|
||||||
|
stage === null
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -198,7 +216,8 @@ function useStageInteraction(
|
|||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
// TODO: Find better way to detect whether keyboard event should fire.
|
// TODO: Find better way to detect whether keyboard event should fire.
|
||||||
// This one fires on all open stages
|
// This one fires on all open stages
|
||||||
if (preventInteraction) {
|
const stage = stageRef.current;
|
||||||
|
if (preventInteraction || stage === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shortcuts.stageZoomIn(event) || shortcuts.stageZoomOut(event)) {
|
if (shortcuts.stageZoomIn(event) || shortcuts.stageZoomOut(event)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user