mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-04-06 01:25:33 +00:00
add geo distance to header
This commit is contained in:
parent
52a4c3dea2
commit
996823ddec
@ -10,7 +10,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
|
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
|
||||||
import { MapPinIcon, ShareIcon, UserPlusIcon } from '@heroicons/react/24/outline'
|
import { MapPinIcon, ShareIcon } from '@heroicons/react/24/outline'
|
||||||
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
|
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
|
||||||
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
|
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@ -20,7 +20,9 @@ import { useNavigate } from 'react-router-dom'
|
|||||||
|
|
||||||
import TargetDotSVG from '#assets/targetDot.svg'
|
import TargetDotSVG from '#assets/targetDot.svg'
|
||||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
|
import { useGeoDistance } from '#components/Map/hooks/useGeoDistance'
|
||||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||||
|
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||||
import DialogModal from '#components/Templates/DialogModal'
|
import DialogModal from '#components/Templates/DialogModal'
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
@ -56,8 +58,9 @@ export function HeaderView({
|
|||||||
const hasUserPermission = useHasUserPermission()
|
const hasUserPermission = useHasUserPermission()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
|
const getItemTags = useGetItemTags()
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
const { distance } = useGeoDistance(item?.position ?? undefined)
|
||||||
|
|
||||||
const avatar =
|
const avatar =
|
||||||
(item?.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') ||
|
(item?.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') ||
|
||||||
@ -69,6 +72,11 @@ export function HeaderView({
|
|||||||
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
|
||||||
|
const formatDistance = (dist: number | null): string | null => {
|
||||||
|
if (!dist) return null
|
||||||
|
return dist < 10 ? `${dist.toFixed(1)} km` : `${Math.round(dist)} km`
|
||||||
|
}
|
||||||
|
|
||||||
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
|
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
|
||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@ -197,13 +205,28 @@ export function HeaderView({
|
|||||||
</div>
|
</div>
|
||||||
{big && (
|
{big && (
|
||||||
<div className='tw:flex tw:row tw:mt-2 '>
|
<div className='tw:flex tw:row tw:mt-2 '>
|
||||||
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '></div>
|
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '>
|
||||||
|
{' '}
|
||||||
|
{formatDistance(distance) && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatDistance(distance)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className='tw:grow'></div>
|
<div className='tw:grow'></div>
|
||||||
<div className=''>
|
<div className=''>
|
||||||
<div className='tw:btn tw:btn-sm tw:btn-primary tw:mr-2'>
|
<button
|
||||||
<UserPlusIcon className='tw:w-4' />
|
style={{
|
||||||
|
backgroundColor: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
|
||||||
|
}}
|
||||||
|
className='tw:btn tw:text-white tw:btn-sm tw:mr-2 '
|
||||||
|
>
|
||||||
Follow
|
Follow
|
||||||
</div>
|
</button>
|
||||||
<div className='tw:btn tw:btn-sm tw:mr-2 tw:px-2'>
|
<div className='tw:btn tw:btn-sm tw:mr-2 tw:px-2'>
|
||||||
<LuNavigation className='tw:h-4 tw:w-4' />
|
<LuNavigation className='tw:h-4 tw:w-4' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
53
lib/src/Components/Map/hooks/useGeoDistance.tsx
Normal file
53
lib/src/Components/Map/hooks/useGeoDistance.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
import { useMyProfile } from './useMyProfile'
|
||||||
|
|
||||||
|
import type { Point } from 'geojson'
|
||||||
|
|
||||||
|
const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
|
||||||
|
const R = 6371 // Earth's radius in km
|
||||||
|
const dLat = ((lat2 - lat1) * Math.PI) / 180
|
||||||
|
const dLon = ((lon2 - lon1) * Math.PI) / 180
|
||||||
|
const a =
|
||||||
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos((lat1 * Math.PI) / 180) *
|
||||||
|
Math.cos((lat2 * Math.PI) / 180) *
|
||||||
|
Math.sin(dLon / 2) *
|
||||||
|
Math.sin(dLon / 2)
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||||
|
return R * c // Distance in km
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGeoDistance = (targetPoint?: Point) => {
|
||||||
|
const [distance, setDistance] = useState<number | null>(null)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const { myProfile, isMyProfileLoaded } = useMyProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setError(null)
|
||||||
|
setDistance(null)
|
||||||
|
|
||||||
|
if (!isMyProfileLoaded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myProfile?.position || !targetPoint) {
|
||||||
|
setError('Missing location data')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userGeoJson = myProfile.position
|
||||||
|
const [userLon, userLat] = userGeoJson.coordinates
|
||||||
|
const [targetLon, targetLat] = targetPoint.coordinates
|
||||||
|
|
||||||
|
const dist = getDistance(userLat, userLon, targetLat, targetLon)
|
||||||
|
setDistance(dist)
|
||||||
|
} catch (err) {
|
||||||
|
setError('Calculation error')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}, [myProfile, isMyProfileLoaded, targetPoint])
|
||||||
|
|
||||||
|
return { distance, error, userLocation: myProfile?.position as Point | undefined }
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user