From c1ceaed4cf1d197ba534ceb2eff336c0f30aaf9e Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 28 Jun 2023 13:05:49 +0200 Subject: [PATCH 01/44] usecases: add grid layer. --- doc/usecases/done/gridlayer.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 doc/usecases/done/gridlayer.md diff --git a/doc/usecases/done/gridlayer.md b/doc/usecases/done/gridlayer.md new file mode 100644 index 000000000..7e382a9be --- /dev/null +++ b/doc/usecases/done/gridlayer.md @@ -0,0 +1,21 @@ +# Use Case: Grid Layer + +## Summary + +- **Scope:** Grid Layer +- **Level:** User Goal +- **Actors:** App User +- **Brief:** The app will display a fixed scale coordinate grid. +- **Status:** Done +- **Assignee:** Moritz + +## Scenarios + +- **Precondition:** + - User has opened the map. +- **Main success scenario:** + - The user sees a fixed scale coordinate grid with 10 centimeter spacing. + - Lines that are a whole number of meters away from the origin are drawn boldly. +- **Non-functional Constraints:** + - Support for changing viewport (zoom, position, etc.) + - The grid layer is only available in the frontend. \ No newline at end of file From a12c903da5d9e99a8e3948175f0b166fd4f33679 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 29 Jun 2023 11:00:32 +0200 Subject: [PATCH 02/44] layers: add grid layer. --- .../map_planning/components/BaseStage.tsx | 36 +++++++++-- .../features/map_planning/components/Map.tsx | 2 + .../layers/_frontend_only/grid/GridLayer.tsx | 61 +++++++++++++++++++ .../layers/_frontend_only/index.ts | 3 + .../map_planning/store/MapStoreTypes.ts | 29 +++++++-- .../map_planning/store/UntrackedMapStore.ts | 13 +++- 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/index.ts diff --git a/frontend/src/features/map_planning/components/BaseStage.tsx b/frontend/src/features/map_planning/components/BaseStage.tsx index 8994c2425..59941e637 100644 --- a/frontend/src/features/map_planning/components/BaseStage.tsx +++ b/frontend/src/features/map_planning/components/BaseStage.tsx @@ -37,6 +37,8 @@ export const BaseStage = ({ selectable = true, draggable = true, }: BaseStageProps) => { + const updateMapBounds = useMapStore(store => store.updateMapBounds); + // Represents the state of the stage const [stage, setStage] = useState({ scale: 1, @@ -79,21 +81,30 @@ export const BaseStage = ({ const onStageWheel = (e: KonvaEventObject) => { e.evt.preventDefault(); - const stage = e.target.getStage(); - if (stage === null) return; + const targetStage = e.target.getStage(); + if (targetStage === null) return; - const pointerVector = stage.getPointerPosition(); + const pointerVector = targetStage.getPointerPosition(); if (pointerVector === null) return; if (e.evt.ctrlKey) { if (zoomable) { - handleZoom(pointerVector, e.evt.deltaY, stage, setStage); + handleZoom(pointerVector, e.evt.deltaY, targetStage, setStage); } } else { if (scrollable) { - handleScroll(e.evt.deltaX, e.evt.deltaY, stage); + handleScroll(e.evt.deltaX, e.evt.deltaY, targetStage); } } + + if (stageRef.current === null) return; + + updateMapBounds({ + x: Math.floor(stageRef.current.getAbsolutePosition().x / stage.scale), + y: Math.floor(stageRef.current.getAbsolutePosition().y / stage.scale), + width: Math.floor(window.innerWidth / stage.scale), + height: Math.floor(window.innerHeight / stage.scale), + }); }; // Event listener responsible for allowing dragging of the stage only with the wheel mouse button @@ -117,6 +128,20 @@ export const BaseStage = ({ } }; + const onStageDragEnd = (e: KonvaEventObject) => { + if (e.evt === null || e.evt === undefined) return; + e.evt.preventDefault(); + + if (stageRef.current === null) return; + + updateMapBounds({ + x: Math.floor(stageRef.current.getAbsolutePosition().x / stage.scale), + y: Math.floor(stageRef.current.getAbsolutePosition().y / stage.scale), + width: Math.floor(window.innerWidth / stage.scale), + height: Math.floor(window.innerHeight / stage.scale), + }); + }; + // Event listener responsible for updating the selection rectangle const onMouseMove = (e: KonvaEventObject) => { e.evt.preventDefault(); @@ -180,6 +205,7 @@ export const BaseStage = ({ width={window.innerWidth} height={window.innerHeight} onWheel={onStageWheel} + onDragEnd={onStageDragEnd} onDragStart={onStageDragStart} onMouseDown={onStageMouseDown} onMouseMove={onMouseMove} diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index b793999f6..e2d24f861 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -14,6 +14,7 @@ import { ReactComponent as MoveIcon } from '@/icons/move.svg'; import { ReactComponent as PlantIcon } from '@/icons/plant.svg'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; +import {GridLayer} from "@/features/map_planning/layers/_frontend_only/grid/GridLayer"; export type MapProps = { layers: LayerDto[]; @@ -155,6 +156,7 @@ export const Map = ({ layers }: MapProps) => { opacity={untrackedState.layers.plants.opacity} listening={selectedLayer.type_ === LayerType.Plants} > +
{ + const mapBounds = useMapStore(state => state.untrackedState.editorBounds); + + return ( + + + + ) +}; + +interface GridProps { + x: number, + y: number, + width: number, + height: number, +}; + +const Grid = (rect: GridProps) => { + let step = 10; + if (rect.width > 5000) { + step = 1000; + } else if (rect.width > 2000) { + step = 100; + } + + const dynamicStrokeWidth = rect.width / 3000; + + const startX = -rect.x - rect.width - ((-rect.x - rect.width) % step); + const startY = -rect.y - rect.height - ((-rect.y - rect.height) % step); + + const endX = -rect.x + rect.width; + const endY = -rect.y + rect.height; + + const horizontalLines = []; + for(let x = startX; x < endX; x += step) { + const width = x % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; + horizontalLines.push(); + } + + const verticalLines = []; + for(let y = startY; y < endY; y += step) { + const width = y % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; + verticalLines.push(); + } + + return ( + + {horizontalLines} + {verticalLines} + + ) +} \ No newline at end of file diff --git a/frontend/src/features/map_planning/layers/_frontend_only/index.ts b/frontend/src/features/map_planning/layers/_frontend_only/index.ts new file mode 100644 index 000000000..1a69d7047 --- /dev/null +++ b/frontend/src/features/map_planning/layers/_frontend_only/index.ts @@ -0,0 +1,3 @@ +export enum FrontendOnlyLayerType { + Grid = "grid", +} \ No newline at end of file diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index c38617806..344e79906 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -1,6 +1,13 @@ import { LayerDto, LayerType, PlantingDto, PlantsSummaryDto } from '@/bindings/definitions'; import Konva from 'konva'; import { Node } from 'konva/lib/Node'; +import {FrontendOnlyLayerType} from "@/features/map_planning/layers/_frontend_only"; + +/** + * This type combines layers that are only available in the frontend + * with layers that are also reflected in the backend. + */ +export type CombinedLayerType = LayerType | FrontendOnlyLayerType; /** * An action is a change to the map state, initiated by the user. @@ -102,14 +109,16 @@ export type History = Array>; export interface UntrackedMapSlice { untrackedState: UntrackedMapState; stageRef: React.RefObject; + updateMapBounds: (bounds: BoundsRect) => void; updateSelectedLayer: (selectedLayer: LayerDto) => void; - updateLayerVisible: (layerName: LayerType, visible: UntrackedLayerState['visible']) => void; - updateLayerOpacity: (layerName: LayerType, opacity: UntrackedLayerState['opacity']) => void; + updateLayerVisible: (layerName: CombinedLayerType, visible: UntrackedLayerState['visible']) => void; + updateLayerOpacity: (layerName: CombinedLayerType, opacity: UntrackedLayerState['opacity']) => void; selectPlantForPlanting: (plant: PlantsSummaryDto | null) => void; selectPlanting: (planting: PlantingDto | null) => void; } const LAYER_TYPES = Object.values(LayerType); +const COMBINED_LAYER_TYPES = [...Object.values(LayerType), ...Object.values(FrontendOnlyLayerType)]; export const TRACKED_DEFAULT_STATE: TrackedMapState = { layers: LAYER_TYPES.reduce( @@ -133,6 +142,7 @@ export const TRACKED_DEFAULT_STATE: TrackedMapState = { export const UNTRACKED_DEFAULT_STATE: UntrackedMapState = { mapId: -1, + editorBounds: {x: 0, y: 0, width: 0, height: 0}, selectedLayer: { id: -1, is_alternative: false, @@ -140,7 +150,7 @@ export const UNTRACKED_DEFAULT_STATE: UntrackedMapState = { type_: LayerType.Base, map_id: -1, }, - layers: LAYER_TYPES.reduce( + layers: COMBINED_LAYER_TYPES.reduce( (acc, layerName) => ({ ...acc, [layerName]: { @@ -214,7 +224,7 @@ export type TrackedBaseLayerState = { * The state of the layers of the map. */ export type UntrackedLayers = { - [key in Exclude]: UntrackedLayerState; + [key in Exclude]: UntrackedLayerState; } & { [LayerType.Plants]: UntrackedPlantLayerState; }; @@ -236,6 +246,17 @@ export type TrackedMapState = { */ export type UntrackedMapState = { mapId: number; + editorBounds: BoundsRect, selectedLayer: LayerDto; layers: UntrackedLayers; }; + +/** + * Represents a simple rectangle with width, height and position. + */ +export type BoundsRect = { + x: number, + y: number, + width: number, + height: number, +} diff --git a/frontend/src/features/map_planning/store/UntrackedMapStore.ts b/frontend/src/features/map_planning/store/UntrackedMapStore.ts index 0c3f501a2..b933090d1 100644 --- a/frontend/src/features/map_planning/store/UntrackedMapStore.ts +++ b/frontend/src/features/map_planning/store/UntrackedMapStore.ts @@ -1,7 +1,9 @@ -import { TrackedMapSlice, UNTRACKED_DEFAULT_STATE, UntrackedMapSlice } from './MapStoreTypes'; +import {BoundsRect, TrackedMapSlice, UNTRACKED_DEFAULT_STATE, UntrackedMapSlice} from './MapStoreTypes'; import Konva from 'konva'; import { createRef } from 'react'; import { StateCreator } from 'zustand'; +import {Simulate} from "react-dom/test-utils"; +import select = Simulate.select; export const createUntrackedMapSlice: StateCreator< TrackedMapSlice & UntrackedMapSlice, @@ -11,6 +13,15 @@ export const createUntrackedMapSlice: StateCreator< > = (set, get) => ({ untrackedState: UNTRACKED_DEFAULT_STATE, stageRef: createRef(), + updateMapBounds(bounds: BoundsRect) { + set((state) => ({ + ...state, + untrackedState: { + ...state.untrackedState, + editorBounds: bounds, + } + })); + }, updateSelectedLayer(selectedLayer) { // Clear the transformer's nodes. get().transformer.current?.nodes([]); From 420a0f1b23989008ef212da022317770c26c116f Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 29 Jun 2023 11:35:47 +0200 Subject: [PATCH 03/44] grid layer: add yard stick. --- .../layers/_frontend_only/grid/GridLayer.tsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 121060d74..d869e2c5b 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,4 +1,4 @@ -import {Layer, Group, Line, Circle} from "react-konva"; +import {Layer, Group, Line, Text} from "react-konva"; import Konva from "konva/cmj"; import useMapStore from "@/features/map_planning/store/MapStore"; @@ -13,6 +13,12 @@ export const GridLayer = (props: Konva.LayerConfig) => { width={mapBounds.width} height={mapBounds.height} /> + ) }; @@ -58,4 +64,20 @@ const Grid = (rect: GridProps) => { {verticalLines} ) +} + +const YardStick = (rect: GridProps) => { + const dynamicStrokeWidth = rect.width / 1000; + + const startX = -rect.x + (rect.width / 15); + const endX = -rect.x + (rect.width / 15) + 100; + + const y = -rect.y + (rect.width / 15); + + return ( + + + + + ) } \ No newline at end of file From 528fd240208c20318c2e04369bf3ab303b7261b6 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 29 Jun 2023 11:37:15 +0200 Subject: [PATCH 04/44] chore: format and lint. --- .../map_planning/components/BaseStage.tsx | 2 +- .../features/map_planning/components/Map.tsx | 2 +- .../layers/_frontend_only/grid/GridLayer.tsx | 127 +++++++++--------- .../layers/_frontend_only/index.ts | 4 +- .../map_planning/store/MapStoreTypes.ts | 26 ++-- .../map_planning/store/UntrackedMapStore.ts | 11 +- 6 files changed, 90 insertions(+), 82 deletions(-) diff --git a/frontend/src/features/map_planning/components/BaseStage.tsx b/frontend/src/features/map_planning/components/BaseStage.tsx index 59941e637..edaf5cd25 100644 --- a/frontend/src/features/map_planning/components/BaseStage.tsx +++ b/frontend/src/features/map_planning/components/BaseStage.tsx @@ -37,7 +37,7 @@ export const BaseStage = ({ selectable = true, draggable = true, }: BaseStageProps) => { - const updateMapBounds = useMapStore(store => store.updateMapBounds); + const updateMapBounds = useMapStore((store) => store.updateMapBounds); // Represents the state of the stage const [stage, setStage] = useState({ diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index e2d24f861..b8a0de153 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -9,12 +9,12 @@ import { Layers } from './toolbar/Layers'; import { Toolbar } from './toolbar/Toolbar'; import { LayerDto, LayerType } from '@/bindings/definitions'; import IconButton from '@/components/Button/IconButton'; +import { GridLayer } from '@/features/map_planning/layers/_frontend_only/grid/GridLayer'; import { ReactComponent as ArrowIcon } from '@/icons/arrow.svg'; import { ReactComponent as MoveIcon } from '@/icons/move.svg'; import { ReactComponent as PlantIcon } from '@/icons/plant.svg'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; -import {GridLayer} from "@/features/map_planning/layers/_frontend_only/grid/GridLayer"; export type MapProps = { layers: LayerDto[]; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index d869e2c5b..e5b1a001b 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,83 +1,82 @@ -import {Layer, Group, Line, Text} from "react-konva"; -import Konva from "konva/cmj"; -import useMapStore from "@/features/map_planning/store/MapStore"; +import useMapStore from '@/features/map_planning/store/MapStore'; +import Konva from 'konva/cmj'; +import { Layer, Group, Line, Text } from 'react-konva'; export const GridLayer = (props: Konva.LayerConfig) => { - const mapBounds = useMapStore(state => state.untrackedState.editorBounds); + const mapBounds = useMapStore((state) => state.untrackedState.editorBounds); - return ( - - - - - ) + return ( + + + + + ); }; interface GridProps { - x: number, - y: number, - width: number, - height: number, -}; + x: number; + y: number; + width: number; + height: number; +} const Grid = (rect: GridProps) => { - let step = 10; - if (rect.width > 5000) { - step = 1000; - } else if (rect.width > 2000) { - step = 100; - } + let step = 10; + if (rect.width > 5000) { + step = 1000; + } else if (rect.width > 2000) { + step = 100; + } - const dynamicStrokeWidth = rect.width / 3000; + const dynamicStrokeWidth = rect.width / 3000; - const startX = -rect.x - rect.width - ((-rect.x - rect.width) % step); - const startY = -rect.y - rect.height - ((-rect.y - rect.height) % step); + const startX = -rect.x - rect.width - ((-rect.x - rect.width) % step); + const startY = -rect.y - rect.height - ((-rect.y - rect.height) % step); - const endX = -rect.x + rect.width; - const endY = -rect.y + rect.height; + const endX = -rect.x + rect.width; + const endY = -rect.y + rect.height; - const horizontalLines = []; - for(let x = startX; x < endX; x += step) { - const width = x % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; - horizontalLines.push(); - } + const horizontalLines = []; + for (let x = startX; x < endX; x += step) { + const width = x % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; + horizontalLines.push( + , + ); + } - const verticalLines = []; - for(let y = startY; y < endY; y += step) { - const width = y % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; - verticalLines.push(); - } + const verticalLines = []; + for (let y = startY; y < endY; y += step) { + const width = y % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; + verticalLines.push( + , + ); + } - return ( - - {horizontalLines} - {verticalLines} - - ) -} + return ( + + {horizontalLines} + {verticalLines} + + ); +}; const YardStick = (rect: GridProps) => { - const dynamicStrokeWidth = rect.width / 1000; + const dynamicStrokeWidth = rect.width / 1000; - const startX = -rect.x + (rect.width / 15); - const endX = -rect.x + (rect.width / 15) + 100; + const startX = -rect.x + rect.width / 15; + const endX = -rect.x + rect.width / 15 + 100; - const y = -rect.y + (rect.width / 15); + const y = -rect.y + rect.width / 15; - return ( - - - - - ) -} \ No newline at end of file + return ( + + + + + ); +}; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/index.ts b/frontend/src/features/map_planning/layers/_frontend_only/index.ts index 1a69d7047..a31bc501d 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/index.ts +++ b/frontend/src/features/map_planning/layers/_frontend_only/index.ts @@ -1,3 +1,3 @@ export enum FrontendOnlyLayerType { - Grid = "grid", -} \ No newline at end of file + Grid = 'grid', +} diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index 344e79906..480c0f5cf 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -1,7 +1,7 @@ import { LayerDto, LayerType, PlantingDto, PlantsSummaryDto } from '@/bindings/definitions'; +import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; import Konva from 'konva'; import { Node } from 'konva/lib/Node'; -import {FrontendOnlyLayerType} from "@/features/map_planning/layers/_frontend_only"; /** * This type combines layers that are only available in the frontend @@ -111,8 +111,14 @@ export interface UntrackedMapSlice { stageRef: React.RefObject; updateMapBounds: (bounds: BoundsRect) => void; updateSelectedLayer: (selectedLayer: LayerDto) => void; - updateLayerVisible: (layerName: CombinedLayerType, visible: UntrackedLayerState['visible']) => void; - updateLayerOpacity: (layerName: CombinedLayerType, opacity: UntrackedLayerState['opacity']) => void; + updateLayerVisible: ( + layerName: CombinedLayerType, + visible: UntrackedLayerState['visible'], + ) => void; + updateLayerOpacity: ( + layerName: CombinedLayerType, + opacity: UntrackedLayerState['opacity'], + ) => void; selectPlantForPlanting: (plant: PlantsSummaryDto | null) => void; selectPlanting: (planting: PlantingDto | null) => void; } @@ -142,7 +148,7 @@ export const TRACKED_DEFAULT_STATE: TrackedMapState = { export const UNTRACKED_DEFAULT_STATE: UntrackedMapState = { mapId: -1, - editorBounds: {x: 0, y: 0, width: 0, height: 0}, + editorBounds: { x: 0, y: 0, width: 0, height: 0 }, selectedLayer: { id: -1, is_alternative: false, @@ -246,7 +252,7 @@ export type TrackedMapState = { */ export type UntrackedMapState = { mapId: number; - editorBounds: BoundsRect, + editorBounds: BoundsRect; selectedLayer: LayerDto; layers: UntrackedLayers; }; @@ -255,8 +261,8 @@ export type UntrackedMapState = { * Represents a simple rectangle with width, height and position. */ export type BoundsRect = { - x: number, - y: number, - width: number, - height: number, -} + x: number; + y: number; + width: number; + height: number; +}; diff --git a/frontend/src/features/map_planning/store/UntrackedMapStore.ts b/frontend/src/features/map_planning/store/UntrackedMapStore.ts index b933090d1..368cfe4aa 100644 --- a/frontend/src/features/map_planning/store/UntrackedMapStore.ts +++ b/frontend/src/features/map_planning/store/UntrackedMapStore.ts @@ -1,9 +1,12 @@ -import {BoundsRect, TrackedMapSlice, UNTRACKED_DEFAULT_STATE, UntrackedMapSlice} from './MapStoreTypes'; +import { + BoundsRect, + TrackedMapSlice, + UNTRACKED_DEFAULT_STATE, + UntrackedMapSlice, +} from './MapStoreTypes'; import Konva from 'konva'; import { createRef } from 'react'; import { StateCreator } from 'zustand'; -import {Simulate} from "react-dom/test-utils"; -import select = Simulate.select; export const createUntrackedMapSlice: StateCreator< TrackedMapSlice & UntrackedMapSlice, @@ -19,7 +22,7 @@ export const createUntrackedMapSlice: StateCreator< untrackedState: { ...state.untrackedState, editorBounds: bounds, - } + }, })); }, updateSelectedLayer(selectedLayer) { From a47fbb9c9e50228d820392c4936e5323d4701c31 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Fri, 30 Jun 2023 13:37:13 +0200 Subject: [PATCH 05/44] grid layer: exchange lines with dots. --- .../layers/_frontend_only/grid/GridLayer.tsx | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index e5b1a001b..866fc269b 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -27,40 +27,32 @@ interface GridProps { const Grid = (rect: GridProps) => { let step = 10; - if (rect.width > 5000) { + let dynamicStrokeWidth = rect.width / 1000; + if (rect.width > 10000) { step = 1000; - } else if (rect.width > 2000) { + } else if (rect.width > 1000) { step = 100; } - const dynamicStrokeWidth = rect.width / 3000; - const startX = -rect.x - rect.width - ((-rect.x - rect.width) % step); const startY = -rect.y - rect.height - ((-rect.y - rect.height) % step); const endX = -rect.x + rect.width; const endY = -rect.y + rect.height; - const horizontalLines = []; - for (let x = startX; x < endX; x += step) { - const width = x % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; - horizontalLines.push( - , - ); - } - - const verticalLines = []; + const lines = []; for (let y = startY; y < endY; y += step) { - const width = y % 100 === 0 ? dynamicStrokeWidth * 2 : dynamicStrokeWidth; - verticalLines.push( - , + lines.push( + , ); } return ( - {horizontalLines} - {verticalLines} + {lines} ); }; From 4ab7e8f9f729010391c7c8e2101e74abda671635 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Fri, 30 Jun 2023 15:09:11 +0200 Subject: [PATCH 06/44] grid layer: improve yard stick. --- .../layers/_frontend_only/grid/GridLayer.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 866fc269b..2c27b8403 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -60,15 +60,15 @@ const Grid = (rect: GridProps) => { const YardStick = (rect: GridProps) => { const dynamicStrokeWidth = rect.width / 1000; - const startX = -rect.x + rect.width / 15; - const endX = -rect.x + rect.width / 15 + 100; + const startX = -rect.x + rect.width / 20; + const endX = -rect.x + rect.width / 20 + 100; - const y = -rect.y + rect.width / 15; + const y = -rect.y + rect.width / 30; return ( - - + + ); }; From 00af2d8691a587da5d2bbb91b1ae5706141ad733 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 5 Jul 2023 21:50:35 +0200 Subject: [PATCH 07/44] usecase: suggested changes by markus Co-authored-by: Markus Raab --- doc/usecases/done/gridlayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usecases/done/gridlayer.md b/doc/usecases/done/gridlayer.md index 7e382a9be..56831b3b9 100644 --- a/doc/usecases/done/gridlayer.md +++ b/doc/usecases/done/gridlayer.md @@ -18,4 +18,4 @@ - Lines that are a whole number of meters away from the origin are drawn boldly. - **Non-functional Constraints:** - Support for changing viewport (zoom, position, etc.) - - The grid layer is only available in the frontend. \ No newline at end of file + - The functionality is only available in the frontend. \ No newline at end of file From 17360e19004ab54ea77ab17fb543dd3102b5cd37 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 5 Jul 2023 22:00:03 +0200 Subject: [PATCH 08/44] usecase: update with suggestions from Markus. Co-authored-by: Markus Raab --- doc/usecases/done/gridlayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usecases/done/gridlayer.md b/doc/usecases/done/gridlayer.md index 56831b3b9..1e6cc034e 100644 --- a/doc/usecases/done/gridlayer.md +++ b/doc/usecases/done/gridlayer.md @@ -14,7 +14,7 @@ - **Precondition:** - User has opened the map. - **Main success scenario:** - - The user sees a fixed scale coordinate grid with 10 centimeter spacing. + - The user sees a fixed scale coordinate grid with 1 meter spacing. - Lines that are a whole number of meters away from the origin are drawn boldly. - **Non-functional Constraints:** - Support for changing viewport (zoom, position, etc.) From 2caf7aff26bea5156d9b295857596bebaf8c36ac Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 5 Jul 2023 22:00:12 +0200 Subject: [PATCH 09/44] Update doc/usecases/done/gridlayer.md Co-authored-by: Markus Raab --- doc/usecases/done/gridlayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usecases/done/gridlayer.md b/doc/usecases/done/gridlayer.md index 1e6cc034e..bf255b0a4 100644 --- a/doc/usecases/done/gridlayer.md +++ b/doc/usecases/done/gridlayer.md @@ -15,7 +15,7 @@ - User has opened the map. - **Main success scenario:** - The user sees a fixed scale coordinate grid with 1 meter spacing. - - Lines that are a whole number of meters away from the origin are drawn boldly. + - Dots that indicate 10 m are bigger. - **Non-functional Constraints:** - Support for changing viewport (zoom, position, etc.) - The functionality is only available in the frontend. \ No newline at end of file From 21a42c2fb714a188f58b7792563ef2386ed25526 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Mon, 10 Jul 2023 16:40:46 +0200 Subject: [PATCH 10/44] grid: change title of use case Co-authored-by: Markus Raab --- doc/usecases/done/gridlayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usecases/done/gridlayer.md b/doc/usecases/done/gridlayer.md index bf255b0a4..cedc0b748 100644 --- a/doc/usecases/done/gridlayer.md +++ b/doc/usecases/done/gridlayer.md @@ -1,4 +1,4 @@ -# Use Case: Grid Layer +# Use Case: Grid ## Summary From 8159bb766e3ade4acfea663a60a55d84013b088f Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Mon, 10 Jul 2023 17:21:27 +0200 Subject: [PATCH 11/44] merge: add missing imports. --- frontend/src/features/map_planning/components/Map.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index ee91ecfb5..100375e06 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -12,6 +12,7 @@ import IconButton from '@/components/Button/IconButton'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; import { useTranslation } from 'react-i18next'; +import {GridLayer} from "@/features/map_planning/layers/_frontend_only/grid/GridLayer"; export type MapProps = { layers: LayerDto[]; From ac11c368d97a0d1d73e2ec402ccd11534047fc33 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Mon, 10 Jul 2023 20:21:31 +0200 Subject: [PATCH 12/44] grid: extract constants. --- .../layers/_frontend_only/grid/GridLayer.tsx | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 2c27b8403..14dc8d168 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -2,6 +2,16 @@ import useMapStore from '@/features/map_planning/store/MapStore'; import Konva from 'konva/cmj'; import { Layer, Group, Line, Text } from 'react-konva'; +const TEN_CENTIMETERS = 10; +const ONE_METER = 100; + +const RELATIVE_DOT_SIZE = 1 / 1000; + +const RELATIVE_YARD_STICK_OFFSET_X = 1 / 20; +const RELATIVE_YARD_STICK_OFFSET_Y = 1 / 30; + +const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; + export const GridLayer = (props: Konva.LayerConfig) => { const mapBounds = useMapStore((state) => state.untrackedState.editorBounds); @@ -26,27 +36,28 @@ interface GridProps { } const Grid = (rect: GridProps) => { - let step = 10; - let dynamicStrokeWidth = rect.width / 1000; - if (rect.width > 10000) { - step = 1000; + let gridStep = TEN_CENTIMETERS; + if (rect.width > 100 * ONE_METER) { + gridStep = 10 * ONE_METER; } else if (rect.width > 1000) { - step = 100; + gridStep = ONE_METER; } - const startX = -rect.x - rect.width - ((-rect.x - rect.width) % step); - const startY = -rect.y - rect.height - ((-rect.y - rect.height) % step); + let gridDotSize = rect.width * RELATIVE_DOT_SIZE; + + const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); + const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); const endX = -rect.x + rect.width; const endY = -rect.y + rect.height; const lines = []; - for (let y = startY; y < endY; y += step) { + for (let y = startY; y < endY; y += gridStep) { lines.push( - , + dash={[gridDotSize, gridStep - gridDotSize]}>, ); } @@ -58,17 +69,31 @@ const Grid = (rect: GridProps) => { }; const YardStick = (rect: GridProps) => { - const dynamicStrokeWidth = rect.width / 1000; + let yardStickLength = TEN_CENTIMETERS; + let yardStickLengthLabel = '10cm'; + + if (rect.width > 100 * ONE_METER) { + yardStickLength = 10 * ONE_METER; + yardStickLengthLabel = '10m'; + } else if (rect.width > 1000) { + yardStickLength = ONE_METER; + yardStickLengthLabel = '1m'; + } + + const strokeWidth = rect.width * RELATIVE_DOT_SIZE; + + const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; + const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; - const startX = -rect.x + rect.width / 20; - const endX = -rect.x + rect.width / 20 + 100; + const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; - const y = -rect.y + rect.width / 30; + const textX = lineStartX; + const textY = lineY + (rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y); return ( - - + + ); }; From 52560c38d9fb5b81a6ec774e41918383fc7fdad2 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Mon, 10 Jul 2023 20:25:50 +0200 Subject: [PATCH 13/44] grid: use translations. --- frontend/src/config/i18n/de/common.json | 4 +++- frontend/src/config/i18n/en/common.json | 4 +++- .../layers/_frontend_only/grid/GridLayer.tsx | 9 ++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/config/i18n/de/common.json b/frontend/src/config/i18n/de/common.json index d709e4b20..b2edf0e06 100644 --- a/frontend/src/config/i18n/de/common.json +++ b/frontend/src/config/i18n/de/common.json @@ -5,5 +5,7 @@ "no": "Nein", "cancel": "Abbrechen", "unknown_error": "Ein unbekannter Fehler ist aufgetreten.", - "save": "Speichern" + "save": "Speichern", + "meter_shorthand": "m", + "centimeter_shorthand": "cm" } diff --git a/frontend/src/config/i18n/en/common.json b/frontend/src/config/i18n/en/common.json index 0acaf8a51..a02653f30 100644 --- a/frontend/src/config/i18n/en/common.json +++ b/frontend/src/config/i18n/en/common.json @@ -5,5 +5,7 @@ "no": "No", "cancel": "Cancel", "unknown_error": "An unknown error occurred.", - "save": "Save" + "save": "Save", + "meter_shorthand": "m", + "centimeter_shorthand": "cm" } diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 14dc8d168..631d28787 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,6 +1,7 @@ import useMapStore from '@/features/map_planning/store/MapStore'; import Konva from 'konva/cmj'; import { Layer, Group, Line, Text } from 'react-konva'; +import {useTranslation} from "react-i18next"; const TEN_CENTIMETERS = 10; const ONE_METER = 100; @@ -69,15 +70,17 @@ const Grid = (rect: GridProps) => { }; const YardStick = (rect: GridProps) => { + const {t} = useTranslation('common'); + let yardStickLength = TEN_CENTIMETERS; - let yardStickLengthLabel = '10cm'; + let yardStickLengthLabel = '10' + t('centimeter_shorthand'); if (rect.width > 100 * ONE_METER) { yardStickLength = 10 * ONE_METER; - yardStickLengthLabel = '10m'; + yardStickLengthLabel = '10' + t('meter_shorthand'); } else if (rect.width > 1000) { yardStickLength = ONE_METER; - yardStickLengthLabel = '1m'; + yardStickLengthLabel = '1' + t('meter_shorthand'); } const strokeWidth = rect.width * RELATIVE_DOT_SIZE; From a3acd03524be11f5031dc17fc7ac4eb3f83d9339 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Mon, 10 Jul 2023 20:26:45 +0200 Subject: [PATCH 14/44] grid: lint and format. --- .../features/map_planning/components/Map.tsx | 2 +- .../layers/_frontend_only/grid/GridLayer.tsx | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index 100375e06..0419b494c 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -9,10 +9,10 @@ import { Layers } from './toolbar/Layers'; import { Toolbar } from './toolbar/Toolbar'; import { LayerDto, LayerType } from '@/bindings/definitions'; import IconButton from '@/components/Button/IconButton'; +import { GridLayer } from '@/features/map_planning/layers/_frontend_only/grid/GridLayer'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; import { useTranslation } from 'react-i18next'; -import {GridLayer} from "@/features/map_planning/layers/_frontend_only/grid/GridLayer"; export type MapProps = { layers: LayerDto[]; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 631d28787..6f4f4cd1f 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,7 +1,7 @@ import useMapStore from '@/features/map_planning/store/MapStore'; import Konva from 'konva/cmj'; +import { useTranslation } from 'react-i18next'; import { Layer, Group, Line, Text } from 'react-konva'; -import {useTranslation} from "react-i18next"; const TEN_CENTIMETERS = 10; const ONE_METER = 100; @@ -44,7 +44,7 @@ const Grid = (rect: GridProps) => { gridStep = ONE_METER; } - let gridDotSize = rect.width * RELATIVE_DOT_SIZE; + const gridDotSize = rect.width * RELATIVE_DOT_SIZE; const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); @@ -55,22 +55,20 @@ const Grid = (rect: GridProps) => { const lines = []; for (let y = startY; y < endY; y += gridStep) { lines.push( - , + , ); } - return ( - - {lines} - - ); + return {lines}; }; const YardStick = (rect: GridProps) => { - const {t} = useTranslation('common'); + const { t } = useTranslation('common'); let yardStickLength = TEN_CENTIMETERS; let yardStickLengthLabel = '10' + t('centimeter_shorthand'); @@ -91,12 +89,22 @@ const YardStick = (rect: GridProps) => { const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; const textX = lineStartX; - const textY = lineY + (rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y); + const textY = lineY + rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y; return ( - - + + ); }; From ccb2e0e525a930e76f64f63953faa5e37344668c Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Tue, 11 Jul 2023 09:32:57 +0200 Subject: [PATCH 15/44] grid: use blue color and for grid dots. --- .../layers/_frontend_only/grid/GridLayer.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 6f4f4cd1f..0d30e4ebc 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -6,13 +6,21 @@ import { Layer, Group, Line, Text } from 'react-konva'; const TEN_CENTIMETERS = 10; const ONE_METER = 100; -const RELATIVE_DOT_SIZE = 1 / 1000; +const RELATIVE_DOT_SIZE = 1 / 500; + +const RELATIVE_YARD_STICK_STROKE_WIDTH = 1 / 1000; const RELATIVE_YARD_STICK_OFFSET_X = 1 / 20; const RELATIVE_YARD_STICK_OFFSET_Y = 1 / 30; const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; +// This color should ideally be imported from tailwind.config.js +// +// However, the official guide (https://tailwindcss.com/docs/configuration#referencing-in-java-script) +// does not seem to work with our current setup. +const SEA_BLUE_500 = '#007499'; + export const GridLayer = (props: Konva.LayerConfig) => { const mapBounds = useMapStore((state) => state.untrackedState.editorBounds); @@ -57,7 +65,7 @@ const Grid = (rect: GridProps) => { lines.push( , @@ -81,7 +89,7 @@ const YardStick = (rect: GridProps) => { yardStickLengthLabel = '1' + t('meter_shorthand'); } - const strokeWidth = rect.width * RELATIVE_DOT_SIZE; + const strokeWidth = rect.width * RELATIVE_YARD_STICK_STROKE_WIDTH; const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; From 0fa29dc1f943ea2e2fad0eaf4de5102d0c038508 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Tue, 11 Jul 2023 10:01:34 +0200 Subject: [PATCH 16/44] grid: improve comments. --- .../layers/_frontend_only/grid/GridLayer.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 0d30e4ebc..8639b033e 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -6,6 +6,8 @@ import { Layer, Group, Line, Text } from 'react-konva'; const TEN_CENTIMETERS = 10; const ONE_METER = 100; +// Sizes are relative to the viewport width. +// E.g.: 1 / 500 would result in a width of 2px with a 1000px viewport. const RELATIVE_DOT_SIZE = 1 / 500; const RELATIVE_YARD_STICK_STROKE_WIDTH = 1 / 1000; @@ -15,10 +17,14 @@ const RELATIVE_YARD_STICK_OFFSET_Y = 1 / 30; const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; -// This color should ideally be imported from tailwind.config.js +// These colors should ideally be imported from tailwind.config.js // // However, the official guide (https://tailwindcss.com/docs/configuration#referencing-in-java-script) // does not seem to work with our current setup. +// +// Reason: tailwind.config.js is a commonjs module. +// Importing it with our current build setup - as suggested in the guide above - +// will result in browser errors. const SEA_BLUE_500 = '#007499'; export const GridLayer = (props: Konva.LayerConfig) => { @@ -54,11 +60,12 @@ const Grid = (rect: GridProps) => { const gridDotSize = rect.width * RELATIVE_DOT_SIZE; + // Draw the grid larger than necessary to avoid artifacts while panning the viewport. const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); - const endX = -rect.x + rect.width; - const endY = -rect.y + rect.height; + const endX = -rect.x + (rect.width * 2); + const endY = -rect.y + (rect.height * 2); const lines = []; for (let y = startY; y < endY; y += gridStep) { From fbf3e90f4702dae41f388ebe1752393a5795b7dd Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 12 Jul 2023 14:28:16 +0200 Subject: [PATCH 17/44] grid: render on map load. --- .../features/map_planning/components/BaseStage.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/map_planning/components/BaseStage.tsx b/frontend/src/features/map_planning/components/BaseStage.tsx index 295f6e885..b109cd97b 100644 --- a/frontend/src/features/map_planning/components/BaseStage.tsx +++ b/frontend/src/features/map_planning/components/BaseStage.tsx @@ -37,7 +37,6 @@ export const BaseStage = ({ selectable = true, draggable = true, }: BaseStageProps) => { - const updateMapBounds = useMapStore((store) => store.updateMapBounds); // Represents the state of the stage const [stage, setStage] = useState({ @@ -81,6 +80,18 @@ export const BaseStage = ({ const step = useMapStore((map) => map.step); const historyLength = useMapStore((map) => map.history.length); + const updateMapBounds = useMapStore((store) => store.updateMapBounds); + const mapBounds = useMapStore((store) => store.untrackedState.editorBounds); + useEffect(() => { + if (mapBounds.width !== 0 || mapBounds.height !== 0) return; + updateMapBounds({ + x: 0, + y: 0, + width: Math.floor(window.innerWidth / stage.scale), + height: Math.floor(window.innerHeight / stage.scale), + }); + }); + // Event listener responsible for allowing zooming with the ctrl key + mouse wheel const onStageWheel = (e: KonvaEventObject) => { e.evt.preventDefault(); From 442bad00b9bf4ce0ba1e5904ad9c00cdacaac316 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Wed, 12 Jul 2023 16:20:55 +0200 Subject: [PATCH 18/44] grid: add button for controlling map grid. --- .../src/features/map_planning/components/Map.tsx | 8 ++++++++ frontend/src/icons/grid.svg | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 frontend/src/icons/grid.svg diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index 0419b494c..33c5c9016 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -12,6 +12,7 @@ import IconButton from '@/components/Button/IconButton'; import { GridLayer } from '@/features/map_planning/layers/_frontend_only/grid/GridLayer'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; +import { ReactComponent as GridIcon } from '@/icons/grid.svg'; import { useTranslation } from 'react-i18next'; export type MapProps = { @@ -88,6 +89,13 @@ export const Map = ({ layers }: MapProps) => { > + redo()} + title={t('undoRedo:redo_tooltip')} + > + + } contentBottom={getToolbarContent(untrackedState.selectedLayer.type_).left} diff --git a/frontend/src/icons/grid.svg b/frontend/src/icons/grid.svg new file mode 100644 index 000000000..e75a74e74 --- /dev/null +++ b/frontend/src/icons/grid.svg @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file From 8118ef2cf0516fe552bfdd9e6119c80c26e04bb8 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 13 Jul 2023 13:01:02 +0200 Subject: [PATCH 19/44] grid: make visibility and transparency editable. --- frontend/src/config/i18n/de/grid.json | 5 +++ frontend/src/config/i18n/de/index.ts | 2 + frontend/src/config/i18n/en/grid.json | 5 +++ frontend/src/config/i18n/en/index.ts | 6 ++- .../features/map_planning/components/Map.tsx | 30 +++++++++----- .../components/toolbar/LayerList.tsx | 5 ++- .../grid/components/GridLayerLeftToolbar.tsx | 41 +++++++++++++++++++ .../map_planning/store/MapStore.test.ts | 12 ++++++ .../map_planning/store/MapStoreTypes.ts | 10 +++-- .../map_planning/store/UntrackedMapStore.ts | 32 +++++++++++++-- 10 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 frontend/src/config/i18n/de/grid.json create mode 100644 frontend/src/config/i18n/en/grid.json create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx diff --git a/frontend/src/config/i18n/de/grid.json b/frontend/src/config/i18n/de/grid.json new file mode 100644 index 000000000..e0acd94c1 --- /dev/null +++ b/frontend/src/config/i18n/de/grid.json @@ -0,0 +1,5 @@ +{ + "tooltip": "Raster-Einstellungen", + "visibility_slider": "Raster-Transparenz", + "settings_header": "Raster Einstellungen" +} \ No newline at end of file diff --git a/frontend/src/config/i18n/de/index.ts b/frontend/src/config/i18n/de/index.ts index 7e827fbbf..21cd8dab3 100644 --- a/frontend/src/config/i18n/de/index.ts +++ b/frontend/src/config/i18n/de/index.ts @@ -7,6 +7,7 @@ import contact from './contact.json'; import enums from './enums.json'; import featureDescriptions from './featureDescriptions.json'; import geomap from './geomap.json'; +import grid from './grid.json'; import imprint from './imprint.json'; import landingPage from './landingPage.json'; import layerSettings from './layerSettings.json'; @@ -33,6 +34,7 @@ const de = { enums, featureDescriptions, geomap, + grid, imprint, landingPage, pricing, diff --git a/frontend/src/config/i18n/en/grid.json b/frontend/src/config/i18n/en/grid.json new file mode 100644 index 000000000..1f09cc12b --- /dev/null +++ b/frontend/src/config/i18n/en/grid.json @@ -0,0 +1,5 @@ +{ + "tooltip": "Grid Settings", + "visibility_slider": "Grid Transparency", + "settings_header": "Grid Settings" +} \ No newline at end of file diff --git a/frontend/src/config/i18n/en/index.ts b/frontend/src/config/i18n/en/index.ts index 7ea1924ac..21cd8dab3 100644 --- a/frontend/src/config/i18n/en/index.ts +++ b/frontend/src/config/i18n/en/index.ts @@ -7,6 +7,7 @@ import contact from './contact.json'; import enums from './enums.json'; import featureDescriptions from './featureDescriptions.json'; import geomap from './geomap.json'; +import grid from './grid.json'; import imprint from './imprint.json'; import landingPage from './landingPage.json'; import layerSettings from './layerSettings.json'; @@ -24,7 +25,7 @@ import seeds from './seeds.json'; import team from './team.json'; import undoRedo from './undoRedo.json'; -const en = { +const de = { blog, baseLayer, baseLayerForm, @@ -33,6 +34,7 @@ const en = { enums, featureDescriptions, geomap, + grid, imprint, landingPage, pricing, @@ -52,4 +54,4 @@ const en = { plantEdit, }; -export default en; +export default de; diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index 33c5c9016..62db9c8c2 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -9,10 +9,13 @@ import { Layers } from './toolbar/Layers'; import { Toolbar } from './toolbar/Toolbar'; import { LayerDto, LayerType } from '@/bindings/definitions'; import IconButton from '@/components/Button/IconButton'; +import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; import { GridLayer } from '@/features/map_planning/layers/_frontend_only/grid/GridLayer'; +import { GridLayerLeftToolbar } from '@/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar'; +import { CombinedLayerType } from '@/features/map_planning/store/MapStoreTypes'; +import { ReactComponent as GridIcon } from '@/icons/grid.svg'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; import { ReactComponent as UndoIcon } from '@/icons/undo.svg'; -import { ReactComponent as GridIcon } from '@/icons/grid.svg'; import { useTranslation } from 'react-i18next'; export type MapProps = { @@ -32,11 +35,12 @@ export const Map = ({ layers }: MapProps) => { const undo = useMapStore((map) => map.undo); const redo = useMapStore((map) => map.redo); const executeAction = useMapStore((map) => map.executeAction); - const selectedLayer = useMapStore((state) => state.untrackedState.selectedLayer); + const selectLayer = useMapStore((map) => map.updateSelectedLayer); + const getSelectedLayerType = useMapStore((map) => map.getSelectedLayerType); - const { t } = useTranslation(['undoRedo']); + const { t } = useTranslation(['undoRedo', 'grid']); - const getToolbarContent = (layerType: LayerType) => { + const getToolbarContent = (layerType: CombinedLayerType) => { const content = { [LayerType.Base]: { left:
, @@ -63,6 +67,7 @@ export const Map = ({ layers }: MapProps) => { [LayerType.Todo]: { right:
, left:
}, [LayerType.Photo]: { right:
, left:
}, [LayerType.Watering]: { right:
, left:
}, + [FrontendOnlyLayerType.Grid]: { right:
, left: }, }; return content[layerType]; @@ -90,15 +95,15 @@ export const Map = ({ layers }: MapProps) => { redo()} - title={t('undoRedo:redo_tooltip')} + className="m-2 h-8 w-8 border border-neutral-500 p-1" + onClick={() => selectLayer(FrontendOnlyLayerType.Grid)} + title={t('grid:tooltip')} > } - contentBottom={getToolbarContent(untrackedState.selectedLayer.type_).left} + contentBottom={getToolbarContent(getSelectedLayerType()).left} position="left" >
@@ -114,14 +119,17 @@ export const Map = ({ layers }: MapProps) => { - +
} - contentBottom={getToolbarContent(untrackedState.selectedLayer.type_).right} + contentBottom={getToolbarContent(getSelectedLayerType()).right} position="right" minWidth={200} > diff --git a/frontend/src/features/map_planning/components/toolbar/LayerList.tsx b/frontend/src/features/map_planning/components/toolbar/LayerList.tsx index c23982ec7..9789455bc 100644 --- a/frontend/src/features/map_planning/components/toolbar/LayerList.tsx +++ b/frontend/src/features/map_planning/components/toolbar/LayerList.tsx @@ -38,6 +38,9 @@ export const LayerList = ({ const [alternativesVisible, setAlternativesVisible] = useState(false); const { t } = useTranslation(['layerSettings', 'layers']); + // If a frontend only layer is active, no other layer should be selected. + const selectedLayerId = typeof selectedLayer === 'object' ? selectedLayer.id : null; + return ( <>
@@ -54,7 +57,7 @@ export const LayerList = ({ className="h-4 w-4" type="radio" value={layer.name} - checked={selectedLayer.id === layer.id} + checked={selectedLayerId === layer.id} onChange={() => { if (setSelectedLayer) setSelectedLayer(layer); }} diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx new file mode 100644 index 000000000..69016f261 --- /dev/null +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx @@ -0,0 +1,41 @@ +import IconButton from '@/components/Button/IconButton'; +import { NamedSlider } from '@/components/Slider/NamedSlider'; +import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; +import useMapStore from '@/features/map_planning/store/MapStore'; +import { ReactComponent as EyeOffIcon } from '@/icons/eye-off.svg'; +import { ReactComponent as EyeIcon } from '@/icons/eye.svg'; +import { useTranslation } from 'react-i18next'; + +export const GridLayerLeftToolbar = () => { + const { t } = useTranslation(['grid', 'layerSettings']); + const layerVisible = useMapStore((map) => map.untrackedState.layers['grid'].visible); + const updateLayerVisible = useMapStore((map) => map.updateLayerVisible); + const setLayerOpacity = useMapStore((map) => map.updateLayerOpacity); + + return ( +
+

{t('grid:settings_header')}

+
+
+ updateLayerVisible(FrontendOnlyLayerType.Grid, !layerVisible)} + > + {layerVisible ? : } + +
+
+ { + if (setLayerOpacity) setLayerOpacity(FrontendOnlyLayerType.Grid, percentage); + }} + title={t('layerSettings:sliderTooltip')} + value={1} + > + {t(`grid:visibility_slider`)} + +
+
+
+ ); +}; diff --git a/frontend/src/features/map_planning/store/MapStore.test.ts b/frontend/src/features/map_planning/store/MapStore.test.ts index c728af46d..2105de883 100644 --- a/frontend/src/features/map_planning/store/MapStore.test.ts +++ b/frontend/src/features/map_planning/store/MapStore.test.ts @@ -308,6 +308,12 @@ describe('MapHistoryStore', () => { useMapStore.getState().updateSelectedLayer(createTestLayerObject()); const { untrackedState: newState } = useMapStore.getState(); + + if (typeof newState.selectedLayer !== 'object') { + expect(true).toEqual(false); + return; + } + expect(newState.selectedLayer.type_).toEqual(LayerType.Soil); }); @@ -331,6 +337,12 @@ describe('MapHistoryStore', () => { useMapStore.getState().undo(); const { untrackedState: newState } = useMapStore.getState(); + + if (typeof newState.selectedLayer !== 'object') { + expect(true).toEqual(false); + return; + } + expect(newState.selectedLayer.type_).toEqual(LayerType.Soil); }); }); diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index 78f4d4481..547cdef04 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -111,7 +111,8 @@ export interface UntrackedMapSlice { stageRef: React.RefObject; tooltipRef: React.RefObject; updateMapBounds: (bounds: BoundsRect) => void; - updateSelectedLayer: (selectedLayer: LayerDto) => void; + // The backend does not know about frontend only layers, hence they are not part of LayerDto. + updateSelectedLayer: (selectedLayer: LayerDto | FrontendOnlyLayerType) => void; updateLayerVisible: ( layerName: CombinedLayerType, visible: UntrackedLayerState['visible'], @@ -122,10 +123,12 @@ export interface UntrackedMapSlice { ) => void; selectPlantForPlanting: (plant: PlantsSummaryDto | null) => void; selectPlanting: (planting: PlantingDto | null) => void; + getSelectedLayerType: () => CombinedLayerType; } const LAYER_TYPES = Object.values(LayerType); -const COMBINED_LAYER_TYPES = [...Object.values(LayerType), ...Object.values(FrontendOnlyLayerType)]; +const FRONTEND_ONLY_LAYER_TYPES = Object.values(FrontendOnlyLayerType); +const COMBINED_LAYER_TYPES = [...LAYER_TYPES, ...FRONTEND_ONLY_LAYER_TYPES]; export const TRACKED_DEFAULT_STATE: TrackedMapState = { layers: LAYER_TYPES.reduce( @@ -254,7 +257,8 @@ export type TrackedMapState = { export type UntrackedMapState = { mapId: number; editorBounds: BoundsRect; - selectedLayer: LayerDto; + // The backend does not know about frontend only layers, hence they are not part of LayerDto. + selectedLayer: LayerDto | FrontendOnlyLayerType; layers: UntrackedLayers; }; diff --git a/frontend/src/features/map_planning/store/UntrackedMapStore.ts b/frontend/src/features/map_planning/store/UntrackedMapStore.ts index 6e6e5110f..b779920b4 100644 --- a/frontend/src/features/map_planning/store/UntrackedMapStore.ts +++ b/frontend/src/features/map_planning/store/UntrackedMapStore.ts @@ -4,6 +4,7 @@ import { UNTRACKED_DEFAULT_STATE, UntrackedMapSlice, } from './MapStoreTypes'; +import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; import Konva from 'konva'; import { createRef } from 'react'; import { StateCreator } from 'zustand'; @@ -30,13 +31,32 @@ export const createUntrackedMapSlice: StateCreator< // Clear the transformer's nodes. get().transformer.current?.nodes([]); + if (typeof selectedLayer === 'object' && 'is_alternative' in selectedLayer) { + // selectedLayer is a LayerDto + set((state) => ({ + ...state, + untrackedState: { + ...state.untrackedState, + selectedLayer: { + ...selectedLayer, + }, + layers: { + ...state.untrackedState.layers, + plants: { + ...state.untrackedState.layers.plants, + selectedPlanting: null, + selectedPlantForPlanting: null, + }, + }, + }, + })); + } + set((state) => ({ ...state, untrackedState: { ...state.untrackedState, - selectedLayer: { - ...selectedLayer, - }, + selectedLayer: selectedLayer, layers: { ...state.untrackedState.layers, plants: { @@ -110,4 +130,10 @@ export const createUntrackedMapSlice: StateCreator< }, })); }, + getSelectedLayerType() { + const selectedLayer = get().untrackedState.selectedLayer; + if (typeof selectedLayer === 'object' && 'type_' in selectedLayer) return selectedLayer.type_; + + return get().untrackedState.selectedLayer as FrontendOnlyLayerType; + }, }); From d92404e0d53ad25bb8ff7c9d5b3ea49568a17792 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 13 Jul 2023 15:03:22 +0200 Subject: [PATCH 20/44] chore: format. --- frontend/src/features/map_planning/components/BaseStage.tsx | 1 - .../map_planning/layers/_frontend_only/grid/GridLayer.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/map_planning/components/BaseStage.tsx b/frontend/src/features/map_planning/components/BaseStage.tsx index b109cd97b..536885bb6 100644 --- a/frontend/src/features/map_planning/components/BaseStage.tsx +++ b/frontend/src/features/map_planning/components/BaseStage.tsx @@ -37,7 +37,6 @@ export const BaseStage = ({ selectable = true, draggable = true, }: BaseStageProps) => { - // Represents the state of the stage const [stage, setStage] = useState({ scale: 1, diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index 8639b033e..be3b82535 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -64,8 +64,8 @@ const Grid = (rect: GridProps) => { const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); - const endX = -rect.x + (rect.width * 2); - const endY = -rect.y + (rect.height * 2); + const endX = -rect.x + rect.width * 2; + const endY = -rect.y + rect.height * 2; const lines = []; for (let y = startY; y < endY; y += gridStep) { From 650b8a7a300010a7c23b1ddb16d569eb2f610a95 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 14:45:58 +0200 Subject: [PATCH 21/44] merge: fix errors that were introduced by merge. --- frontend/src/features/map_planning/components/Map.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index 2b1b68641..43ee226e9 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -31,16 +31,14 @@ export type MapProps = { * In order to add a new layer you can add another layer file under the "layers" folder. * Features such as zooming and panning are handled by the BaseStage component. * You only have to make sure that every shape has the property "draggable" set to true. - * Otherwise they cannot be moved. + * Otherwise, they cannot be moved. */ export const Map = ({ layers }: MapProps) => { const untrackedState = useMapStore((map) => map.untrackedState); const undo = useMapStore((map) => map.undo); const redo = useMapStore((map) => map.redo); - const executeAction = useMapStore((map) => map.executeAction); const selectLayer = useMapStore((map) => map.updateSelectedLayer); const getSelectedLayerType = useMapStore((map) => map.getSelectedLayerType); - const selectedLayer = useMapStore((state) => state.untrackedState.selectedLayer); const timelineDate = useMapStore((state) => state.untrackedState.timelineDate); const updateTimelineDate = useMapStore((state) => state.updateTimelineDate); @@ -116,12 +114,12 @@ export const Map = ({ layers }: MapProps) => { Date: Sat, 15 Jul 2023 14:47:02 +0200 Subject: [PATCH 22/44] merge: remove duplicate identifier. --- frontend/src/features/map_planning/store/MapStoreTypes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index 274c59e8d..264f5fea7 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -321,7 +321,6 @@ export type UntrackedMapState = { from: string; to: string; }; - selectedLayer: LayerDto; layers: UntrackedLayers; }; From fc564b78a24a0e8417f3fb76e7a01f90c030e983 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 15:05:32 +0200 Subject: [PATCH 23/44] grid: refactor every component into its own file. --- frontend/src/config/i18n/en/common.json | 2 +- .../layers/_frontend_only/grid/GridLayer.tsx | 110 +----------------- .../_frontend_only/grid/groups/Grid.tsx | 39 +++++++ .../_frontend_only/grid/groups/YardStick.tsx | 50 ++++++++ .../_frontend_only/grid/util/Constants.ts | 21 ++++ .../features/map_planning/utils/Constants.ts | 3 + 6 files changed, 117 insertions(+), 108 deletions(-) create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx create mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts diff --git a/frontend/src/config/i18n/en/common.json b/frontend/src/config/i18n/en/common.json index c106d8872..ef37771b3 100644 --- a/frontend/src/config/i18n/en/common.json +++ b/frontend/src/config/i18n/en/common.json @@ -9,5 +9,5 @@ "meters": "Meters", "centimeters": "Centimeters", "meter_shorthand": "m", - "centimeter_shorthand": "cm", + "centimeter_shorthand": "cm" } diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index be3b82535..d2037b53c 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,31 +1,8 @@ import useMapStore from '@/features/map_planning/store/MapStore'; import Konva from 'konva/cmj'; -import { useTranslation } from 'react-i18next'; -import { Layer, Group, Line, Text } from 'react-konva'; - -const TEN_CENTIMETERS = 10; -const ONE_METER = 100; - -// Sizes are relative to the viewport width. -// E.g.: 1 / 500 would result in a width of 2px with a 1000px viewport. -const RELATIVE_DOT_SIZE = 1 / 500; - -const RELATIVE_YARD_STICK_STROKE_WIDTH = 1 / 1000; - -const RELATIVE_YARD_STICK_OFFSET_X = 1 / 20; -const RELATIVE_YARD_STICK_OFFSET_Y = 1 / 30; - -const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; - -// These colors should ideally be imported from tailwind.config.js -// -// However, the official guide (https://tailwindcss.com/docs/configuration#referencing-in-java-script) -// does not seem to work with our current setup. -// -// Reason: tailwind.config.js is a commonjs module. -// Importing it with our current build setup - as suggested in the guide above - -// will result in browser errors. -const SEA_BLUE_500 = '#007499'; +import { Layer } from 'react-konva'; +import {Grid} from "@/features/map_planning/layers/_frontend_only/grid/groups/Grid"; +import {YardStick} from "@/features/map_planning/layers/_frontend_only/grid/groups/YardStick"; export const GridLayer = (props: Konva.LayerConfig) => { const mapBounds = useMapStore((state) => state.untrackedState.editorBounds); @@ -42,84 +19,3 @@ export const GridLayer = (props: Konva.LayerConfig) => { ); }; - -interface GridProps { - x: number; - y: number; - width: number; - height: number; -} - -const Grid = (rect: GridProps) => { - let gridStep = TEN_CENTIMETERS; - if (rect.width > 100 * ONE_METER) { - gridStep = 10 * ONE_METER; - } else if (rect.width > 1000) { - gridStep = ONE_METER; - } - - const gridDotSize = rect.width * RELATIVE_DOT_SIZE; - - // Draw the grid larger than necessary to avoid artifacts while panning the viewport. - const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); - const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); - - const endX = -rect.x + rect.width * 2; - const endY = -rect.y + rect.height * 2; - - const lines = []; - for (let y = startY; y < endY; y += gridStep) { - lines.push( - , - ); - } - - return {lines}; -}; - -const YardStick = (rect: GridProps) => { - const { t } = useTranslation('common'); - - let yardStickLength = TEN_CENTIMETERS; - let yardStickLengthLabel = '10' + t('centimeter_shorthand'); - - if (rect.width > 100 * ONE_METER) { - yardStickLength = 10 * ONE_METER; - yardStickLengthLabel = '10' + t('meter_shorthand'); - } else if (rect.width > 1000) { - yardStickLength = ONE_METER; - yardStickLengthLabel = '1' + t('meter_shorthand'); - } - - const strokeWidth = rect.width * RELATIVE_YARD_STICK_STROKE_WIDTH; - - const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; - const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; - - const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; - - const textX = lineStartX; - const textY = lineY + rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y; - - return ( - - - - - ); -}; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx new file mode 100644 index 000000000..a5a646933 --- /dev/null +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx @@ -0,0 +1,39 @@ +import {Group, Line} from "react-konva"; +import { + RELATIVE_DOT_SIZE, + SEA_BLUE_500 +} from "@/features/map_planning/layers/_frontend_only/grid/util/Constants"; +import {BoundsRect} from "@/features/map_planning/store/MapStoreTypes"; +import {ONE_METER, TEN_CENTIMETERS} from "@/features/map_planning/utils/Constants"; + +export const Grid = (rect: BoundsRect) => { + let gridStep = TEN_CENTIMETERS; + if (rect.width > 100 * ONE_METER) { + gridStep = 10 * ONE_METER; + } else if (rect.width > 1000) { + gridStep = ONE_METER; + } + + const gridDotSize = rect.width * RELATIVE_DOT_SIZE; + + // Draw the grid larger than necessary to avoid artifacts while panning the viewport. + const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); + const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); + + const endX = -rect.x + rect.width * 2; + const endY = -rect.y + rect.height * 2; + + const lines = []; + for (let y = startY; y < endY; y += gridStep) { + lines.push( + , + ); + } + + return {lines}; +}; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx new file mode 100644 index 000000000..39d4b8ed2 --- /dev/null +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx @@ -0,0 +1,50 @@ +import {useTranslation} from "react-i18next"; +import {Group, Line, Text} from "react-konva"; +import { + RELATIVE_YARD_STICK_LABEL_OFFSET_Y, RELATIVE_YARD_STICK_OFFSET_X, RELATIVE_YARD_STICK_OFFSET_Y, + RELATIVE_YARD_STICK_STROKE_WIDTH, +} from "@/features/map_planning/layers/_frontend_only/grid/util/Constants"; +import {BoundsRect} from "@/features/map_planning/store/MapStoreTypes"; +import {ONE_METER, TEN_CENTIMETERS} from "@/features/map_planning/utils/Constants"; + +export const YardStick = (rect: BoundsRect) => { + const { t } = useTranslation('common'); + + let yardStickLength = TEN_CENTIMETERS; + let yardStickLengthLabel = '10' + t('centimeter_shorthand'); + + if (rect.width > 100 * ONE_METER) { + yardStickLength = 10 * ONE_METER; + yardStickLengthLabel = '10' + t('meter_shorthand'); + } else if (rect.width > 1000) { + yardStickLength = ONE_METER; + yardStickLengthLabel = '1' + t('meter_shorthand'); + } + + const strokeWidth = rect.width * RELATIVE_YARD_STICK_STROKE_WIDTH; + + const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; + const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; + + const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; + + const textX = lineStartX; + const textY = lineY + rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y; + + return ( + + + + + ); +}; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts new file mode 100644 index 000000000..b1308de0a --- /dev/null +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts @@ -0,0 +1,21 @@ + +// Sizes are relative to the viewport width. +// E.g.: 1 / 500 would result in a width of 2px with a 1000px viewport. +export const RELATIVE_DOT_SIZE = 1 / 500; + +export const RELATIVE_YARD_STICK_STROKE_WIDTH = 1 / 1000; + +export const RELATIVE_YARD_STICK_OFFSET_X = 1 / 20; +export const RELATIVE_YARD_STICK_OFFSET_Y = 1 / 30; + +export const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; + +// These colors should ideally be imported from tailwind.config.js +// +// However, the official guide (https://tailwindcss.com/docs/configuration#referencing-in-java-script) +// does not seem to work with our current setup. +// +// Reason: tailwind.config.js is a commonjs module. +// Importing it with our current build setup - as suggested in the guide above - +// will result in browser errors. +export const SEA_BLUE_500 = '#007499'; diff --git a/frontend/src/features/map_planning/utils/Constants.ts b/frontend/src/features/map_planning/utils/Constants.ts index 78aa6f4c3..b33f68bf5 100644 --- a/frontend/src/features/map_planning/utils/Constants.ts +++ b/frontend/src/features/map_planning/utils/Constants.ts @@ -1,3 +1,6 @@ export const MAP_PIXELS_PER_METER = 100; +export const TEN_CENTIMETERS = 10; +export const ONE_METER = 100; + // Defines the length of an invisible rect that is used to capture input. export const BASE_LAYER_MEASUREMENT_RECT_WIDTH = 999999; From e35973bf83e07d0d6b0299448b881a66afe0c263 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 15:15:36 +0200 Subject: [PATCH 24/44] grid: add dark mode support. --- .../layers/_frontend_only/grid/groups/YardStick.tsx | 7 +++++-- .../layers/_frontend_only/grid/util/Constants.ts | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx index 39d4b8ed2..e326c38a6 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx @@ -1,14 +1,17 @@ import {useTranslation} from "react-i18next"; import {Group, Line, Text} from "react-konva"; import { + GRAY_700_DARK, GRAY_700_LIGHT, RELATIVE_YARD_STICK_LABEL_OFFSET_Y, RELATIVE_YARD_STICK_OFFSET_X, RELATIVE_YARD_STICK_OFFSET_Y, RELATIVE_YARD_STICK_STROKE_WIDTH, } from "@/features/map_planning/layers/_frontend_only/grid/util/Constants"; import {BoundsRect} from "@/features/map_planning/store/MapStoreTypes"; import {ONE_METER, TEN_CENTIMETERS} from "@/features/map_planning/utils/Constants"; +import {useDarkModeStore} from "@/features/dark_mode"; export const YardStick = (rect: BoundsRect) => { const { t } = useTranslation('common'); + const { darkMode } = useDarkModeStore(); let yardStickLength = TEN_CENTIMETERS; let yardStickLengthLabel = '10' + t('centimeter_shorthand'); @@ -35,13 +38,13 @@ export const YardStick = (rect: BoundsRect) => { diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts index b1308de0a..abfa8fec1 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts @@ -19,3 +19,5 @@ export const RELATIVE_YARD_STICK_LABEL_OFFSET_Y = 1 / 120; // Importing it with our current build setup - as suggested in the guide above - // will result in browser errors. export const SEA_BLUE_500 = '#007499'; +export const GRAY_700_LIGHT = '#474747'; +export const GRAY_700_DARK = '#9e9e9e'; From a67a60fad4108d7efcaceb49a7a0d62356e0a6ad Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 15:28:45 +0200 Subject: [PATCH 25/44] chore: format. --- .../features/map_planning/components/Map.tsx | 4 +- .../layers/_frontend_only/grid/GridLayer.tsx | 4 +- .../_frontend_only/grid/groups/Grid.tsx | 60 +++++------ .../_frontend_only/grid/groups/YardStick.tsx | 101 +++++++++--------- .../_frontend_only/grid/util/Constants.ts | 1 - 5 files changed, 86 insertions(+), 84 deletions(-) diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index 43ee226e9..c1c65c86e 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -123,8 +123,8 @@ export const Map = ({ layers }: MapProps) => { >
diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx index d2037b53c..46106f5e9 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/GridLayer.tsx @@ -1,8 +1,8 @@ +import { Grid } from '@/features/map_planning/layers/_frontend_only/grid/groups/Grid'; +import { YardStick } from '@/features/map_planning/layers/_frontend_only/grid/groups/YardStick'; import useMapStore from '@/features/map_planning/store/MapStore'; import Konva from 'konva/cmj'; import { Layer } from 'react-konva'; -import {Grid} from "@/features/map_planning/layers/_frontend_only/grid/groups/Grid"; -import {YardStick} from "@/features/map_planning/layers/_frontend_only/grid/groups/YardStick"; export const GridLayer = (props: Konva.LayerConfig) => { const mapBounds = useMapStore((state) => state.untrackedState.editorBounds); diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx index a5a646933..022b39d07 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/Grid.tsx @@ -1,39 +1,39 @@ -import {Group, Line} from "react-konva"; import { - RELATIVE_DOT_SIZE, - SEA_BLUE_500 -} from "@/features/map_planning/layers/_frontend_only/grid/util/Constants"; -import {BoundsRect} from "@/features/map_planning/store/MapStoreTypes"; -import {ONE_METER, TEN_CENTIMETERS} from "@/features/map_planning/utils/Constants"; + RELATIVE_DOT_SIZE, + SEA_BLUE_500, +} from '@/features/map_planning/layers/_frontend_only/grid/util/Constants'; +import { BoundsRect } from '@/features/map_planning/store/MapStoreTypes'; +import { ONE_METER, TEN_CENTIMETERS } from '@/features/map_planning/utils/Constants'; +import { Group, Line } from 'react-konva'; export const Grid = (rect: BoundsRect) => { - let gridStep = TEN_CENTIMETERS; - if (rect.width > 100 * ONE_METER) { - gridStep = 10 * ONE_METER; - } else if (rect.width > 1000) { - gridStep = ONE_METER; - } + let gridStep = TEN_CENTIMETERS; + if (rect.width > 100 * ONE_METER) { + gridStep = 10 * ONE_METER; + } else if (rect.width > 1000) { + gridStep = ONE_METER; + } - const gridDotSize = rect.width * RELATIVE_DOT_SIZE; + const gridDotSize = rect.width * RELATIVE_DOT_SIZE; - // Draw the grid larger than necessary to avoid artifacts while panning the viewport. - const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); - const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); + // Draw the grid larger than necessary to avoid artifacts while panning the viewport. + const startX = -rect.x - rect.width - ((-rect.x - rect.width) % gridStep); + const startY = -rect.y - rect.height - ((-rect.y - rect.height) % gridStep); - const endX = -rect.x + rect.width * 2; - const endY = -rect.y + rect.height * 2; + const endX = -rect.x + rect.width * 2; + const endY = -rect.y + rect.height * 2; - const lines = []; - for (let y = startY; y < endY; y += gridStep) { - lines.push( - , - ); - } + const lines = []; + for (let y = startY; y < endY; y += gridStep) { + lines.push( + , + ); + } - return {lines}; + return {lines}; }; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx index e326c38a6..306a906c5 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/groups/YardStick.tsx @@ -1,53 +1,56 @@ -import {useTranslation} from "react-i18next"; -import {Group, Line, Text} from "react-konva"; +import { useDarkModeStore } from '@/features/dark_mode'; import { - GRAY_700_DARK, GRAY_700_LIGHT, - RELATIVE_YARD_STICK_LABEL_OFFSET_Y, RELATIVE_YARD_STICK_OFFSET_X, RELATIVE_YARD_STICK_OFFSET_Y, - RELATIVE_YARD_STICK_STROKE_WIDTH, -} from "@/features/map_planning/layers/_frontend_only/grid/util/Constants"; -import {BoundsRect} from "@/features/map_planning/store/MapStoreTypes"; -import {ONE_METER, TEN_CENTIMETERS} from "@/features/map_planning/utils/Constants"; -import {useDarkModeStore} from "@/features/dark_mode"; + GRAY_700_DARK, + GRAY_700_LIGHT, + RELATIVE_YARD_STICK_LABEL_OFFSET_Y, + RELATIVE_YARD_STICK_OFFSET_X, + RELATIVE_YARD_STICK_OFFSET_Y, + RELATIVE_YARD_STICK_STROKE_WIDTH, +} from '@/features/map_planning/layers/_frontend_only/grid/util/Constants'; +import { BoundsRect } from '@/features/map_planning/store/MapStoreTypes'; +import { ONE_METER, TEN_CENTIMETERS } from '@/features/map_planning/utils/Constants'; +import { useTranslation } from 'react-i18next'; +import { Group, Line, Text } from 'react-konva'; export const YardStick = (rect: BoundsRect) => { - const { t } = useTranslation('common'); - const { darkMode } = useDarkModeStore(); - - let yardStickLength = TEN_CENTIMETERS; - let yardStickLengthLabel = '10' + t('centimeter_shorthand'); - - if (rect.width > 100 * ONE_METER) { - yardStickLength = 10 * ONE_METER; - yardStickLengthLabel = '10' + t('meter_shorthand'); - } else if (rect.width > 1000) { - yardStickLength = ONE_METER; - yardStickLengthLabel = '1' + t('meter_shorthand'); - } - - const strokeWidth = rect.width * RELATIVE_YARD_STICK_STROKE_WIDTH; - - const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; - const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; - - const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; - - const textX = lineStartX; - const textY = lineY + rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y; - - return ( - - - - - ); + const { t } = useTranslation('common'); + const { darkMode } = useDarkModeStore(); + + let yardStickLength = TEN_CENTIMETERS; + let yardStickLengthLabel = '10' + t('centimeter_shorthand'); + + if (rect.width > 100 * ONE_METER) { + yardStickLength = 10 * ONE_METER; + yardStickLengthLabel = '10' + t('meter_shorthand'); + } else if (rect.width > 1000) { + yardStickLength = ONE_METER; + yardStickLengthLabel = '1' + t('meter_shorthand'); + } + + const strokeWidth = rect.width * RELATIVE_YARD_STICK_STROKE_WIDTH; + + const lineStartX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X; + const lineEndX = -rect.x + rect.width * RELATIVE_YARD_STICK_OFFSET_X + yardStickLength; + + const lineY = -rect.y + rect.width * RELATIVE_YARD_STICK_OFFSET_Y; + + const textX = lineStartX; + const textY = lineY + rect.width * RELATIVE_YARD_STICK_LABEL_OFFSET_Y; + + return ( + + + + + ); }; diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts index abfa8fec1..05297840f 100644 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts +++ b/frontend/src/features/map_planning/layers/_frontend_only/grid/util/Constants.ts @@ -1,4 +1,3 @@ - // Sizes are relative to the viewport width. // E.g.: 1 / 500 would result in a width of 2px with a 1000px viewport. export const RELATIVE_DOT_SIZE = 1 / 500; From 51f40c12d69b04c815dd85eeeccdc72e40115cd8 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 15:34:05 +0200 Subject: [PATCH 26/44] merge: fix scroll to zoom. --- frontend/src/features/map_planning/components/BaseStage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/map_planning/components/BaseStage.tsx b/frontend/src/features/map_planning/components/BaseStage.tsx index 72c7cfcdb..577a6cdfe 100644 --- a/frontend/src/features/map_planning/components/BaseStage.tsx +++ b/frontend/src/features/map_planning/components/BaseStage.tsx @@ -100,7 +100,7 @@ export const BaseStage = ({ if (targetStage === null) return; if (tooltipRef.current) { - setTooltipPosition(tooltipRef.current, stage); + setTooltipPosition(tooltipRef.current, targetStage); } const pointerVector = targetStage.getPointerPosition(); From e59fc3d247cfe872da61b62a223b734cc66f5e06 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Sat, 15 Jul 2023 15:41:55 +0200 Subject: [PATCH 27/44] plants layer: fix build error related to missing ids. --- .../src/features/map_planning/layers/plant/PlantsLayer.tsx | 3 ++- frontend/src/features/map_planning/store/MapStoreTypes.ts | 1 + .../src/features/map_planning/store/UntrackedMapStore.ts | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/map_planning/layers/plant/PlantsLayer.tsx b/frontend/src/features/map_planning/layers/plant/PlantsLayer.tsx index 4ac0ce53c..1676475a6 100644 --- a/frontend/src/features/map_planning/layers/plant/PlantsLayer.tsx +++ b/frontend/src/features/map_planning/layers/plant/PlantsLayer.tsx @@ -21,6 +21,7 @@ function usePlantLayerListeners(listening: boolean) { ); const selectedLayer = useMapStore((state) => state.untrackedState.selectedLayer); const timelineDate = useMapStore((state) => state.untrackedState.timelineDate); + const getSelectedLayerId = useMapStore((state) => state.getSelectedLayerId); /** * Event handler for planting plants @@ -40,7 +41,7 @@ function usePlantLayerListeners(listening: boolean) { new CreatePlantAction({ id: uuid.v4(), plantId: selectedPlant.id, - layerId: selectedLayer.id, + layerId: getSelectedLayerId() ?? -1, // consider the offset of the stage and size of the element x: Math.round(position.x), y: Math.round(position.y), diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index 264f5fea7..aea33748b 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -139,6 +139,7 @@ export interface UntrackedMapSlice { updateTimelineDate: (date: string) => void; setTimelineBounds: (from: string, to: string) => void; getSelectedLayerType: () => CombinedLayerType; + getSelectedLayerId: () => number | null; } const LAYER_TYPES = Object.values(LayerType); diff --git a/frontend/src/features/map_planning/store/UntrackedMapStore.ts b/frontend/src/features/map_planning/store/UntrackedMapStore.ts index 07eae03fe..cb2dee513 100644 --- a/frontend/src/features/map_planning/store/UntrackedMapStore.ts +++ b/frontend/src/features/map_planning/store/UntrackedMapStore.ts @@ -289,4 +289,10 @@ export const createUntrackedMapSlice: StateCreator< return get().untrackedState.selectedLayer as FrontendOnlyLayerType; }, + getSelectedLayerId() { + const selectedLayer = get().untrackedState.selectedLayer; + if (typeof selectedLayer === 'object' && 'id' in selectedLayer) return selectedLayer.id; + + return null; + }, }); From 6cc3666693b958d3126427b0420042d137377311 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 20 Jul 2023 13:50:00 +0200 Subject: [PATCH 28/44] grid: use grid button to toggle grid. --- .../features/map_planning/components/Map.tsx | 7 ++-- .../grid/components/GridLayerLeftToolbar.tsx | 41 ------------------- 2 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx diff --git a/frontend/src/features/map_planning/components/Map.tsx b/frontend/src/features/map_planning/components/Map.tsx index c1c65c86e..5ab521846 100644 --- a/frontend/src/features/map_planning/components/Map.tsx +++ b/frontend/src/features/map_planning/components/Map.tsx @@ -14,7 +14,6 @@ import { LayerDto, LayerType } from '@/bindings/definitions'; import IconButton from '@/components/Button/IconButton'; import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; import { GridLayer } from '@/features/map_planning/layers/_frontend_only/grid/GridLayer'; -import { GridLayerLeftToolbar } from '@/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar'; import { CombinedLayerType } from '@/features/map_planning/store/MapStoreTypes'; import { ReactComponent as GridIcon } from '@/icons/grid.svg'; import { ReactComponent as RedoIcon } from '@/icons/redo.svg'; @@ -37,7 +36,7 @@ export const Map = ({ layers }: MapProps) => { const untrackedState = useMapStore((map) => map.untrackedState); const undo = useMapStore((map) => map.undo); const redo = useMapStore((map) => map.redo); - const selectLayer = useMapStore((map) => map.updateSelectedLayer); + const updateLayerVisible = useMapStore((map) => map.updateLayerVisible); const getSelectedLayerType = useMapStore((map) => map.getSelectedLayerType); const timelineDate = useMapStore((state) => state.untrackedState.timelineDate); const updateTimelineDate = useMapStore((state) => state.updateTimelineDate); @@ -69,7 +68,7 @@ export const Map = ({ layers }: MapProps) => { [LayerType.Todo]: { right:
, left:
}, [LayerType.Photo]: { right:
, left:
}, [LayerType.Watering]: { right:
, left:
}, - [FrontendOnlyLayerType.Grid]: { right:
, left: }, + [FrontendOnlyLayerType.Grid]: { right:
, left:
}, }; return content[layerType]; @@ -98,7 +97,7 @@ export const Map = ({ layers }: MapProps) => { selectLayer(FrontendOnlyLayerType.Grid)} + onClick={() => updateLayerVisible(FrontendOnlyLayerType.Grid, !untrackedState.layers.grid.visible)} title={t('grid:tooltip')} > diff --git a/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx b/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx deleted file mode 100644 index 69016f261..000000000 --- a/frontend/src/features/map_planning/layers/_frontend_only/grid/components/GridLayerLeftToolbar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import IconButton from '@/components/Button/IconButton'; -import { NamedSlider } from '@/components/Slider/NamedSlider'; -import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; -import useMapStore from '@/features/map_planning/store/MapStore'; -import { ReactComponent as EyeOffIcon } from '@/icons/eye-off.svg'; -import { ReactComponent as EyeIcon } from '@/icons/eye.svg'; -import { useTranslation } from 'react-i18next'; - -export const GridLayerLeftToolbar = () => { - const { t } = useTranslation(['grid', 'layerSettings']); - const layerVisible = useMapStore((map) => map.untrackedState.layers['grid'].visible); - const updateLayerVisible = useMapStore((map) => map.updateLayerVisible); - const setLayerOpacity = useMapStore((map) => map.updateLayerOpacity); - - return ( -
-

{t('grid:settings_header')}

-
-
- updateLayerVisible(FrontendOnlyLayerType.Grid, !layerVisible)} - > - {layerVisible ? : } - -
-
- { - if (setLayerOpacity) setLayerOpacity(FrontendOnlyLayerType.Grid, percentage); - }} - title={t('layerSettings:sliderTooltip')} - value={1} - > - {t(`grid:visibility_slider`)} - -
-
-
- ); -}; From 9cee03680c92bd9131b3584eb4245b16c8951d53 Mon Sep 17 00:00:00 2001 From: Moritz Schalk Date: Thu, 20 Jul 2023 13:56:01 +0200 Subject: [PATCH 29/44] grid: add manual test protocol. --- doc/tests/manual/protocol.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/tests/manual/protocol.md b/doc/tests/manual/protocol.md index b9d150dde..17498d5e2 100644 --- a/doc/tests/manual/protocol.md +++ b/doc/tests/manual/protocol.md @@ -223,6 +223,23 @@ - Test Result: - Notes: +## TC-013 - Grid +- Description: Display a point grid on the screen. +- Preconditions: + - [ ] User must be on the map screen. +- Test Steps: + 1. Press the grid button in the left upper menu bar. + 2. Zoom all the way in. + 3. Zoom all the way out. +- Expected Result: + - [ ] The grid is displayed. + - [ ] Each press on the grid button toggles the grid of/on. + - [ ] Zooming in, grid spacing should switch from one meter to ten centimeters. + - [ ] Zooming out, grid spacing should switch ten centimeters to one meter to ten centimeters. +- Actual Result: +- Test Result: +- Notes: +