Move measurement options to map settings
This commit is contained in:
parent
19def8cdb0
commit
96298e8943
@ -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: <MeasureToolIcon />,
|
||||
title: "Measure Tool (M)",
|
||||
SettingsComponent: MeasureToolSettings,
|
||||
},
|
||||
pointer: {
|
||||
id: "pointer",
|
||||
|
@ -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,
|
||||
|
@ -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({
|
||||
<Flex
|
||||
mt={2}
|
||||
mb={mapEmpty || map.type === "default" ? 2 : 0}
|
||||
sx={{ alignItems: "flex-end" }}
|
||||
sx={{ flexDirection: "column" }}
|
||||
>
|
||||
<Box mb={1} sx={{ width: "50%" }}>
|
||||
<Label mb={1}>Grid Type</Label>
|
||||
<Select
|
||||
isDisabled={mapEmpty || map.type === "default"}
|
||||
options={gridTypeSettings}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
gridTypeSettings.find((s) => s.value === map.grid.type)
|
||||
}
|
||||
onChange={handleGridTypeChange}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</Box>
|
||||
<Flex sx={{ width: "50%", flexDirection: "column" }} ml={2}>
|
||||
<Label>
|
||||
<Checkbox
|
||||
checked={!mapEmpty && map.showGrid}
|
||||
disabled={mapEmpty || map.type === "default"}
|
||||
onChange={(e) =>
|
||||
onSettingsChange("showGrid", e.target.checked)
|
||||
<Flex sx={{ alignItems: "flex-end" }}>
|
||||
<Box mb={1} sx={{ width: "50%" }}>
|
||||
<Label mb={1}>Grid Type</Label>
|
||||
<Select
|
||||
isDisabled={mapEmpty || map.type === "default"}
|
||||
options={gridTypeSettings}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
gridTypeSettings.find((s) => s.value === map.grid.type)
|
||||
}
|
||||
onChange={handleGridTypeChange}
|
||||
isSearchable={false}
|
||||
/>
|
||||
Draw Grid
|
||||
</Label>
|
||||
<Label>
|
||||
<Checkbox
|
||||
checked={!mapEmpty && map.snapToGrid}
|
||||
disabled={mapEmpty || map.type === "default"}
|
||||
onChange={(e) =>
|
||||
onSettingsChange("snapToGrid", e.target.checked)
|
||||
</Box>
|
||||
<Flex sx={{ width: "50%", flexDirection: "column" }} ml={2}>
|
||||
<Label>
|
||||
<Checkbox
|
||||
checked={!mapEmpty && map.showGrid}
|
||||
disabled={mapEmpty || map.type === "default"}
|
||||
onChange={(e) =>
|
||||
onSettingsChange("showGrid", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Draw Grid
|
||||
</Label>
|
||||
<Label>
|
||||
<Checkbox
|
||||
checked={!mapEmpty && map.snapToGrid}
|
||||
disabled={mapEmpty || map.type === "default"}
|
||||
onChange={(e) =>
|
||||
onSettingsChange("snapToGrid", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Snap to Grid
|
||||
</Label>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex sx={{ alignItems: "flex-end" }}>
|
||||
<Box my={2} sx={{ width: "50%" }}>
|
||||
<Label mb={1}>Grid Measurement</Label>
|
||||
<Select
|
||||
isDisabled={mapEmpty || map.type === "default"}
|
||||
options={
|
||||
map && map.grid.type === "square"
|
||||
? gridSquareMeasurementTypeSettings
|
||||
: gridHexMeasurementTypeSettings
|
||||
}
|
||||
value={
|
||||
!mapEmpty &&
|
||||
gridSquareMeasurementTypeSettings.find(
|
||||
(s) => s.value === map.grid.measurement.type
|
||||
)
|
||||
}
|
||||
onChange={handleGridMeasurementTypeChange}
|
||||
isSearchable={false}
|
||||
/>
|
||||
Snap to Grid
|
||||
</Label>
|
||||
</Box>
|
||||
<Box mb={1} mx={2} sx={{ flexGrow: 1 }}>
|
||||
<Label htmlFor="gridMeasurementScale">Grid Scale</Label>
|
||||
<Input
|
||||
name="gridMeasurementScale"
|
||||
value={`${map && map.grid.measurement.scale}`}
|
||||
onChange={handleGridMeasurementScaleChange}
|
||||
disabled={mapEmpty || map.type === "default"}
|
||||
min={1}
|
||||
my={1}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{!mapEmpty && map.type !== "default" && (
|
||||
|
@ -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: <MeasureChebyshevIcon />,
|
||||
},
|
||||
{
|
||||
id: "alternating",
|
||||
title: "Alternating Diagonal Distance (A)",
|
||||
isSelected: settings.type === "alternating",
|
||||
icon: <MeasureAlternatingIcon />,
|
||||
},
|
||||
{
|
||||
id: "euclidean",
|
||||
title: "Line Distance (L)",
|
||||
isSelected: settings.type === "euclidean",
|
||||
icon: <MeasureEuclideanIcon />,
|
||||
},
|
||||
{
|
||||
id: "manhattan",
|
||||
title: "City Block Distance (C)",
|
||||
isSelected: settings.type === "manhattan",
|
||||
icon: <MeasureManhattanIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ToolSection
|
||||
tools={tools}
|
||||
onToolClick={(tool) => onSettingChange({ type: tool.id })}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<Text as="label" variant="body2" sx={{ fontSize: "16px" }} p={1}>
|
||||
Scale:
|
||||
</Text>
|
||||
<Input
|
||||
p={1}
|
||||
pl={0}
|
||||
sx={{
|
||||
width: "40px",
|
||||
border: "none",
|
||||
":focus": {
|
||||
outline: "none",
|
||||
},
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
value={settings.scale}
|
||||
onChange={(e) => onSettingChange({ scale: e.target.value })}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeasureToolSettings;
|
@ -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,
|
||||
|
@ -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" };
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function MeasureAlternatingIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M15.54 4.93a2.5 2.5 0 1 1 .85 4.1l-.94.94a4 4 0 0 1-5.48 5.48l-.95.94a2.5 2.5 0 1 1-1.41-1.41l.94-.95a4 4 0 0 1 5.48-5.48l.95-.94a2.5 2.5 0 0 1 .56-2.68z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeasureAlternatingIcon;
|
@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function MeasureChebyshevIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeasureChebyshevIcon;
|
@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function MeasureEuclideanIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M6.36,18.78L6.61,21l1.62-1.54l2.77-7.6c-0.68-0.17-1.28-0.51-1.77-0.98L6.36,18.78z" />
|
||||
<path d="M14.77,10.88c-0.49,0.47-1.1,0.81-1.77,0.98l2.77,7.6L17.39,21l0.26-2.22L14.77,10.88z" />
|
||||
<path d="M14.94,8.6c0.3-1.56-0.6-2.94-1.94-3.42V4c0-0.55-0.45-1-1-1h0c-0.55,0-1,0.45-1,1v1.18C9.84,5.6,9,6.7,9,8 c0,1.84,1.66,3.3,3.56,2.95C13.74,10.73,14.71,9.78,14.94,8.6z M12,9c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1s1,0.45,1,1 C13,8.55,12.55,9,12,9z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeasureEuclideanIcon;
|
@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function MeasureManhattanIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M17,11V5c0-1.1-0.9-2-2-2H9C7.9,3,7,3.9,7,5v2H5C3.9,7,3,7.9,3,9v10c0,1.1,0.9,2,2,2h5c0.55,0,1-0.45,1-1v-3h2v3 c0,0.55,0.45,1,1,1h5c1.1,0,2-0.9,2-2v-6c0-1.1-0.9-2-2-2H17z M7,19H5v-2h2V19z M7,15H5v-2h2V15z M7,11H5V9h2V11z M11,15H9v-2h2V15 z M11,11H9V9h2V11z M11,7H9V5h2V7z M15,15h-2v-2h2V15z M15,11h-2V9h2V11z M15,7h-2V5h2V7z M19,19h-2v-2h2V19z M19,15h-2v-2h2V15z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeasureManhattanIcon;
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user