improved locate control

This commit is contained in:
Anton Tranelis 2025-08-11 13:36:56 +02:00
parent 4e6fb57042
commit c39a04761a

View File

@ -1,9 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-ts-expect-error */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { control } from 'leaflet' import { control } from 'leaflet'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg' import SVG from 'react-inlinesvg'
@ -14,20 +8,32 @@ import TargetSVG from '#assets/target.svg'
// eslint-disable-next-line import/no-unassigned-import // eslint-disable-next-line import/no-unassigned-import
import 'leaflet.locatecontrol' import 'leaflet.locatecontrol'
// Converts leaflet.locatecontrol to a React Component // Type definitions for leaflet.locatecontrol
export const LocateControl = () => { declare module 'leaflet' {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace control {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function locate(options?: object): any
}
}
/**
* React wrapper for leaflet.locatecontrol that provides user geolocation functionality
* @category Map Controls
*/
export const LocateControl = (): JSX.Element => {
const map = useMap() const map = useMap()
// prevent react18 from calling useEffect twice // Prevent React 18 StrictMode from calling useEffect twice
const init = useRef(false) const init = useRef(false)
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
const [lc, setLc] = useState<any>(null) const [lc, setLc] = useState<any>(null)
const [active, setActive] = useState<boolean>(false) const [active, setActive] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
if (!init.current) { if (!init.current) {
// @ts-ignore
setLc(control.locate().addTo(map)) setLc(control.locate().addTo(map))
init.current = true init.current = true
} }
@ -39,34 +45,51 @@ export const LocateControl = () => {
setLoading(false) setLoading(false)
setActive(true) setActive(true)
}, },
locationerror: () => {
setLoading(false)
setActive(false)
},
}) })
const handleLocateClick = (): void => {
if (!lc) return
if (active) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
lc.stop()
setActive(false)
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
lc.start()
setLoading(true)
}
}
return ( return (
<> <div className='tw:card tw:flex-none tw:h-12 tw:w-12 tw:bg-base-100 tw:shadow-xl tw:items-center tw:justify-center tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:ml-2'>
<div className='tw:card tw:flex-none tw:h-12 tw:w-12 tw:bg-base-100 tw:shadow-xl tw:items-center tw:justify-center tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:ml-2'> <div
<div className='tw:card-body tw:card tw:p-2 tw:h-10 tw:w-10'
className='tw:card-body tw:card tw:p-2 tw:h-10 tw:w-10 ' onClick={handleLocateClick}
onClick={() => { role='button'
if (active) { tabIndex={0}
lc.stop() onKeyDown={(e) => {
setActive(false) if (e.key === 'Enter' || e.key === ' ') {
} else { e.preventDefault()
lc.start() handleLocateClick()
setLoading(true) }
} }}
}} aria-label={active ? 'Stop location tracking' : 'Start location tracking'}
> >
{loading ? ( {loading ? (
<span className='tw:loading tw:loading-spinner tw:loading-md tw:mt-1'></span> <span className='tw:loading tw:loading-spinner tw:loading-md tw:mt-1' />
) : ( ) : (
<SVG <SVG
src={TargetSVG} src={TargetSVG}
className='tw:mt-1 tw:p-[1px]' className='tw:mt-1 tw:p-[1px]'
style={{ fill: `${active ? '#fc8702' : 'currentColor'}` }} style={{ fill: active ? '#fc8702' : 'currentColor' }}
/> />
)} )}
</div>
</div> </div>
</> </div>
) )
} }