Skip to content

Commit

Permalink
fix annotations cursor and interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
j8seangel committed Aug 14, 2024
1 parent 3bf35ac commit 2b6c0d2
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 30 deletions.
13 changes: 10 additions & 3 deletions apps/fishing-map/features/map/map-interactions.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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']

Expand Down Expand Up @@ -257,6 +258,7 @@ export const useMapMouseHover = () => {

const [hoveredCoordinates, setHoveredCoordinates] = useState<number[]>()

// eslint-disable-next-line react-hooks/exhaustive-deps
const onMouseMove: DeckProps['onHover'] = useCallback(
throttle((info: PickingInfo, event: MjolnirPointerEvent) => {
setHoveredCoordinates(info.coordinate)
Expand Down Expand Up @@ -331,6 +333,7 @@ export const useMapMouseClick = () => {
}

export const useMapCursor = () => {
const annotationsCursor = useAtomValue(annotationsCursorAtom)
const areClusterTilesLoading = useMapClusterTilesLoading()
const { isMapAnnotating } = useMapAnnotation()
const { isErrorNotificationEditing } = useMapErrorNotification()
Expand All @@ -339,6 +342,9 @@ export const useMapCursor = () => {

const getCursor = useCallback(
({ isDragging }: { isDragging: boolean }) => {
if (annotationsCursor) {
return annotationsCursor
}
if (hoverFeatures?.some(isRulerLayerPoint)) {
return 'move'
}
Expand All @@ -363,11 +369,12 @@ export const useMapCursor = () => {
return 'grab'
},
[
annotationsCursor,
hoverFeatures,
rulersEditing,
isMapAnnotating,
areClusterTilesLoading,
isErrorNotificationEditing,
rulersEditing,
areClusterTilesLoading,
]
)

Expand Down
47 changes: 31 additions & 16 deletions apps/fishing-map/features/map/overlays/annotations/Annotations.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<AnnotationsCursor>('')

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<number | null>(null)
const [newCoords, setNewCoords] = useState<number[] | null>(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 =
Expand All @@ -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(),
Expand All @@ -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
}

Expand All @@ -87,15 +98,19 @@ const MapAnnotations = (): React.ReactNode | null => {
onClick={(event) => {
setMapAnnotation(annotation)
}}
onMouseEnter={handleHover}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{ color: annotation.color }}
draggable={true}
onDragStart={(event) => handleDragStart({ event, annotation })}
onDrag={handleDrag}
onDragEnd={() => handleDragEnd(annotation)}
>
{annotation.label}
<Tooltip
content={t('map.annotationsHover', 'Drag to move or click to edit annotation')}
>
<span>{annotation.label}</span>
</Tooltip>
</p>
</HtmlOverlayItem>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import {
Button,
ColorBar,
Expand All @@ -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'
Expand All @@ -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
Expand All @@ -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
}
Expand Down

0 comments on commit 2b6c0d2

Please sign in to comment.