From 91b08e56feac9c15dcc747f31b98ace6e4c80c65 Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Wed, 27 Dec 2023 23:20:06 +0100 Subject: [PATCH 01/10] feat: creat new PolygonMap functional component --- src/PolygonDraw/Map.tsx | 10 +- src/PolygonDraw/PolygonDraw.tsx | 12 +- src/PolygonDraw/PolygonMap.tsx | 423 ++++++++++++++++++++++++++++ src/PolygonDraw/usePolygonEditor.ts | 16 +- 4 files changed, 447 insertions(+), 14 deletions(-) create mode 100644 src/PolygonDraw/PolygonMap.tsx diff --git a/src/PolygonDraw/Map.tsx b/src/PolygonDraw/Map.tsx index 42bb129..1d54284 100644 --- a/src/PolygonDraw/Map.tsx +++ b/src/PolygonDraw/Map.tsx @@ -35,7 +35,8 @@ interface MapSnapshot { export interface Props { /** * activePolygonIndex is the index of the polygon that is currently available for editing - */ + */ + activePolygon: Coordinate[] activePolygonIndex: number; highlightedPolygonIndex?: number; polygonCoordinates: Coordinate[][]; @@ -366,8 +367,7 @@ export class BaseMap extends React.Component { }; render() { - const activePolygon = this.props.polygonCoordinates[this.props.activePolygonIndex]; - const activePolygonIsClosed = isPolygonClosed(activePolygon); + const activePolygonIsClosed = this.props.isPolygonClosed; const { editable, selection, initialZoom, initialCenter } = this.props; const { newPointPosition, isPenToolActive } = this.state; @@ -423,7 +423,7 @@ export class BaseMap extends React.Component { {editable && ( { {this.state.showExportPolygonModal && ( - + )} diff --git a/src/PolygonDraw/PolygonDraw.tsx b/src/PolygonDraw/PolygonDraw.tsx index dda6427..d5da6ee 100644 --- a/src/PolygonDraw/PolygonDraw.tsx +++ b/src/PolygonDraw/PolygonDraw.tsx @@ -6,6 +6,7 @@ import { createLeafletLatLngTupleFromCoordinate, ensurePolygonList } from '../he import { MAP } from '../constants'; import Map from './Map'; import UndoRedoProvider, { usePolygonEditor } from './usePolygonEditor'; +import { PolygonMap } from './PolygonMap'; export type Props = { boundary?: Coordinate[]; @@ -35,6 +36,7 @@ function PolygonEditor({ onMouseLeave, }: Props): React.ReactElement { const { + activePolygon, polygons, selection, addPoint, @@ -56,7 +58,9 @@ function PolygonEditor({ } = usePolygonEditor(onChange, polygon, onClick); return ( - ({ export function PolygonDraw(props: Props): React.ReactElement { return ( diff --git a/src/PolygonDraw/PolygonMap.tsx b/src/PolygonDraw/PolygonMap.tsx new file mode 100644 index 0000000..494bfc0 --- /dev/null +++ b/src/PolygonDraw/PolygonMap.tsx @@ -0,0 +1,423 @@ +import { latLngBounds, LatLngBounds, LatLngTuple, LeafletMouseEvent } from 'leaflet'; +import flatten from 'lodash.flatten'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useMap } from 'react-leaflet'; + +import { MAP } from '../constants'; +import { + createCoordinateFromLeafletLatLng, + createLeafletLatLngBoundsFromCoordinates, + createLeafletLatLngFromCoordinate, + isCoordinateInPolygon, +} from '../helpers'; +import { Container, Map } from '../leaflet/Map'; +import { Coordinate, RectangleSelection } from '../types'; +import { BoundaryPolygon } from './BoundaryPolygon'; +import { ActivePolygon } from './map/ActivePolygon'; +import { InactivePolygon } from './map/InactivePolygon'; +import { Polyline } from './map/Polyline'; +import { PolygonPane } from './map/PolygonPane'; +import { SelectionRectangle } from './map/SelectionRectangle'; +import { TileLayer } from '../leaflet/TileLayer'; +import MapInner from './MapInner'; +import { ActionBar } from '../ActionBar/ActionBar'; +import { Modal } from '../common/components/Modal'; +import { ExportPolygonForm } from '../conversion/ExportPolygonForm'; +import { ImportPolygonForm } from '../conversion/ImportPolygonForm'; + +export interface PolygonMapProps { + activePolygon: Coordinate[]; + activePolygonIndex: number; + highlightedPolygonIndex?: number; + polygonCoordinates: Coordinate[][]; + boundaryPolygonCoordinates: Coordinate[]; + selection: Set; + editable: boolean; + initialCenter: LatLngTuple; + initialZoom: number; + isPolygonClosed: boolean; + onClick?: (index: number) => void; + onMouseEnter?: (index: number) => void; + onMouseLeave?: (index: number) => void; + addPoint: (coord: Coordinate) => void; + addPointToEdge: (coordinate: Coordinate, index: number) => void; + deselectAllPoints: () => void; + removePointFromSelection: (index: number) => void; + addPointsToSelection: (indices: number[]) => void; + selectPoints: (indices: number[]) => void; + moveSelectedPoints: (newPosition: Coordinate) => void; + deletePolygonPoints: () => void; + selectAllPoints: () => void; + setPolygon: (polygon: Coordinate[]) => void; + onUndo: () => void; + onRedo: () => void; + isRedoPossible: boolean; + isUndoPossible: boolean; +} + +type MapType = ReturnType; + +export const PolygonMap: React.FC = ({ + activePolygon, + activePolygonIndex, + addPoint, + addPointsToSelection, + addPointToEdge, + boundaryPolygonCoordinates, + deletePolygonPoints, + deselectAllPoints, + editable, + highlightedPolygonIndex, + initialCenter, + initialZoom, + isPolygonClosed, + isRedoPossible, + isUndoPossible, + moveSelectedPoints, + onClick, + onMouseEnter, + onMouseLeave, + onRedo, + onUndo, + removePointFromSelection, + polygonCoordinates, + selectAllPoints, + selection, + selectPoints, + setPolygon, +}: PolygonMapProps) => { + const map = useRef(); + + const [isMovedPointInBoundary, setIsMovedPointInBoundary] = useState(true); + const [isShiftPressed, setIsShiftPressed] = useState(false); + const [rectangleSelection, setRectangleSelection] = useState(null); + const [isPenToolActive, setIsPenToolActive] = useState(polygonCoordinates.length === 0); + const [newPointPosition, setNewPointPosition] = useState(null); + const [showExportPolygonModal, setShowExportPolygonModal] = useState(false); + const [showImportPolygonModal, setShowImportPolygonModal] = useState(false); + + const reframe = useCallback(() => { + if (polygonCoordinates[activePolygonIndex].length > 1) { + reframeOnPolygon(polygonCoordinates); + } else if (boundaryPolygonCoordinates.length > 0 && boundaryPolygonCoordinates !== MAP.WORLD_COORDINATES) { + reframeOnPolygon(boundaryPolygonCoordinates); + } else if (map.current) { + map.current.setView(initialCenter, initialZoom); + } + }, [polygonCoordinates, activePolygonIndex, initialCenter, initialZoom, boundaryPolygonCoordinates]); + + const reframeOnPolygon = (polygonCoordinates: Coordinate[] | Coordinate[][]) => { + if (map.current && !!polygonCoordinates.length) { + const bounds = createLeafletLatLngBoundsFromCoordinates(flatten(polygonCoordinates)); + + map.current.fitBounds(bounds); + } + }; + + const toggleVectorMode = useCallback(() => { + if (!editable) { + return; + } + setIsPenToolActive((prevState) => !prevState); + setNewPointPosition(null); + }, [editable]); + + const handleOnFocusClicked = () => { + if (activePolygon) { + reframeOnPolygon(activePolygon); + } else { + reframe(); + } + }; + + /////////////////////////////////////////////////////////////////////////// + // Export / Import methods // + /////////////////////////////////////////////////////////////////////////// + + const handleExportPolygon = (serialized: string) => navigator.clipboard.writeText(serialized); + const handleExportPolygonActionClicked = () => setShowExportPolygonModal(true); + const handleExportPolygonModalClosed = () => setShowExportPolygonModal(false); + const handleImportPolygonActionClicked = () => setShowImportPolygonModal(true); + const handleImportPolygonModalClosed = () => setShowImportPolygonModal(false); + const handleImportPolygon = (coordinates: Coordinate[]) => { + setPolygon(coordinates); + reframeOnPolygon(coordinates); + }; + + /////////////////////////////////////////////////////////////////////////// + // Map Events methods // + /////////////////////////////////////////////////////////////////////////// + + const handleMapClick = (event: LeafletMouseEvent) => { + const coordinate = createCoordinateFromLeafletLatLng(event.latlng); + if (isPenToolActive && !isPolygonClosed && isCoordinateInPolygon(coordinate, boundaryPolygonCoordinates)) { + addPoint(coordinate); + } else if (!isShiftPressed) { + deselectAllPoints(); + } + }; + + const handleMouseDownOnMap = (event: LeafletMouseEvent) => { + const coordinate = createCoordinateFromLeafletLatLng(event.latlng); + + if (isShiftPressed) { + setRectangleSelection({ + startPosition: coordinate, + endPosition: coordinate, + startTime: new Date().getTime(), + }); + } + }; + + const handleMouseUpOnMap = () => { + if (rectangleSelection) { + setRectangleSelection(null); + } + }; + + const handleMouseMoveOnMap = (event: LeafletMouseEvent) => { + const mouseCoordinate = createCoordinateFromLeafletLatLng(event.latlng); + if (rectangleSelection && new Date().getTime() - rectangleSelection?.startTime >= 100) { + const start = rectangleSelection.startPosition; + if (start) { + const bounds: LatLngBounds = latLngBounds(createLeafletLatLngFromCoordinate(start), event.latlng); + + if (activePolygon) { + const pointsInsideBounds: number[] = []; + activePolygon.forEach((point, index) => { + if (bounds.contains(createLeafletLatLngFromCoordinate(point))) { + pointsInsideBounds.push(index); + } + }); + selectPoints(pointsInsideBounds); + } + } + setRectangleSelection({ + ...rectangleSelection, + endPosition: mouseCoordinate, + }); + } else { + const newPointPosition = + isPenToolActive && + !isPolygonClosed && + isCoordinateInPolygon(mouseCoordinate, boundaryPolygonCoordinates) + ? mouseCoordinate + : null; + + setNewPointPosition(newPointPosition); + } + }; + + const handleMouseOutOfMap = () => { + setRectangleSelection(null); + setNewPointPosition(null); + }; + + /////////////////////////////////////////////////////////////////////////// + // Keyboard handling methods // + /////////////////////////////////////////////////////////////////////////// + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + e.preventDefault(); + switch (e.key) { + case 'Escape': + deselectAllPoints(); + break; + case 'Backspace': + deletePolygonPoints(); + break; + case 'Shift': + setIsShiftPressed(true); + break; + case 'p': + toggleVectorMode(); + break; + case 'd': + if (editable) { + deselectAllPoints(); + } + break; + case 'a': + if (editable) { + selectAllPoints(); + } + break; + case 'f': + reframe(); + break; + case 'z': + if (e.metaKey && e.shiftKey && isRedoPossible) { + onRedo(); + } else if (e.metaKey && isUndoPossible) { + onUndo(); + } + break; + } + }, + [ + deletePolygonPoints, + deselectAllPoints, + editable, + isRedoPossible, + isUndoPossible, + onRedo, + reframe, + selectAllPoints, + toggleVectorMode, + onUndo, + ] + ); + + const handleKeyUp = useCallback((e: KeyboardEvent) => { + switch (e.key) { + case 'Shift': + setIsShiftPressed(false); + break; + } + }, []); + + useEffect(() => { + reframe(); + toggleVectorMode(); + + const container = map?.current?.getContainer(); + + if (container) { + container.addEventListener('keydown', handleKeyDown, false); + container.addEventListener('keyup', handleKeyUp); + } + + return () => { + if (container) { + container.removeEventListener('keydown', handleKeyDown, false); + container.removeEventListener('keyup', handleKeyUp); + } + }; + }, [map, handleKeyDown, handleKeyUp, reframe, toggleVectorMode]); + + const setMap = useCallback( + (ref: MapType | null) => { + if (ref) { + map.current = ref; + + reframe(); + toggleVectorMode(); + + const container = ref?.getContainer(); + + if (container) { + container?.addEventListener('keydown', handleKeyDown, false); + container?.addEventListener('keyup', handleKeyUp); + } + } + }, + [handleKeyDown, handleKeyUp, reframe, toggleVectorMode] + ); + + return ( + + + + + {isPolygonClosed ? ( + + ) : ( + + )} + + {polygonCoordinates.map((positions, index) => { + return index !== activePolygonIndex ? ( + + ) : null; + })} + + {editable && ( + setIsMovedPointInBoundary(check)} + /> + )} + + {rectangleSelection && } + + + + + + + {showExportPolygonModal && ( + + + + )} + + {showImportPolygonModal && ( + + + + )} + + ); +}; diff --git a/src/PolygonDraw/usePolygonEditor.ts b/src/PolygonDraw/usePolygonEditor.ts index 4d9e954..4b8018f 100644 --- a/src/PolygonDraw/usePolygonEditor.ts +++ b/src/PolygonDraw/usePolygonEditor.ts @@ -1,13 +1,14 @@ -import { useCallback, useMemo } from 'react'; -import { createUndoRedo } from 'react-undo-redo'; +import { useMemo } from 'react' +import { createUndoRedo } from 'react-undo-redo' -import { isPolygonClosed, isPolygonList } from '../helpers'; -import { ActionWithPayload, Coordinate } from '../types'; -import { Action, DESELECT_ALL_POINTS, MOVE_SELECTED_POINTS, SELECT_ALL_POINTS, SET_ACTIVE_INDEX, actions } from './actions'; -import { polygonEditReducer } from './reducer'; -import { isValidPolygon } from './validators'; +import { isPolygonClosed, isPolygonList } from '../helpers' +import { Coordinate } from '../types' +import { Action, DESELECT_ALL_POINTS, MOVE_SELECTED_POINTS, SELECT_ALL_POINTS, SET_ACTIVE_INDEX, actions } from './actions' +import { polygonEditReducer } from './reducer' +import { isValidPolygon } from './validators' type PolygonEditor = { + activePolygon: Coordinate[]; selection: Set; polygons: Coordinate[][]; isPolygonClosed: boolean; @@ -103,6 +104,7 @@ export const usePolygonEditor = ( }; return { + activePolygon, addPoint, addPointsToSelection, addPointToEdge, From 68be1b90e8aef4073d87ad5c01bbe571e5afc011 Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Wed, 27 Dec 2023 23:23:36 +0100 Subject: [PATCH 02/10] fix: change type Polygon map and remove ts ignore --- src/PolygonDraw/PolygonDraw.tsx | 14 +++---- src/PolygonDraw/PolygonMap.test.tsx | 65 +++++++++++++++++++++++++++++ src/PolygonDraw/PolygonMap.tsx | 4 +- 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/PolygonDraw/PolygonMap.test.tsx diff --git a/src/PolygonDraw/PolygonDraw.tsx b/src/PolygonDraw/PolygonDraw.tsx index d5da6ee..36e8629 100644 --- a/src/PolygonDraw/PolygonDraw.tsx +++ b/src/PolygonDraw/PolygonDraw.tsx @@ -1,12 +1,11 @@ -import React from 'react'; +import React from 'react' -import { Coordinate } from 'types'; -import { createLeafletLatLngTupleFromCoordinate, ensurePolygonList } from '../helpers'; +import { Coordinate } from 'types' +import { createLeafletLatLngTupleFromCoordinate, ensurePolygonList } from '../helpers' -import { MAP } from '../constants'; -import Map from './Map'; -import UndoRedoProvider, { usePolygonEditor } from './usePolygonEditor'; -import { PolygonMap } from './PolygonMap'; +import { MAP } from '../constants' +import { PolygonMap } from './PolygonMap' +import UndoRedoProvider, { usePolygonEditor } from './usePolygonEditor' export type Props = { boundary?: Coordinate[]; @@ -59,7 +58,6 @@ function PolygonEditor({ return ( ({ + __esModule: true, + MapContainer: React.forwardRef(() =>
), + Pane: React.forwardRef(() =>
), + useMap: jest.fn, +})); + +describe('Map component', () => { + let initialProps: PolygonMapProps; + + beforeEach(() => { + initialProps = { + activePolygon: MOCK_POLYGON, + activePolygonIndex: 0, + polygonCoordinates: [MOCK_POLYGON], + boundaryPolygonCoordinates: MOCK_POLYGON, + selection: new Set(), + editable: true, + initialCenter: MAP.DEFAULT_CENTER, + initialZoom: MAP.DEFAULT_ZOOM, + isPolygonClosed: true, + addPoint: jest.fn(), + addPointToEdge: jest.fn(), + addPointsToSelection: jest.fn(), + deselectAllPoints: jest.fn(), + removePointFromSelection: jest.fn(), + selectPoints: jest.fn(), + selectAllPoints: jest.fn(), + moveSelectedPoints: jest.fn(), + deletePolygonPoints: jest.fn(), + setPolygon: jest.fn(), + onUndo: jest.fn(), + onRedo: jest.fn(), + isRedoPossible: false, + isUndoPossible: false + }; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('WHEN polygon is NOT disabled', () => { + it('should NOT enable pen tool', () => { + render(); + + expect(screen.queryByText('Pen')).not.toBeInTheDocument(); + }); + }); + + describe('WHEN polygon is disabled', () => { + it('should enable pen tool', () => { + render(); + const editButton = screen.getByText('Pen'); + expect(editButton).toBeInTheDocument(); + }); + }); +}); diff --git a/src/PolygonDraw/PolygonMap.tsx b/src/PolygonDraw/PolygonMap.tsx index 494bfc0..97f3d75 100644 --- a/src/PolygonDraw/PolygonMap.tsx +++ b/src/PolygonDraw/PolygonMap.tsx @@ -57,7 +57,7 @@ export interface PolygonMapProps { type MapType = ReturnType; -export const PolygonMap: React.FC = ({ +export const PolygonMap = ({ activePolygon, activePolygonIndex, addPoint, @@ -85,7 +85,7 @@ export const PolygonMap: React.FC = ({ selection, selectPoints, setPolygon, -}: PolygonMapProps) => { +}: PolygonMapProps): React.ReactElement => { const map = useRef(); const [isMovedPointInBoundary, setIsMovedPointInBoundary] = useState(true); From 54db065d00da9d72a6dfd8fd234004c205685468 Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Wed, 27 Dec 2023 23:24:51 +0100 Subject: [PATCH 03/10] refactor: remove map class component --- src/PolygonDraw/Map.test.tsx | 64 ----- src/PolygonDraw/Map.tsx | 487 ----------------------------------- 2 files changed, 551 deletions(-) delete mode 100644 src/PolygonDraw/Map.test.tsx delete mode 100644 src/PolygonDraw/Map.tsx diff --git a/src/PolygonDraw/Map.test.tsx b/src/PolygonDraw/Map.test.tsx deleted file mode 100644 index e54b2a8..0000000 --- a/src/PolygonDraw/Map.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; - -import { MAP } from '../constants'; -import { Props, BaseMap } from './Map'; -import { MOCK_POLYGON } from '../mockPolygon'; - -jest.mock('react-leaflet', () => ({ - __esModule: true, - MapContainer: React.forwardRef(() =>
), - Pane: React.forwardRef(() =>
), - useMap: jest.fn, -})); - -describe('Map component', () => { - let initialProps: Props; - - beforeEach(() => { - initialProps = { - activePolygonIndex: 0, - polygonCoordinates: [MOCK_POLYGON], - boundaryPolygonCoordinates: MOCK_POLYGON, - selection: new Set(), - editable: true, - initialCenter: MAP.DEFAULT_CENTER, - initialZoom: MAP.DEFAULT_ZOOM, - isPolygonClosed: true, - addPoint: jest.fn(), - addPointToEdge: jest.fn(), - addPointsToSelection: jest.fn(), - deselectAllPoints: jest.fn(), - removePointFromSelection: jest.fn(), - selectPoints: jest.fn(), - selectAllPoints: jest.fn(), - moveSelectedPoints: jest.fn(), - deletePolygonPoints: jest.fn(), - setPolygon: jest.fn(), - onUndo: jest.fn(), - onRedo: jest.fn(), - isRedoPossible: false, - isUndoPossible: false - }; - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe('WHEN polygon is NOT disabled', () => { - it('should NOT enable pen tool', () => { - render(); - - expect(screen.queryByText('Pen')).not.toBeInTheDocument(); - }); - }); - - describe('WHEN polygon is disabled', () => { - it('should enable pen tool', () => { - render(); - const editButton = screen.getByText('Pen'); - expect(editButton).toBeInTheDocument(); - }); - }); -}); diff --git a/src/PolygonDraw/Map.tsx b/src/PolygonDraw/Map.tsx deleted file mode 100644 index 1d54284..0000000 --- a/src/PolygonDraw/Map.tsx +++ /dev/null @@ -1,487 +0,0 @@ -import React, { memo } from 'react'; -import { latLngBounds, LatLngBounds, LatLngTuple, LeafletMouseEvent } from 'leaflet'; -import { useMap } from 'react-leaflet'; -import flatten from 'lodash.flatten'; - -import { Coordinate, RectangleSelection } from '../types'; - -import { - createCoordinateFromLeafletLatLng, - createLeafletLatLngBoundsFromCoordinates, - createLeafletLatLngFromCoordinate, - isCoordinateInPolygon, - isPolygonClosed, -} from '../helpers'; -import { Modal } from '../common/components/Modal'; -import { ExportPolygonForm } from '../conversion/ExportPolygonForm'; -import { ImportPolygonForm } from '../conversion/ImportPolygonForm'; -import { TileLayer } from '../leaflet/TileLayer'; -import { MAP } from '../constants'; -import { Map, Container } from '../leaflet/Map'; -import { ActionBar } from '../ActionBar/ActionBar'; -import { BoundaryPolygon } from './BoundaryPolygon'; -import MapInner from './MapInner'; -import { SelectionRectangle } from './map/SelectionRectangle'; -import { Polyline } from './map/Polyline'; -import { ActivePolygon } from './map/ActivePolygon'; -import { InactivePolygon } from './map/InactivePolygon'; -import { PolygonPane } from './map/PolygonPane'; - -interface MapSnapshot { - reframe: boolean; - size: string; -} - -export interface Props { - /** - * activePolygonIndex is the index of the polygon that is currently available for editing - */ - activePolygon: Coordinate[] - activePolygonIndex: number; - highlightedPolygonIndex?: number; - polygonCoordinates: Coordinate[][]; - boundaryPolygonCoordinates: Coordinate[]; - selection: Set; - editable: boolean; - initialCenter: LatLngTuple; - initialZoom: number; - isPolygonClosed: boolean; - onClick?: (index: number) => void; - onMouseEnter?: (index: number) => void; - onMouseLeave?: (index: number) => void; - addPoint: (coord: Coordinate) => void; - addPointToEdge: (coordinate: Coordinate, index: number) => void; - deselectAllPoints: () => void; - removePointFromSelection: (index: number) => void; - addPointsToSelection: (indices: number[]) => void; - selectPoints: (indices: number[]) => void; - moveSelectedPoints: (newPosition: Coordinate) => void; - deletePolygonPoints: () => void; - selectAllPoints: () => void; - setPolygon: (polygon: Coordinate[]) => void; - onUndo: () => void; - onRedo: () => void; - isRedoPossible: boolean; - isUndoPossible: boolean; -} - -type MapType = ReturnType; - -export interface State { - isMovedPointInBoundary: boolean; - isShiftPressed: boolean; - rectangleSelection: RectangleSelection | null; - isPenToolActive: boolean; - newPointPosition: Coordinate | null; - showExportPolygonModal: boolean; - showImportPolygonModal: boolean; -} - -export class BaseMap extends React.Component { - private map: MapType | null = null; - - state: State = { - isMovedPointInBoundary: true, - isShiftPressed: false, - rectangleSelection: null, - isPenToolActive: false, - newPointPosition: null, - showExportPolygonModal: false, - showImportPolygonModal: false, - }; - - static getDerivedStateFromProps(props: Props, state: State): State { - return { - ...state, - isPenToolActive: props.polygonCoordinates.length === 0 ? true : state.isPenToolActive, - }; - } - - componentDidMount() { - this.reframe(); - this.toggleVectorMode(); - - const container = this.map?.getContainer(); - - if (container) { - container.addEventListener('keydown', this.handleKeyDown, false); - container.addEventListener('keyup', this.handleKeyUp); - } - } - - componentWillUnmount() { - const container = this.map?.getContainer(); - - if (container) { - container.removeEventListener('keydown', this.handleKeyDown, false); - container.removeEventListener('keyup', this.handleKeyUp); - } - } - - getSnapshotBeforeUpdate(prevProps: Props, prevState: State): MapSnapshot { - const reframe = - // Reframe when the polygon loads for the first time - (prevProps.polygonCoordinates[prevProps.activePolygonIndex].length === 0 && - this.props.polygonCoordinates[this.props.activePolygonIndex].length > 1) || - // Reframe when the boundary polygon loads for the first time - prevProps.boundaryPolygonCoordinates !== this.props.boundaryPolygonCoordinates; - - const size = this.getSize(this.map); - - return { reframe, size }; - } - - componentDidUpdate(prevProps: Readonly, prevState: Readonly, { reframe, size }: MapSnapshot): void { - if (reframe) { - this.reframe(); - } - - if (this.map && this.getSize(this.map) !== size) { - this.map.invalidateSize(); - } - } - - setMap = (map: MapType) => { - if (map) { - this.map = map; - - this.reframe(); - this.toggleVectorMode(); - - const container = map?.getContainer(); - - if (container) { - container?.addEventListener('keydown', this.handleKeyDown, false); - container?.addEventListener('keyup', this.handleKeyUp); - } - } - }; - - reframe = () => { - const { polygonCoordinates, boundaryPolygonCoordinates, initialCenter, initialZoom } = this.props; - - if (polygonCoordinates[this.props.activePolygonIndex].length > 1) { - this.reframeOnPolygon(polygonCoordinates); - } else if (boundaryPolygonCoordinates.length > 0 && boundaryPolygonCoordinates !== MAP.WORLD_COORDINATES) { - this.reframeOnPolygon(boundaryPolygonCoordinates); - } else if (this.map) { - this.map.setView(initialCenter, initialZoom); - } - }; - - reframeOnPolygon = (polygonCoordinates: Coordinate[] | Coordinate[][]) => { - if (this.map && polygonCoordinates.length > 0) { - const bounds = createLeafletLatLngBoundsFromCoordinates(flatten(polygonCoordinates)); - - this.map.fitBounds(bounds); - } - }; - - toggleVectorMode = () => { - if (!this.props.editable) { - return; - } - this.setState({ - isPenToolActive: !this.state.isPenToolActive, - newPointPosition: null, - }); - }; - - getSize = (map: MapType | null): string => { - const container = map?.getContainer(); - return container ? `${container.clientHeight}x${container.clientWidth}` : ''; - }; - - handleOnFocusClicked = () => { - const activePolygon = this.props.polygonCoordinates[this.props.activePolygonIndex]; - if (activePolygon) { - this.reframeOnPolygon(activePolygon); - } else { - this.reframe(); - } - }; - - updateIsMovedPointInBoundary = (check: boolean) => { - this.setState({ isMovedPointInBoundary: check }); - }; - - /////////////////////////////////////////////////////////////////////////// - // Export / Import methods // - /////////////////////////////////////////////////////////////////////////// - - handleExportPolygon = (serialized: string) => { - navigator.clipboard.writeText(serialized); - }; - - handleExportPolygonActionClicked = () => { - this.setState({ showExportPolygonModal: true }); - }; - - handleExportPolygonModalClosed = () => { - this.setState({ showExportPolygonModal: false }); - }; - - handleImportPolygon = (coordinates: Coordinate[]) => { - this.props.setPolygon(coordinates); - this.reframeOnPolygon(coordinates); - }; - - handleImportPolygonActionClicked = () => { - this.setState({ showImportPolygonModal: true }); - }; - - handleImportPolygonModalClosed = () => { - this.setState({ showImportPolygonModal: false }); - }; - - /////////////////////////////////////////////////////////////////////////// - // Map Events methods // - /////////////////////////////////////////////////////////////////////////// - - handleMapClick = (event: LeafletMouseEvent) => { - const coordinate = createCoordinateFromLeafletLatLng(event.latlng); - if ( - this.state.isPenToolActive && - !this.props.isPolygonClosed && - isCoordinateInPolygon(coordinate, this.props.boundaryPolygonCoordinates) - ) { - this.props.addPoint(coordinate); - } else if (!this.state.isShiftPressed) { - this.props.deselectAllPoints(); - } - }; - - handleMouseDownOnMap = (event: LeafletMouseEvent) => { - const coordinate = createCoordinateFromLeafletLatLng(event.latlng); - - if (this.state.isShiftPressed) { - this.setState({ - rectangleSelection: { - startPosition: coordinate, - endPosition: coordinate, - startTime: new Date().getTime(), - }, - }); - } - }; - - handleMouseUpOnMap = () => { - if (this.state.rectangleSelection) { - this.setState({ - rectangleSelection: null, - }); - } - }; - - handleMouseMoveOnMap = (event: LeafletMouseEvent) => { - const mouseCoordinate = createCoordinateFromLeafletLatLng(event.latlng); - if (this.state.rectangleSelection && new Date().getTime() - this.state.rectangleSelection?.startTime >= 100) { - const start = this.state.rectangleSelection.startPosition; - if (start) { - const bounds: LatLngBounds = latLngBounds(createLeafletLatLngFromCoordinate(start), event.latlng); - - const activePolygon: Coordinate[] | undefined = - this.props.polygonCoordinates[this.props.activePolygonIndex]; - if (activePolygon) { - const pointsInsideBounds: number[] = []; - activePolygon.forEach((point, index) => { - if (bounds.contains(createLeafletLatLngFromCoordinate(point))) { - pointsInsideBounds.push(index); - } - }); - this.props.selectPoints(pointsInsideBounds); - } - } - this.setState({ - rectangleSelection: { - ...this.state.rectangleSelection, - endPosition: mouseCoordinate, - }, - }); - } else { - const newPointPosition = - this.state.isPenToolActive && - !this.props.isPolygonClosed && - isCoordinateInPolygon(mouseCoordinate, this.props.boundaryPolygonCoordinates) - ? mouseCoordinate - : null; - - this.setState({ newPointPosition }); - } - }; - - handleMouseOutOfMap = () => - this.setState({ - newPointPosition: null, - rectangleSelection: null, - }); - - /////////////////////////////////////////////////////////////////////////// - // Keyboard handling methods // - /////////////////////////////////////////////////////////////////////////// - - handleKeyDown = (e: KeyboardEvent) => { - e.preventDefault(); - switch (e.key) { - case 'Escape': - this.props.deselectAllPoints(); - break; - case 'Backspace': - this.props.deletePolygonPoints(); - break; - case 'Shift': - this.setState({ isShiftPressed: true }); - break; - case 'p': - this.toggleVectorMode(); - break; - case 'd': - if (this.props.editable) { - this.props.deselectAllPoints(); - } - break; - case 'a': - if (this.props.editable) { - this.props.selectAllPoints(); - } - break; - case 'f': - this.reframe(); - break; - case 'z': - if (e.metaKey && e.shiftKey && this.props.isRedoPossible) { - this.props.onRedo(); - } else if (e.metaKey && this.props.isUndoPossible) { - this.props.onUndo(); - } - break; - } - }; - - handleKeyUp = (e: KeyboardEvent) => { - switch (e.key) { - case 'Shift': - this.setState({ isShiftPressed: false }); - break; - } - }; - - render() { - const activePolygonIsClosed = this.props.isPolygonClosed; - const { editable, selection, initialZoom, initialCenter } = this.props; - const { newPointPosition, isPenToolActive } = this.state; - - return ( - - - - - {this.props.isPolygonClosed ? ( - - ) : ( - - )} - - {this.props.polygonCoordinates.map((positions, index) => { - return index !== this.props.activePolygonIndex ? ( - - ) : null; - })} - - {editable && ( - - )} - - {this.state.rectangleSelection && ( - - )} - - - - - - - {this.state.showExportPolygonModal && ( - - - - )} - - {this.state.showImportPolygonModal && ( - - - - )} - - ); - } -} - -export default memo(BaseMap); From 81102a8a688f88edfcfadb4cb67b3980f29aa655 Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Thu, 28 Dec 2023 11:52:02 +0100 Subject: [PATCH 04/10] refactor: move mouse handling functions to MapInner --- src/PolygonDraw/MapInner.tsx | 114 ++++++++++++++++++--- src/PolygonDraw/PolygonMap.tsx | 178 +++++++++++---------------------- 2 files changed, 157 insertions(+), 135 deletions(-) diff --git a/src/PolygonDraw/MapInner.tsx b/src/PolygonDraw/MapInner.tsx index ed8a9f1..595bf8b 100644 --- a/src/PolygonDraw/MapInner.tsx +++ b/src/PolygonDraw/MapInner.tsx @@ -1,24 +1,110 @@ -import { LeafletMouseEvent } from 'leaflet'; +import { latLngBounds, LatLngBounds, LeafletMouseEvent } from 'leaflet'; import { useMapEvents } from 'react-leaflet'; +import { + createCoordinateFromLeafletLatLng, + isPolygonClosed, + isCoordinateInPolygon, + createLeafletLatLngFromCoordinate, +} from '../helpers'; +import { Coordinate, RectangleSelection } from '../types'; type Props = { - onClick: (event: LeafletMouseEvent) => void; - onMouseDown: (event: LeafletMouseEvent) => void; - onMouseUp: (event: LeafletMouseEvent) => void; - onMouseMove: (event: LeafletMouseEvent) => void; - onMouseOut: (event: LeafletMouseEvent) => void; + activePolygon: Coordinate[]; + addPoint: (coord: Coordinate) => void; + boundaryPolygonCoordinates: Coordinate[]; + deselectAllPoints: () => void; + isPenToolActive: boolean; + isShiftPressed: boolean; + rectangleSelection: RectangleSelection | null; + selectPoints: (indices: number[]) => void; + setNewPointPosition: React.Dispatch>; + setRectangleSelection: React.Dispatch>; }; -const MapInner = ({ onClick, onMouseMove, onMouseOut }: Props) => { +export const MapInner = ({ + activePolygon, + addPoint, + boundaryPolygonCoordinates, + deselectAllPoints, + isPenToolActive, + isShiftPressed, + rectangleSelection, + selectPoints, + setNewPointPosition, + setRectangleSelection, +}: Props) => { + const handleMapClick = (event: LeafletMouseEvent) => { + const coordinate = createCoordinateFromLeafletLatLng(event.latlng); + if (isPenToolActive && !isPolygonClosed && isCoordinateInPolygon(coordinate, boundaryPolygonCoordinates)) { + addPoint(coordinate); + } else if (!isShiftPressed) { + deselectAllPoints(); + } + }; + + const handleMouseDownOnMap = (event: LeafletMouseEvent) => { + const coordinate = createCoordinateFromLeafletLatLng(event.latlng); + + if (isShiftPressed) { + setRectangleSelection({ + startPosition: coordinate, + endPosition: coordinate, + startTime: new Date().getTime(), + }); + } + }; + + const handleMouseUpOnMap = () => { + if (rectangleSelection) { + setRectangleSelection(null); + } + }; + + const handleMouseMoveOnMap = (event: LeafletMouseEvent) => { + const mouseCoordinate = createCoordinateFromLeafletLatLng(event.latlng); + if (rectangleSelection && new Date().getTime() - rectangleSelection?.startTime >= 100) { + const start = rectangleSelection.startPosition; + if (start) { + const bounds: LatLngBounds = latLngBounds(createLeafletLatLngFromCoordinate(start), event.latlng); + + if (activePolygon) { + const pointsInsideBounds: number[] = []; + activePolygon.forEach((point, index) => { + if (bounds.contains(createLeafletLatLngFromCoordinate(point))) { + pointsInsideBounds.push(index); + } + }); + selectPoints(pointsInsideBounds); + } + } + setRectangleSelection({ + ...rectangleSelection, + endPosition: mouseCoordinate, + }); + } else { + const newPointPosition = + isPenToolActive && + !isPolygonClosed && + isCoordinateInPolygon(mouseCoordinate, boundaryPolygonCoordinates) + ? mouseCoordinate + : null; + + setNewPointPosition(newPointPosition); + } + }; + + const handleMouseOutOfMap = () => { + setRectangleSelection(null); + setNewPointPosition(null); + }; + useMapEvents({ - click: onClick, - mousedown: onMouseOut, - mouseup: onMouseOut, - mousemove: onMouseMove, - mouseout: onMouseOut, + click: handleMapClick, + mousedown: handleMouseDownOnMap, + mouseup: handleMouseUpOnMap, + mousemove: handleMouseMoveOnMap, + mouseout: handleMouseOutOfMap, }); return null; }; - -export default MapInner; diff --git a/src/PolygonDraw/PolygonMap.tsx b/src/PolygonDraw/PolygonMap.tsx index 97f3d75..5590019 100644 --- a/src/PolygonDraw/PolygonMap.tsx +++ b/src/PolygonDraw/PolygonMap.tsx @@ -1,58 +1,53 @@ -import { latLngBounds, LatLngBounds, LatLngTuple, LeafletMouseEvent } from 'leaflet'; +import { LatLngTuple } from 'leaflet'; import flatten from 'lodash.flatten'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useMap } from 'react-leaflet'; +import { ActionBar } from '../ActionBar/ActionBar'; +import { Modal } from '../common/components/Modal'; import { MAP } from '../constants'; -import { - createCoordinateFromLeafletLatLng, - createLeafletLatLngBoundsFromCoordinates, - createLeafletLatLngFromCoordinate, - isCoordinateInPolygon, -} from '../helpers'; +import { ExportPolygonForm } from '../conversion/ExportPolygonForm'; +import { ImportPolygonForm } from '../conversion/ImportPolygonForm'; +import { createLeafletLatLngBoundsFromCoordinates } from '../helpers'; import { Container, Map } from '../leaflet/Map'; +import { TileLayer } from '../leaflet/TileLayer'; import { Coordinate, RectangleSelection } from '../types'; import { BoundaryPolygon } from './BoundaryPolygon'; import { ActivePolygon } from './map/ActivePolygon'; import { InactivePolygon } from './map/InactivePolygon'; -import { Polyline } from './map/Polyline'; import { PolygonPane } from './map/PolygonPane'; +import { Polyline } from './map/Polyline'; import { SelectionRectangle } from './map/SelectionRectangle'; -import { TileLayer } from '../leaflet/TileLayer'; -import MapInner from './MapInner'; -import { ActionBar } from '../ActionBar/ActionBar'; -import { Modal } from '../common/components/Modal'; -import { ExportPolygonForm } from '../conversion/ExportPolygonForm'; -import { ImportPolygonForm } from '../conversion/ImportPolygonForm'; +import { MapInner } from './MapInner'; export interface PolygonMapProps { activePolygon: Coordinate[]; activePolygonIndex: number; - highlightedPolygonIndex?: number; - polygonCoordinates: Coordinate[][]; + addPoint: (coord: Coordinate) => void; + addPointsToSelection: (indices: number[]) => void; + addPointToEdge: (coordinate: Coordinate, index: number) => void; boundaryPolygonCoordinates: Coordinate[]; - selection: Set; + deletePolygonPoints: () => void; + deselectAllPoints: () => void; editable: boolean; + highlightedPolygonIndex?: number; initialCenter: LatLngTuple; initialZoom: number; isPolygonClosed: boolean; + isRedoPossible: boolean; + isUndoPossible: boolean; + moveSelectedPoints: (newPosition: Coordinate) => void; onClick?: (index: number) => void; onMouseEnter?: (index: number) => void; onMouseLeave?: (index: number) => void; - addPoint: (coord: Coordinate) => void; - addPointToEdge: (coordinate: Coordinate, index: number) => void; - deselectAllPoints: () => void; + onRedo: () => void; + onUndo: () => void; + polygonCoordinates: Coordinate[][]; removePointFromSelection: (index: number) => void; - addPointsToSelection: (indices: number[]) => void; - selectPoints: (indices: number[]) => void; - moveSelectedPoints: (newPosition: Coordinate) => void; - deletePolygonPoints: () => void; selectAllPoints: () => void; + selection: Set; + selectPoints: (indices: number[]) => void; setPolygon: (polygon: Coordinate[]) => void; - onUndo: () => void; - onRedo: () => void; - isRedoPossible: boolean; - isUndoPossible: boolean; } type MapType = ReturnType; @@ -135,84 +130,20 @@ export const PolygonMap = ({ /////////////////////////////////////////////////////////////////////////// const handleExportPolygon = (serialized: string) => navigator.clipboard.writeText(serialized); + const handleExportPolygonActionClicked = () => setShowExportPolygonModal(true); + const handleExportPolygonModalClosed = () => setShowExportPolygonModal(false); + const handleImportPolygonActionClicked = () => setShowImportPolygonModal(true); + const handleImportPolygonModalClosed = () => setShowImportPolygonModal(false); + const handleImportPolygon = (coordinates: Coordinate[]) => { setPolygon(coordinates); reframeOnPolygon(coordinates); }; - /////////////////////////////////////////////////////////////////////////// - // Map Events methods // - /////////////////////////////////////////////////////////////////////////// - - const handleMapClick = (event: LeafletMouseEvent) => { - const coordinate = createCoordinateFromLeafletLatLng(event.latlng); - if (isPenToolActive && !isPolygonClosed && isCoordinateInPolygon(coordinate, boundaryPolygonCoordinates)) { - addPoint(coordinate); - } else if (!isShiftPressed) { - deselectAllPoints(); - } - }; - - const handleMouseDownOnMap = (event: LeafletMouseEvent) => { - const coordinate = createCoordinateFromLeafletLatLng(event.latlng); - - if (isShiftPressed) { - setRectangleSelection({ - startPosition: coordinate, - endPosition: coordinate, - startTime: new Date().getTime(), - }); - } - }; - - const handleMouseUpOnMap = () => { - if (rectangleSelection) { - setRectangleSelection(null); - } - }; - - const handleMouseMoveOnMap = (event: LeafletMouseEvent) => { - const mouseCoordinate = createCoordinateFromLeafletLatLng(event.latlng); - if (rectangleSelection && new Date().getTime() - rectangleSelection?.startTime >= 100) { - const start = rectangleSelection.startPosition; - if (start) { - const bounds: LatLngBounds = latLngBounds(createLeafletLatLngFromCoordinate(start), event.latlng); - - if (activePolygon) { - const pointsInsideBounds: number[] = []; - activePolygon.forEach((point, index) => { - if (bounds.contains(createLeafletLatLngFromCoordinate(point))) { - pointsInsideBounds.push(index); - } - }); - selectPoints(pointsInsideBounds); - } - } - setRectangleSelection({ - ...rectangleSelection, - endPosition: mouseCoordinate, - }); - } else { - const newPointPosition = - isPenToolActive && - !isPolygonClosed && - isCoordinateInPolygon(mouseCoordinate, boundaryPolygonCoordinates) - ? mouseCoordinate - : null; - - setNewPointPosition(newPointPosition); - } - }; - - const handleMouseOutOfMap = () => { - setRectangleSelection(null); - setNewPointPosition(null); - }; - /////////////////////////////////////////////////////////////////////////// // Keyboard handling methods // /////////////////////////////////////////////////////////////////////////// @@ -277,25 +208,6 @@ export const PolygonMap = ({ } }, []); - useEffect(() => { - reframe(); - toggleVectorMode(); - - const container = map?.current?.getContainer(); - - if (container) { - container.addEventListener('keydown', handleKeyDown, false); - container.addEventListener('keyup', handleKeyUp); - } - - return () => { - if (container) { - container.removeEventListener('keydown', handleKeyDown, false); - container.removeEventListener('keyup', handleKeyUp); - } - }; - }, [map, handleKeyDown, handleKeyUp, reframe, toggleVectorMode]); - const setMap = useCallback( (ref: MapType | null) => { if (ref) { @@ -315,6 +227,25 @@ export const PolygonMap = ({ [handleKeyDown, handleKeyUp, reframe, toggleVectorMode] ); + useEffect(() => { + reframe(); + toggleVectorMode(); + + const container = map?.current?.getContainer(); + + if (container) { + container.addEventListener('keydown', handleKeyDown, false); + container.addEventListener('keyup', handleKeyUp); + } + + return () => { + if (container) { + container.removeEventListener('keydown', handleKeyDown, false); + container.removeEventListener('keyup', handleKeyUp); + } + }; + }, [map, handleKeyDown, handleKeyUp, reframe, toggleVectorMode]); + return ( Date: Thu, 28 Dec 2023 12:37:18 +0100 Subject: [PATCH 05/10] refactor: use activeIndex from present instead of initial to track index and remove from filtered redo-undo actions --- src/PolygonDraw/PolygonDraw.tsx | 4 ++-- src/PolygonDraw/usePolygonEditor.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PolygonDraw/PolygonDraw.tsx b/src/PolygonDraw/PolygonDraw.tsx index 36e8629..f5a6f8b 100644 --- a/src/PolygonDraw/PolygonDraw.tsx +++ b/src/PolygonDraw/PolygonDraw.tsx @@ -23,7 +23,6 @@ export type Props = { function PolygonEditor({ polygon, - activeIndex = 0, highlightedIndex, boundary, initialCenter, @@ -36,6 +35,7 @@ function PolygonEditor({ }: Props): React.ReactElement { const { activePolygon, + activePolygonIndex, polygons, selection, addPoint, @@ -64,7 +64,7 @@ function PolygonEditor({ initialCenter={initialCenter ? createLeafletLatLngTupleFromCoordinate(initialCenter) : MAP.DEFAULT_CENTER} initialZoom={initialZoom || MAP.DEFAULT_ZOOM} boundaryPolygonCoordinates={boundary || MAP.WORLD_COORDINATES} - activePolygonIndex={activeIndex} + activePolygonIndex={activePolygonIndex} highlightedPolygonIndex={highlightedIndex} polygonCoordinates={polygons} setPolygon={setPolygon} diff --git a/src/PolygonDraw/usePolygonEditor.ts b/src/PolygonDraw/usePolygonEditor.ts index 4b8018f..ec2f480 100644 --- a/src/PolygonDraw/usePolygonEditor.ts +++ b/src/PolygonDraw/usePolygonEditor.ts @@ -9,6 +9,7 @@ import { isValidPolygon } from './validators' type PolygonEditor = { activePolygon: Coordinate[]; + activePolygonIndex: number, selection: Set; polygons: Coordinate[][]; isPolygonClosed: boolean; @@ -29,7 +30,7 @@ type PolygonEditor = { isRedoPossible: boolean; }; -const unundoableActions = [MOVE_SELECTED_POINTS, SET_ACTIVE_INDEX, DESELECT_ALL_POINTS, SELECT_ALL_POINTS]; +const unundoableActions = [MOVE_SELECTED_POINTS, DESELECT_ALL_POINTS, SELECT_ALL_POINTS]; const { UndoRedoProvider, usePresent, useUndoRedo } = createUndoRedo(polygonEditReducer, { track: (action) => !unundoableActions.includes(action.type), @@ -43,6 +44,7 @@ export const usePolygonEditor = ( ): PolygonEditor => { const [present, dispatch] = usePresent(); const [undo, redo] = useUndoRedo(); + console.log('🚀 ~ file: usePolygonEditor.ts:45 ~ present:', present) const dispatchWithCallback = (dispatchAction: Action) => { dispatch(dispatchAction) @@ -105,6 +107,7 @@ export const usePolygonEditor = ( return { activePolygon, + activePolygonIndex: present.activeIndex, addPoint, addPointsToSelection, addPointToEdge, From 731df211ebf43ca32bd24f9e6fcf00d065b55c5a Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Thu, 28 Dec 2023 12:41:47 +0100 Subject: [PATCH 06/10] fix: remove unused import --- src/PolygonDraw/usePolygonEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PolygonDraw/usePolygonEditor.ts b/src/PolygonDraw/usePolygonEditor.ts index ec2f480..84b0cfe 100644 --- a/src/PolygonDraw/usePolygonEditor.ts +++ b/src/PolygonDraw/usePolygonEditor.ts @@ -3,7 +3,7 @@ import { createUndoRedo } from 'react-undo-redo' import { isPolygonClosed, isPolygonList } from '../helpers' import { Coordinate } from '../types' -import { Action, DESELECT_ALL_POINTS, MOVE_SELECTED_POINTS, SELECT_ALL_POINTS, SET_ACTIVE_INDEX, actions } from './actions' +import { Action, DESELECT_ALL_POINTS, MOVE_SELECTED_POINTS, SELECT_ALL_POINTS, actions } from './actions' import { polygonEditReducer } from './reducer' import { isValidPolygon } from './validators' From 5e01a7ef48b01ca26df93ab51db4213f335db41f Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Thu, 28 Dec 2023 12:56:34 +0100 Subject: [PATCH 07/10] fix: remove console.log --- src/PolygonDraw/usePolygonEditor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PolygonDraw/usePolygonEditor.ts b/src/PolygonDraw/usePolygonEditor.ts index 84b0cfe..504a9b0 100644 --- a/src/PolygonDraw/usePolygonEditor.ts +++ b/src/PolygonDraw/usePolygonEditor.ts @@ -44,7 +44,6 @@ export const usePolygonEditor = ( ): PolygonEditor => { const [present, dispatch] = usePresent(); const [undo, redo] = useUndoRedo(); - console.log('🚀 ~ file: usePolygonEditor.ts:45 ~ present:', present) const dispatchWithCallback = (dispatchAction: Action) => { dispatch(dispatchAction) From c01e57191e3f898da049e98de3c636a168bde7d0 Mon Sep 17 00:00:00 2001 From: Nikolai Lopin Date: Thu, 4 Jan 2024 17:53:44 +0100 Subject: [PATCH 08/10] rename unreadable variable name --- src/PolygonDraw/usePolygonEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PolygonDraw/usePolygonEditor.ts b/src/PolygonDraw/usePolygonEditor.ts index 504a9b0..f7ae54b 100644 --- a/src/PolygonDraw/usePolygonEditor.ts +++ b/src/PolygonDraw/usePolygonEditor.ts @@ -30,10 +30,10 @@ type PolygonEditor = { isRedoPossible: boolean; }; -const unundoableActions = [MOVE_SELECTED_POINTS, DESELECT_ALL_POINTS, SELECT_ALL_POINTS]; +const notTrackedActions = [MOVE_SELECTED_POINTS, DESELECT_ALL_POINTS, SELECT_ALL_POINTS]; const { UndoRedoProvider, usePresent, useUndoRedo } = createUndoRedo(polygonEditReducer, { - track: (action) => !unundoableActions.includes(action.type), + track: (action) => !notTrackedActions.includes(action.type), }); export default UndoRedoProvider; From ede4f4520f0060b90253db5346e49e85d447d10c Mon Sep 17 00:00:00 2001 From: Nikolai Lopin Date: Thu, 4 Jan 2024 18:06:25 +0100 Subject: [PATCH 09/10] fix redo keyboard shortcut --- src/PolygonDraw/PolygonMap.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PolygonDraw/PolygonMap.tsx b/src/PolygonDraw/PolygonMap.tsx index 5590019..5f59120 100644 --- a/src/PolygonDraw/PolygonMap.tsx +++ b/src/PolygonDraw/PolygonMap.tsx @@ -178,10 +178,13 @@ export const PolygonMap = ({ reframe(); break; case 'z': + if (e.metaKey && isUndoPossible) { + onUndo(); + } + break; + case 'Z': if (e.metaKey && e.shiftKey && isRedoPossible) { onRedo(); - } else if (e.metaKey && isUndoPossible) { - onUndo(); } break; } From 1c43b46bfb2bf1eed0b47d7ea27521f3797bd70e Mon Sep 17 00:00:00 2001 From: dornelasnelson Date: Fri, 5 Jan 2024 15:22:16 +0100 Subject: [PATCH 10/10] refactor: force keyboard actions to lowercase --- src/PolygonDraw/PolygonMap.tsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/PolygonDraw/PolygonMap.tsx b/src/PolygonDraw/PolygonMap.tsx index 5f59120..24cde11 100644 --- a/src/PolygonDraw/PolygonMap.tsx +++ b/src/PolygonDraw/PolygonMap.tsx @@ -151,15 +151,17 @@ export const PolygonMap = ({ const handleKeyDown = useCallback( (e: KeyboardEvent) => { e.preventDefault(); - switch (e.key) { - case 'Escape': + switch (e.key.toLocaleLowerCase()) { + case 'escape': deselectAllPoints(); break; - case 'Backspace': + case 'backspace': deletePolygonPoints(); break; - case 'Shift': - setIsShiftPressed(true); + case 'shift': + if (!e.metaKey) { + setIsShiftPressed(true); + } break; case 'p': toggleVectorMode(); @@ -178,13 +180,12 @@ export const PolygonMap = ({ reframe(); break; case 'z': - if (e.metaKey && isUndoPossible) { - onUndo(); - } - break; - case 'Z': - if (e.metaKey && e.shiftKey && isRedoPossible) { - onRedo(); + if (e.metaKey) { + if (e.shiftKey && isRedoPossible) { + onRedo(); + } else if (isUndoPossible) { + onUndo(); + } } break; }