diff --git a/apps/fishing-map/features/map/map-interactions.hooks.ts b/apps/fishing-map/features/map/map-interactions.hooks.ts index 33b6d50ada..57cef703af 100644 --- a/apps/fishing-map/features/map/map-interactions.hooks.ts +++ b/apps/fishing-map/features/map/map-interactions.hooks.ts @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react' import { useSelector } from 'react-redux' import { DeckProps, PickingInfo } from '@deck.gl/core' import type { MjolnirPointerEvent } from 'mjolnir.js' -import { atom, useAtom, useSetAtom } from 'jotai' +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { ThunkDispatch } from '@reduxjs/toolkit' import { debounce, throttle } from 'es-toolkit' import { DataviewCategory, DataviewType } from '@globalfishingwatch/api-types' @@ -44,6 +44,7 @@ import { setClickedEvent, } from './map.slice' import { useSetMapCoordinates } from './map-viewport.hooks' +import { annotationsCursorAtom } from './overlays/annotations/Annotations' export const SUBLAYER_INTERACTION_TYPES_WITH_VESSEL_INTERACTION = ['activity', 'detections'] @@ -257,6 +258,7 @@ export const useMapMouseHover = () => { const [hoveredCoordinates, setHoveredCoordinates] = useState() + // eslint-disable-next-line react-hooks/exhaustive-deps const onMouseMove: DeckProps['onHover'] = useCallback( throttle((info: PickingInfo, event: MjolnirPointerEvent) => { setHoveredCoordinates(info.coordinate) @@ -331,6 +333,7 @@ export const useMapMouseClick = () => { } export const useMapCursor = () => { + const annotationsCursor = useAtomValue(annotationsCursorAtom) const areClusterTilesLoading = useMapClusterTilesLoading() const { isMapAnnotating } = useMapAnnotation() const { isErrorNotificationEditing } = useMapErrorNotification() @@ -339,6 +342,9 @@ export const useMapCursor = () => { const getCursor = useCallback( ({ isDragging }: { isDragging: boolean }) => { + if (annotationsCursor) { + return annotationsCursor + } if (hoverFeatures?.some(isRulerLayerPoint)) { return 'move' } @@ -363,11 +369,12 @@ export const useMapCursor = () => { return 'grab' }, [ + annotationsCursor, hoverFeatures, - rulersEditing, isMapAnnotating, - areClusterTilesLoading, isErrorNotificationEditing, + rulersEditing, + areClusterTilesLoading, ] ) diff --git a/apps/fishing-map/features/map/overlays/annotations/Annotations.tsx b/apps/fishing-map/features/map/overlays/annotations/Annotations.tsx index 2ed471e41c..b5145053e9 100644 --- a/apps/fishing-map/features/map/overlays/annotations/Annotations.tsx +++ b/apps/fishing-map/features/map/overlays/annotations/Annotations.tsx @@ -1,6 +1,8 @@ import { HtmlOverlay, HtmlOverlayItem } from '@nebula.gl/overlays' import { DragEvent, useCallback, useRef, useState } from 'react' -import { useDeckMap } from 'features/map/map-context.hooks' +import { atom, useSetAtom } from 'jotai' +import { useTranslation } from 'react-i18next' +import { Tooltip } from '@globalfishingwatch/ui-components' import { useMapViewport } from 'features/map/map-viewport.hooks' import { useMapAnnotation, useMapAnnotations } from './annotations.hooks' import { MapAnnotation } from './annotations.types' @@ -9,35 +11,43 @@ import { MapAnnotation } from './annotations.types' const blankImage = new Image() blankImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=' +type AnnotationsCursor = 'pointer' | 'grab' | 'move' | '' +export const annotationsCursorAtom = atom('') + const MapAnnotations = (): React.ReactNode | null => { + const { t } = useTranslation() + const setAnnotationsCursor = useSetAtom(annotationsCursorAtom) const xOffset = 390 // sidebar width const yOffset = 116 // timebar + map attribution + font height const { upsertMapAnnotations, mapAnnotations, areMapAnnotationsVisible } = useMapAnnotations() const { setMapAnnotation } = useMapAnnotation() - const deck = useDeckMap() const selectedAnnotationRef = useRef(null) const [newCoords, setNewCoords] = useState(null) const viewport = useMapViewport() - const handleHover = useCallback(() => { - deck?.setProps({ getCursor: () => 'move' }) - }, [deck]) + + const handleMouseEnter = useCallback(() => { + setAnnotationsCursor('pointer') + }, [setAnnotationsCursor]) + const handleMouseLeave = useCallback(() => { - deck?.setProps({ getCursor: () => 'grab' }) - }, [deck]) + setAnnotationsCursor('') + }, [setAnnotationsCursor]) + const handleDragStart = useCallback( ({ event, annotation }: { event: DragEvent; annotation: MapAnnotation }) => { if (!viewport) return - deck?.setProps({ controller: { dragPan: false } }) + setAnnotationsCursor('move') event.dataTransfer.setDragImage(blankImage, 0, 0) event.dataTransfer.effectAllowed = 'none' selectedAnnotationRef.current = annotation.id }, - [deck, viewport] + [setAnnotationsCursor, viewport] ) + const handleDrag = useCallback( (event: DragEvent) => { if (!viewport) return - deck?.setProps({ getCursor: () => 'move' }) + setAnnotationsCursor('move') if (event.clientX && event.clientY) { const x = event.clientX - xOffset > 0 ? event.clientX - xOffset : 0 const y = @@ -48,12 +58,13 @@ const MapAnnotations = (): React.ReactNode | null => { setNewCoords(coords) } }, - [deck, viewport] + [setAnnotationsCursor, viewport] ) + const handleDragEnd = useCallback( (annotation: MapAnnotation) => { if (!viewport || !newCoords) return - deck?.setProps({ controller: { dragPan: true }, getCursor: () => 'grab' }) + setAnnotationsCursor('') upsertMapAnnotations({ ...annotation, id: annotation.id || Date.now(), @@ -62,10 +73,10 @@ const MapAnnotations = (): React.ReactNode | null => { }) setNewCoords(null) }, - [deck, newCoords, upsertMapAnnotations, viewport] + [newCoords, setAnnotationsCursor, upsertMapAnnotations, viewport] ) - if (!deck || !mapAnnotations || !areMapAnnotationsVisible) { + if (!mapAnnotations || !areMapAnnotationsVisible) { return null } @@ -87,7 +98,7 @@ const MapAnnotations = (): React.ReactNode | null => { onClick={(event) => { setMapAnnotation(annotation) }} - onMouseEnter={handleHover} + onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} style={{ color: annotation.color }} draggable={true} @@ -95,7 +106,11 @@ const MapAnnotations = (): React.ReactNode | null => { onDrag={handleDrag} onDragEnd={() => handleDragEnd(annotation)} > - {annotation.label} + + {annotation.label} +

))} diff --git a/apps/fishing-map/features/map/overlays/annotations/AnnotationsDialog.tsx b/apps/fishing-map/features/map/overlays/annotations/AnnotationsDialog.tsx index 018f651b9b..b0b06ab970 100644 --- a/apps/fishing-map/features/map/overlays/annotations/AnnotationsDialog.tsx +++ b/apps/fishing-map/features/map/overlays/annotations/AnnotationsDialog.tsx @@ -1,5 +1,4 @@ import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { Button, ColorBar, @@ -10,7 +9,6 @@ import { import { useEventKeyListener } from '@globalfishingwatch/react-hooks' import { DEFAUL_ANNOTATION_COLOR } from 'features/map/map.config' import { useLocationConnect } from 'routes/routes.hook' -import { selectIsGFWUser } from 'features/user/selectors/user.selectors' import PopupWrapper from 'features/map/popups/PopupWrapper' import { useMapAnnotation, useMapAnnotations } from './annotations.hooks' import styles from './Annotations.module.css' @@ -20,11 +18,8 @@ const colors = [{ id: 'white', value: DEFAUL_ANNOTATION_COLOR }, ...LineColorBar const MapAnnotationsDialog = (): React.ReactNode | null => { const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() - const gfwUser = useSelector(selectIsGFWUser) - const { mapAnnotation, resetMapAnnotation, setMapAnnotation, isMapAnnotating } = - useMapAnnotation() + const { mapAnnotation, resetMapAnnotation, setMapAnnotation } = useMapAnnotation() const { deleteMapAnnotation, upsertMapAnnotations } = useMapAnnotations() - const isDialogVisible = gfwUser && isMapAnnotating && mapAnnotation const onConfirmClick = () => { if (!mapAnnotation) { return @@ -36,17 +31,14 @@ const MapAnnotationsDialog = (): React.ReactNode | null => { resetMapAnnotation() dispatchQueryParams({ mapAnnotationsVisible: true }) } - const ref = useEventKeyListener(['Enter'], onConfirmClick) - - if (!isDialogVisible) { - return null - } const onDeleteClick = () => { deleteMapAnnotation(mapAnnotation.id) resetMapAnnotation() } + const ref = useEventKeyListener(['Enter'], onConfirmClick) + if (!mapAnnotation) { return null }