diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx
index 4acdfb9..f4d3bc4 100644
--- a/src/components/map/Map.tsx
+++ b/src/components/map/Map.tsx
@@ -43,6 +43,7 @@ import Action from "../../actions/Action";
import Konva from "konva";
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
+import MapSelect from "./MapSelect";
type MapProps = {
map: MapType | null;
@@ -385,6 +386,13 @@ function Map({
/>
) : null;
+ const mapSelect = (
+
+ );
+
return (
);
diff --git a/src/components/map/MapControls.tsx b/src/components/map/MapControls.tsx
index b8b8296..71c40dd 100644
--- a/src/components/map/MapControls.tsx
+++ b/src/components/map/MapControls.tsx
@@ -75,7 +75,7 @@ function MapContols({
select: {
id: "select",
icon: ,
- title: "Select Tool (S)",
+ title: "Select Tool (L)",
SettingsComponent: SelectToolSettings,
},
fog: {
diff --git a/src/components/map/MapSelect.tsx b/src/components/map/MapSelect.tsx
new file mode 100644
index 0000000..fcac63f
--- /dev/null
+++ b/src/components/map/MapSelect.tsx
@@ -0,0 +1,195 @@
+import { useState, useEffect } from "react";
+import { Group, Line, Rect } from "react-konva";
+
+import {
+ useDebouncedStageScale,
+ useMapWidth,
+ useMapHeight,
+ useInteractionEmitter,
+} from "../../contexts/MapInteractionContext";
+import { useMapStage } from "../../contexts/MapStageContext";
+
+import {
+ getDefaultShapeData,
+ getUpdatedShapeData,
+ simplifyPoints,
+} from "../../helpers/drawing";
+import Vector2 from "../../helpers/Vector2";
+import colors from "../../helpers/colors";
+import { getRelativePointerPosition } from "../../helpers/konva";
+
+import { Selection, SelectToolSettings } from "../../types/Select";
+import { RectData } from "../../types/Drawing";
+import {
+ useGridCellNormalizedSize,
+ useGridStrokeWidth,
+} from "../../contexts/GridContext";
+
+type MapSelectProps = {
+ active: boolean;
+ toolSettings: SelectToolSettings;
+};
+
+function MapSelect({ active, toolSettings }: MapSelectProps) {
+ const stageScale = useDebouncedStageScale();
+ const mapWidth = useMapWidth();
+ const mapHeight = useMapHeight();
+ const interactionEmitter = useInteractionEmitter();
+
+ const gridCellNormalizedSize = useGridCellNormalizedSize();
+ const gridStrokeWidth = useGridStrokeWidth();
+
+ const mapStageRef = useMapStage();
+ const [selection, setSelection] = useState(null);
+ const [isBrushDown, setIsBrushDown] = useState(false);
+
+ useEffect(() => {
+ if (!active) {
+ return;
+ }
+ const mapStage = mapStageRef.current;
+ const mapImage = mapStage?.findOne("#mapImage");
+
+ function getBrushPosition() {
+ if (!mapImage) {
+ return;
+ }
+ let position = getRelativePointerPosition(mapImage);
+ if (!position) {
+ return;
+ }
+ return Vector2.divide(position, {
+ x: mapImage.width(),
+ y: mapImage.height(),
+ });
+ }
+
+ function handleBrushDown() {
+ const brushPosition = getBrushPosition();
+ if (!brushPosition) {
+ return;
+ }
+ if (toolSettings.type === "path") {
+ setSelection({
+ type: "path",
+ nodes: [],
+ data: { points: [brushPosition] },
+ });
+ } else {
+ setSelection({
+ type: "rectangle",
+ nodes: [],
+ data: getDefaultShapeData("rectangle", brushPosition) as RectData,
+ });
+ }
+ setIsBrushDown(true);
+ }
+
+ function handleBrushMove() {
+ const brushPosition = getBrushPosition();
+ if (isBrushDown && selection && brushPosition && mapImage) {
+ if (selection.type === "path") {
+ setSelection((prevSelection) => {
+ if (prevSelection?.type !== "path") {
+ return prevSelection;
+ }
+ const prevPoints = prevSelection.data.points;
+ if (
+ Vector2.compare(
+ prevPoints[prevPoints.length - 1],
+ brushPosition,
+ 0.001
+ )
+ ) {
+ return prevSelection;
+ }
+ const simplified = simplifyPoints(
+ [...prevPoints, brushPosition],
+ 1 / 1000 / stageScale
+ );
+ return {
+ ...prevSelection,
+ data: { points: simplified },
+ };
+ });
+ } else {
+ setSelection((prevSelection) => {
+ if (prevSelection?.type !== "rectangle") {
+ return prevSelection;
+ }
+ return {
+ ...prevSelection,
+ data: getUpdatedShapeData(
+ "rectangle",
+ prevSelection.data,
+ brushPosition,
+ gridCellNormalizedSize,
+ mapWidth,
+ mapHeight
+ ) as RectData,
+ };
+ });
+ }
+ }
+ }
+
+ function handleBrushUp() {
+ setSelection(null);
+ setIsBrushDown(false);
+ }
+
+ 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);
+ };
+ });
+
+ function renderSelection(selection: Selection) {
+ const strokeWidth = gridStrokeWidth / stageScale;
+ const defaultProps = {
+ stroke: colors.primary,
+ strokeWidth: strokeWidth,
+ dash: [strokeWidth / 2, strokeWidth * 2],
+ };
+ if (selection.type === "path") {
+ return (
+ [
+ ...acc,
+ point.x * mapWidth,
+ point.y * mapHeight,
+ ],
+ []
+ )}
+ tension={0.5}
+ closed={false}
+ lineCap="round"
+ lineJoin="round"
+ {...defaultProps}
+ />
+ );
+ } else if (selection.type === "rectangle") {
+ return (
+
+ );
+ }
+ }
+
+ return {selection && renderSelection(selection)};
+}
+
+export default MapSelect;
diff --git a/src/components/map/controls/SelectToolSettings.tsx b/src/components/map/controls/SelectToolSettings.tsx
index 3f9ad45..f07afee 100644
--- a/src/components/map/controls/SelectToolSettings.tsx
+++ b/src/components/map/controls/SelectToolSettings.tsx
@@ -35,7 +35,7 @@ function SelectToolSettings({
const tools = [
{
- id: "lasso",
+ id: "path",
title: "Lasso (L)",
isSelected: settings.type === "path",
icon: ,
diff --git a/src/shortcuts.ts b/src/shortcuts.ts
index 925c159..3a84e21 100644
--- a/src/shortcuts.ts
+++ b/src/shortcuts.ts
@@ -76,7 +76,7 @@ const shortcuts: Record = {
fogFinishPolygon: ({ key }) => key === "Enter",
fogCancelPolygon: ({ key }) => key === "Escape",
// Select tool
- selectTool: (event) => singleKey(event, "s"),
+ selectTool: (event) => singleKey(event, "l"),
selectPath: (event) => singleKey(event, "l"),
selectRect: (event) => singleKey(event, "r"),
// Stage interaction