diff --git a/src/components/map/MapControls.js b/src/components/map/MapControls.js index fb13f6e..2d2da33 100644 --- a/src/components/map/MapControls.js +++ b/src/components/map/MapControls.js @@ -8,7 +8,6 @@ import SelectMapButton from "./SelectMapButton"; import FogToolSettings from "./controls/FogToolSettings"; import DrawingToolSettings from "./controls/DrawingToolSettings"; -import MeasureToolSettings from "./controls/MeasureToolSettings"; import PointerToolSettings from "./controls/PointerToolSettings"; import PanToolIcon from "../../icons/PanToolIcon"; @@ -61,7 +60,6 @@ function MapContols({ id: "measure", icon: , title: "Measure Tool (M)", - SettingsComponent: MeasureToolSettings, }, pointer: { id: "pointer", diff --git a/src/components/map/MapMeasure.js b/src/components/map/MapMeasure.js index 2914be7..95f1170 100644 --- a/src/components/map/MapMeasure.js +++ b/src/components/map/MapMeasure.js @@ -14,14 +14,14 @@ import { getRelativePointerPosition } from "../../helpers/konva"; import useGridSnapping from "../../hooks/useGridSnapping"; -function MapMeasure({ map, selectedToolSettings, active }) { +function MapMeasure({ map, active }) { const { stageScale, mapWidth, mapHeight, interactionEmitter, } = useMapInteraction(); - const { gridCellNormalizedSize, gridStrokeWidth } = useGrid(); + const { grid, gridCellNormalizedSize, gridStrokeWidth } = useGrid(); const mapStageRef = useMapStage(); const [drawingShapeData, setDrawingShapeData] = useState(null); const [isBrushDown, setIsBrushDown] = useState(false); @@ -45,7 +45,7 @@ function MapMeasure({ map, selectedToolSettings, active }) { return { multiplier: 1, unit: "", digits: 0 }; } - const measureScale = parseToolScale(active && selectedToolSettings.scale); + const measureScale = parseToolScale(active && grid.measurement.scale); const snapPositionToGrid = useGridSnapping(); @@ -95,7 +95,7 @@ function MapMeasure({ map, selectedToolSettings, active }) { Vector2.divide(points[1], gridCellNormalizedSize), precision ), - selectedToolSettings.type + grid.measurement.type ); setDrawingShapeData({ length, diff --git a/src/components/map/MapSettings.js b/src/components/map/MapSettings.js index 66306d7..c8f3276 100644 --- a/src/components/map/MapSettings.js +++ b/src/components/map/MapSettings.js @@ -23,6 +23,18 @@ const gridTypeSettings = [ { value: "hexHorizontal", label: "Hex Horizontal" }, ]; +const gridSquareMeasurementTypeSettings = [ + { 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 = [ + { value: "manhattan", label: "Manhattan" }, + { value: "euclidean", label: "Euclidean" }, +]; + function MapSettings({ map, mapState, @@ -69,11 +81,41 @@ function MapSettings({ } function handleGridTypeChange(option) { - let grid = { ...map.grid, type: option.value }; + const type = option.value; + let grid = { + ...map.grid, + type, + measurement: { + ...map.grid.measurement, + type: type === "square" ? "chebyshev" : "manhattan", + }, + }; grid.inset = getGridUpdatedInset(grid, map.width, map.height); onSettingsChange("grid", grid); } + function handleGridMeasurementTypeChange(option) { + const grid = { + ...map.grid, + measurement: { + ...map.grid.measurement, + type: option.value, + }, + }; + onSettingsChange("grid", grid); + } + + function handleGridMeasurementScaleChange(event) { + const grid = { + ...map.grid, + measurement: { + ...map.grid.measurement, + scale: event.target.value, + }, + }; + onSettingsChange("grid", grid); + } + function getMapSize() { let size = 0; if (map.quality === "original") { @@ -131,42 +173,77 @@ function MapSettings({ - - - s.value === map.grid.type) } + onChange={handleGridTypeChange} + isSearchable={false} /> - Draw Grid - - + + + + + + + + + + {!mapEmpty && map.type !== "default" && ( diff --git a/src/components/map/controls/MeasureToolSettings.js b/src/components/map/controls/MeasureToolSettings.js deleted file mode 100644 index e1a7a18..0000000 --- a/src/components/map/controls/MeasureToolSettings.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from "react"; -import { Flex, Input, Text } from "theme-ui"; - -import ToolSection from "./ToolSection"; -import MeasureChebyshevIcon from "../../../icons/MeasureChebyshevIcon"; -import MeasureEuclideanIcon from "../../../icons/MeasureEuclideanIcon"; -import MeasureManhattanIcon from "../../../icons/MeasureManhattanIcon"; -import MeasureAlternatingIcon from "../../../icons/MeasureAlternatingIcon"; - -import Divider from "../../Divider"; - -import { useKeyboard } from "../../../contexts/KeyboardContext"; - -function MeasureToolSettings({ settings, onSettingChange }) { - // Keyboard shortcuts - function handleKeyDown({ key }) { - if (key === "g") { - onSettingChange({ type: "chebyshev" }); - } else if (key === "l") { - onSettingChange({ type: "euclidean" }); - } else if (key === "c") { - onSettingChange({ type: "manhattan" }); - } else if (key === "a") { - onSettingChange({ type: "alternating" }); - } - } - - useKeyboard(handleKeyDown); - - const tools = [ - { - id: "chebyshev", - title: "Grid Distance (G)", - isSelected: settings.type === "chebyshev", - icon: , - }, - { - id: "alternating", - title: "Alternating Diagonal Distance (A)", - isSelected: settings.type === "alternating", - icon: , - }, - { - id: "euclidean", - title: "Line Distance (L)", - isSelected: settings.type === "euclidean", - icon: , - }, - { - id: "manhattan", - title: "City Block Distance (C)", - isSelected: settings.type === "manhattan", - icon: , - }, - ]; - - return ( - - onSettingChange({ type: tool.id })} - /> - - - Scale: - - onSettingChange({ scale: e.target.value })} - autoComplete="off" - /> - - ); -} - -export default MeasureToolSettings; diff --git a/src/contexts/GridContext.js b/src/contexts/GridContext.js index 88f872c..183b0b0 100644 --- a/src/contexts/GridContext.js +++ b/src/contexts/GridContext.js @@ -51,8 +51,8 @@ export function GridProvider({ grid, width, height, children }) { gridPixelSize.height ); const gridCellNormalizedSize = new Size( - gridCellPixelSize.width / gridPixelSize.width, - gridCellPixelSize.height / gridPixelSize.height + gridCellPixelSize.width / width, + gridCellPixelSize.height / height ); const gridOffset = Vector2.multiply(grid.inset.topLeft, { x: width, diff --git a/src/database.js b/src/database.js index 87fdd3c..6eef6ad 100644 --- a/src/database.js +++ b/src/database.js @@ -348,7 +348,7 @@ function loadVersions(db) { ); } - // 1.8.0 - Add thumbnail to maps + // 1.8.0 - Add thumbnail to maps and add measurement to grid db.version(19) .stores({}) .upgrade(async (tx) => { @@ -362,6 +362,7 @@ function loadVersions(db) { .toCollection() .modify((map) => { map.thumbnail = thumbnails[map.id]; + map.grid.measurement = { type: "chebyshev", scale: "5ft" }; }); }); diff --git a/src/helpers/grid.js b/src/helpers/grid.js index f09d5e0..cdc4b78 100644 --- a/src/helpers/grid.js +++ b/src/helpers/grid.js @@ -14,11 +14,18 @@ const GRID_TYPE_NOT_IMPLEMENTED = new Error("Grid type not implemented"); * @property {Vector2} bottomRight Bottom right position of the inset */ +/** + * @typedef GridMeasurement + * @property {("chebyshev"|"alternating"|"euclidean"|"manhattan")} type + * @property {string} scale + */ + /** * @typedef Grid * @property {GridInset} inset The inset of the grid from the map * @property {Vector2} size The number of columns and rows of the grid as `x` and `y` * @property {("square"|"hexVertical"|"hexHorizontal")} type + * @property {GridMeasurement} measurement */ /** diff --git a/src/icons/MeasureAlternatingIcon.js b/src/icons/MeasureAlternatingIcon.js deleted file mode 100644 index a65e504..0000000 --- a/src/icons/MeasureAlternatingIcon.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -function MeasureAlternatingIcon() { - return ( - - - - - ); -} - -export default MeasureAlternatingIcon; diff --git a/src/icons/MeasureChebyshevIcon.js b/src/icons/MeasureChebyshevIcon.js deleted file mode 100644 index 5073dd2..0000000 --- a/src/icons/MeasureChebyshevIcon.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -function MeasureChebyshevIcon() { - return ( - - - - - ); -} - -export default MeasureChebyshevIcon; diff --git a/src/icons/MeasureEuclideanIcon.js b/src/icons/MeasureEuclideanIcon.js deleted file mode 100644 index 6b29bf5..0000000 --- a/src/icons/MeasureEuclideanIcon.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -function MeasureEuclideanIcon() { - return ( - - - - - - - ); -} - -export default MeasureEuclideanIcon; diff --git a/src/icons/MeasureManhattanIcon.js b/src/icons/MeasureManhattanIcon.js deleted file mode 100644 index 5371f99..0000000 --- a/src/icons/MeasureManhattanIcon.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -function MeasureManhattanIcon() { - return ( - - - - - ); -} - -export default MeasureManhattanIcon; diff --git a/src/maps/index.js b/src/maps/index.js index 0aaa7ea..2b55878 100644 --- a/src/maps/index.js +++ b/src/maps/index.js @@ -25,6 +25,7 @@ export const maps = Object.keys(mapSources).map((key) => ({ size: { x: 22, y: 22 }, inset: { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: 1 } }, type: "square", + measurement: { type: "chebyshev", scale: "5ft" }, }, width: 1024, height: 1024, diff --git a/src/modals/EditMapModal.js b/src/modals/EditMapModal.js index e140e1b..37399f0 100644 --- a/src/modals/EditMapModal.js +++ b/src/modals/EditMapModal.js @@ -14,7 +14,13 @@ import { getGridDefaultInset } from "../helpers/grid"; import useResponsiveLayout from "../hooks/useResponsiveLayout"; function EditMapModal({ isOpen, onDone, mapId }) { - const { updateMap, updateMapState, getMapFromDB, mapStates } = useMapData(); + const { + updateMap, + updateMapState, + getMap, + getMapFromDB, + mapStates, + } = useMapData(); const [isLoading, setIsLoading] = useState(true); const [map, setMap] = useState(); @@ -23,7 +29,12 @@ function EditMapModal({ isOpen, onDone, mapId }) { useEffect(() => { async function loadMap() { setIsLoading(true); - setMap(await getMapFromDB(mapId)); + let loadingMap = getMap(mapId); + // Ensure file is loaded for map + if (loadingMap?.type === "file" && !loadingMap?.file) { + loadingMap = await getMapFromDB(mapId); + } + setMap(loadingMap); setMapState(mapStates.find((state) => state.mapId === mapId)); setIsLoading(false); } diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js index 488eb59..8f03924 100644 --- a/src/modals/SelectMapModal.js +++ b/src/modals/SelectMapModal.js @@ -213,11 +213,15 @@ function SelectMapModal({ grid: { size: gridSize, inset: getGridDefaultInset( - { size: { x: gridSize.x, y: gridSize.y }, type: "square" }, + { size: gridSize, type: "square" }, image.width, image.height ), type: "square", + measurement: { + type: "chebyshev", + scale: "5ft", + }, }, width: image.width, height: image.height, diff --git a/src/settings.js b/src/settings.js index d1c1028..e59cfaf 100644 --- a/src/settings.js +++ b/src/settings.js @@ -37,12 +37,16 @@ function loadVersions(settings) { ...prev, game: { usePassword: true }, })); - // v1.8.0 - Added pointer color and grid snapping sensitivity - settings.version(4, (prev) => ({ - ...prev, - pointer: { color: "red" }, - map: { ...prev.map, gridSnappingSensitivity: 0.2 }, - })); + // v1.8.0 - Added pointer color, grid snapping sensitivity and remove measure + settings.version(4, (prev) => { + let newSettings = { + ...prev, + pointer: { color: "red" }, + map: { ...prev.map, gridSnappingSensitivity: 0.2 }, + }; + delete newSettings.measure; + return newSettings; + }); } export function getSettings() {