/* globals google */ import { memo, useState, useEffect, useContext, PureComponent, type ContextType, } from 'react' import invariant from 'invariant' import { unregisterEvents, applyUpdatersToPropsAndRegisterEvents, } from '../../utils/helper.js' import MapContext from '../../map-context.js' const eventMap = { onCircleComplete: 'circlecomplete', onMarkerComplete: 'markercomplete', onOverlayComplete: 'overlaycomplete', onPolygonComplete: 'polygoncomplete', onPolylineComplete: 'polylinecomplete', onRectangleComplete: 'rectanglecomplete', } const updaterMap = { drawingMode( instance: google.maps.drawing.DrawingManager, drawingMode: google.maps.drawing.OverlayType | null ): void { instance.setDrawingMode(drawingMode) }, options( instance: google.maps.drawing.DrawingManager, options: google.maps.drawing.DrawingManagerOptions ): void { instance.setOptions(options) }, } type DrawingManagerState = { drawingManager: google.maps.drawing.DrawingManager | null } export type DrawingManagerProps = { options?: google.maps.drawing.DrawingManagerOptions | undefined /** Changes the DrawingManager's drawing mode, which defines the type of overlay to be added on the map. Accepted values are 'marker', 'polygon', 'polyline', 'rectangle', 'circle', or null. A drawing mode of null means that the user can interact with the map as normal, and clicks do not draw anything. */ drawingMode?: google.maps.drawing.OverlayType | null | undefined /** This event is fired when the user has finished drawing a circle. */ onCircleComplete?: ((circle: google.maps.Circle) => void) | undefined /** This event is fired when the user has finished drawing a marker. */ onMarkerComplete?: ((marker: google.maps.Marker) => void) | undefined /** This event is fired when the user has finished drawing an overlay of any type. */ onOverlayComplete?: | ((e: google.maps.drawing.OverlayCompleteEvent) => void) | undefined /** This event is fired when the user has finished drawing a polygon. */ onPolygonComplete?: ((polygon: google.maps.Polygon) => void) | undefined /** This event is fired when the user has finished drawing a polyline. */ onPolylineComplete?: ((polyline: google.maps.Polyline) => void) | undefined /** This event is fired when the user has finished drawing a rectangle. */ onRectangleComplete?: ((rectangle: google.maps.Rectangle) => void) | undefined /** This callback is called when the drawingManager instance has loaded. It is called with the drawingManager instance. */ onLoad?: | ((drawingManager: google.maps.drawing.DrawingManager) => void) | undefined /** This callback is called when the component unmounts. It is called with the drawingManager instance. */ onUnmount?: | ((drawingManager: google.maps.drawing.DrawingManager) => void) | undefined } function DrawingManagerFunctional({ options, drawingMode, onCircleComplete, onMarkerComplete, onOverlayComplete, onPolygonComplete, onPolylineComplete, onRectangleComplete, onLoad, onUnmount, }: DrawingManagerProps): null { const map = useContext(MapContext) const [instance, setInstance] = useState(null) const [circlecompleteListener, setCircleCompleteListener] = useState(null) const [markercompleteListener, setMarkerCompleteListener] = useState(null) const [overlaycompleteListener, setOverlayCompleteListener] = useState(null) const [polygoncompleteListener, setPolygonCompleteListener] = useState(null) const [polylinecompleteListener, setPolylineCompleteListener] = useState(null) const [rectanglecompleteListener, setRectangleCompleteListener] = useState(null) // Order does matter useEffect(() => { if (instance !== null) { instance.setMap(map) } }, [map]) useEffect(() => { if (options && instance !== null) { instance.setOptions(options) } }, [instance, options]) useEffect(() => { if (instance !== null) { instance.setDrawingMode(drawingMode ?? null) } }, [instance, drawingMode]) useEffect(() => { if (instance && onCircleComplete) { if (circlecompleteListener !== null) { google.maps.event.removeListener(circlecompleteListener) } setCircleCompleteListener( google.maps.event.addListener( instance, 'circlecomplete', onCircleComplete ) ) } }, [instance, onCircleComplete]) useEffect(() => { if (instance && onMarkerComplete) { if (markercompleteListener !== null) { google.maps.event.removeListener(markercompleteListener) } setMarkerCompleteListener( google.maps.event.addListener( instance, 'markercomplete', onMarkerComplete ) ) } }, [instance, onMarkerComplete]) useEffect(() => { if (instance && onOverlayComplete) { if (overlaycompleteListener !== null) { google.maps.event.removeListener(overlaycompleteListener) } setOverlayCompleteListener( google.maps.event.addListener( instance, 'overlaycomplete', onOverlayComplete ) ) } }, [instance, onOverlayComplete]) useEffect(() => { if (instance && onPolygonComplete) { if (polygoncompleteListener !== null) { google.maps.event.removeListener(polygoncompleteListener) } setPolygonCompleteListener( google.maps.event.addListener( instance, 'polygoncomplete', onPolygonComplete ) ) } }, [instance, onPolygonComplete]) useEffect(() => { if (instance && onPolylineComplete) { if (polylinecompleteListener !== null) { google.maps.event.removeListener(polylinecompleteListener) } setPolylineCompleteListener( google.maps.event.addListener( instance, 'polylinecomplete', onPolylineComplete ) ) } }, [instance, onPolylineComplete]) useEffect(() => { if (instance && onRectangleComplete) { if (rectanglecompleteListener !== null) { google.maps.event.removeListener(rectanglecompleteListener) } setRectangleCompleteListener( google.maps.event.addListener( instance, 'rectanglecomplete', onRectangleComplete ) ) } }, [instance, onRectangleComplete]) useEffect(() => { invariant( !!google.maps.drawing, `Did you include prop libraries={['drawing']} in the URL? %s`, google.maps.drawing ) const drawingManager = new google.maps.drawing.DrawingManager({ ...options, map, }) if (drawingMode) { drawingManager.setDrawingMode(drawingMode) } if (onCircleComplete) { setCircleCompleteListener( google.maps.event.addListener( drawingManager, 'circlecomplete', onCircleComplete ) ) } if (onMarkerComplete) { setMarkerCompleteListener( google.maps.event.addListener( drawingManager, 'markercomplete', onMarkerComplete ) ) } if (onOverlayComplete) { setOverlayCompleteListener( google.maps.event.addListener( drawingManager, 'overlaycomplete', onOverlayComplete ) ) } if (onPolygonComplete) { setPolygonCompleteListener( google.maps.event.addListener( drawingManager, 'polygoncomplete', onPolygonComplete ) ) } if (onPolylineComplete) { setPolylineCompleteListener( google.maps.event.addListener( drawingManager, 'polylinecomplete', onPolylineComplete ) ) } if (onRectangleComplete) { setRectangleCompleteListener( google.maps.event.addListener( drawingManager, 'rectanglecomplete', onRectangleComplete ) ) } setInstance(drawingManager) if (onLoad) { onLoad(drawingManager) } return () => { if (instance !== null) { if (circlecompleteListener) { google.maps.event.removeListener(circlecompleteListener) } if (markercompleteListener) { google.maps.event.removeListener(markercompleteListener) } if (overlaycompleteListener) { google.maps.event.removeListener(overlaycompleteListener) } if (polygoncompleteListener) { google.maps.event.removeListener(polygoncompleteListener) } if (polylinecompleteListener) { google.maps.event.removeListener(polylinecompleteListener) } if (rectanglecompleteListener) { google.maps.event.removeListener(rectanglecompleteListener) } if (onUnmount) { onUnmount(instance) } instance.setMap(null) } } }, []) return null } export const DrawingManagerF = memo(DrawingManagerFunctional) export class DrawingManager extends PureComponent< DrawingManagerProps, DrawingManagerState > { static override contextType = MapContext declare context: ContextType registeredEvents: google.maps.MapsEventListener[] = [] override state: DrawingManagerState = { drawingManager: null, } constructor(props: DrawingManagerProps) { super(props) invariant( !!google.maps.drawing, `Did you include prop libraries={['drawing']} in the URL? %s`, google.maps.drawing ) } setDrawingManagerCallback = (): void => { if (this.state.drawingManager !== null && this.props.onLoad) { this.props.onLoad(this.state.drawingManager) } } override componentDidMount(): void { const drawingManager = new google.maps.drawing.DrawingManager({ ...this.props.options, map: this.context, }) this.registeredEvents = applyUpdatersToPropsAndRegisterEvents({ updaterMap, eventMap, prevProps: {}, nextProps: this.props, instance: drawingManager, }) this.setState(function setDrawingManager() { return { drawingManager, } }, this.setDrawingManagerCallback) } override componentDidUpdate(prevProps: DrawingManagerProps): void { if (this.state.drawingManager !== null) { unregisterEvents(this.registeredEvents) this.registeredEvents = applyUpdatersToPropsAndRegisterEvents({ updaterMap, eventMap, prevProps, nextProps: this.props, instance: this.state.drawingManager, }) } } override componentWillUnmount(): void { if (this.state.drawingManager !== null) { if (this.props.onUnmount) { this.props.onUnmount(this.state.drawingManager) } unregisterEvents(this.registeredEvents) this.state.drawingManager.setMap(null) } } override render(): null { return null } } export default DrawingManager