Skip to content

Commit

Permalink
feat(20432): select ref area by user location (#966)
Browse files Browse the repository at this point in the history
  • Loading branch information
Natallia-Harshunova authored Jan 28, 2025
1 parent c6f9383 commit 9637565
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/core/focused_geometry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type GeometrySource =
| GeometrySourceFromFile
| GeometrySourceDrawn;

export type GeometryWithHash = GeoJSON.GeoJSON & { hash: string };
export type GeometryWithHash = GeoJSON.GeoJSON & { hash?: string };

export interface FocusedGeometry {
source: GeometrySource;
Expand Down
10 changes: 8 additions & 2 deletions src/core/localization/translations/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"km": "km",
"m": "m",
"to": "to",
"or": "or",
"logout": "Log out",
"save": "Save",
"cancel": "Cancel",
Expand Down Expand Up @@ -456,9 +457,14 @@
"reference_area": {
"title": "Reference area",
"freehand_geometry": "Freehand geometry",
"to_replace_reference_area": "To replace reference area, select an area on the map and click \"Save as reference area\" on toolbar.",
"to_replace_reference_area": "You can redefine your reference area on map. Select an area and click \"Save as reference area\" on toolbar.\n",
"description": "Save an area you are familiar with as a reference. We will use it as a baseline to compare other areas and explain the differences.",
"set_the_reference_area": "Set area on map"
"set_the_reference_area": "Set area on map",
"tooltip_text": "1.Select an area of interest on the map using the Admin Boundary or Draw Geometry tool. <br/> 2. Click the 'Save as Reference' button on the toolbar.",
"accessing_location": "Accessing your location",
"accessing_location_error": "Error. Try another way.",
"select_location": "Select my current location",
"notification": "Your reference area {{name}} has been saved"
}
},
"current_event": {
Expand Down
4 changes: 3 additions & 1 deletion src/core/shared_state/referenceArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ function getReferenceAreaFromConfigRepo(): GeometryWithHash | null {
export const setReferenceArea = action(async (ctx, geometry: GeometryWithHash) => {
if (geometry) {
dispatchMetricsEvent('ref_area');
const hash = await crc32(JSON.stringify({ geometry }));
const hash = geometry.hash
? geometry.hash
: await crc32(JSON.stringify({ geometry }));
// update only in case if geometry source or hash has changed
const referenceAreaOld = ctx.get(referenceAreaAtom);
if (!referenceAreaOld || !referenceAreaOld.hash || referenceAreaOld.hash !== hash) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

.linksWrapper {
margin-top: 12px;
display: flex;
align-items: center;
line-height: 18px;
font-size: 12px;
color: var(--faint-strong);
}

.link {
Expand All @@ -14,12 +19,14 @@
}

.geometryNameContainer {
margin-top: var(--double-unit);
display: flex;
justify-content: space-between;
width: 100%;

& + * {
margin-top: var(--unit);
color: var(--faint-strong);
}
}

Expand All @@ -29,3 +36,40 @@
padding: var(--half-unit) 0 0 0;
height: 16px;
}

.tooltip {
margin-inline: 2px;
}

.delimiter {
margin-right: 4px;
margin-left: 2px;
}

.userLocationLoader {
color: var(--base-strong);

&::after {
content: '';
animation: dot-blink 1s infinite;
}
}

.errorMessage {
color: var(--base-strong);
}

@keyframes dot-blink {
0% {
content: '';
}
33% {
content: '.';
}
66% {
content: '..';
}
100% {
content: '...';
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { Text } from '@konturio/ui-kit';
import { Heading, Text } from '@konturio/ui-kit';
import { useAtom } from '@reatom/npm-react';
import { Rubber16 } from '@konturio/default-icons';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import clsx from 'clsx';
import { i18n } from '~core/localization';
import { referenceAreaAtom, resetReferenceArea } from '~core/shared_state/referenceArea';
import {
referenceAreaAtom,
resetReferenceArea,
setReferenceArea,
} from '~core/shared_state/referenceArea';
import { goTo } from '~core/router/goTo';
import { store } from '~core/store/store';
import { PopupTooltipTrigger } from '~components/PopupTooltipTrigger';
import { updateReferenceArea } from '~core/api/features';
import { getUserLocation } from '~utils/common/userLocation';
import { getBoundaries } from '~core/api/boundaries';
import { notificationServiceInstance } from '~core/notificationServiceInstance';
import s from './ReferenceAreaInfo.module.css';

export function ReferenceAreaInfo() {
const [referenceAreaGeometry] = useAtom(referenceAreaAtom);
const [isLocationLoading, setIsLocationLoading] = useState<boolean>(false);
const [locationLoadError, setLocationLoadError] = useState<boolean>(false);

const referenceAreaName = useMemo(() => {
if (
Expand All @@ -25,12 +37,50 @@ export function ReferenceAreaInfo() {
resetReferenceArea(store.v3ctx);
}, []);

const onSelectCurrentLocation = async () => {
setIsLocationLoading(true);
setLocationLoadError(false);

try {
const coords = await getUserLocation();
const response = await getBoundaries(coords);
if (!response) {
throw new Error('No response for boundaries request');
}

const features = response.features.sort(
(f1, f2) => f2.properties?.admin_level - f1.properties?.admin_level,
);

const refArea = features[0];
await updateReferenceArea(refArea);
await setReferenceArea(store.v3ctx, refArea);
notificationServiceInstance.success(
{
title: i18n.t('profile.reference_area.notification', {
name: refArea.properties?.name,
}),
},
2,
);
} catch (error) {
setLocationLoadError(true);
console.error('Error occurred while getting user location', error);
} finally {
setIsLocationLoading(false);
}
};

return (
<div className={s.infoContainer}>
<Text type="long-m">{i18n.t('profile.reference_area.description')}</Text>

{referenceAreaGeometry ? (
<>
<div className={s.geometryNameContainer}>
<Text type="short-m">{referenceAreaName}</Text>
<Heading type="heading-04" margins={false}>
{referenceAreaName}
</Heading>
<span className={s.clean} onClick={onDeleteReferenceAreaClicked}>
<Rubber16 />
</span>
Expand All @@ -43,11 +93,32 @@ export function ReferenceAreaInfo() {
</>
) : (
<>
<Text type="long-m">{i18n.t('profile.reference_area.description')}</Text>
<div className={s.linksWrapper}>
<a className={s.link} onClick={() => goTo('/map')}>
{i18n.t('profile.reference_area.set_the_reference_area')}
</a>
<PopupTooltipTrigger
tipText={i18n.t('profile.reference_area.tooltip_text')}
className={s.tooltip}
showedOnHover={true}
/>
<span className={s.delimiter}>{i18n.t('or')}</span>
{isLocationLoading ? (
<span className={s.userLocationLoader}>
{i18n.t('profile.reference_area.accessing_location')}
</span>
) : locationLoadError ? (
<span className={s.errorMessage}>
{i18n.t('profile.reference_area.accessing_location_error')}
</span>
) : (
<button
className={clsx(s.link, s.userLocation)}
onClick={onSelectCurrentLocation}
>
{i18n.t('profile.reference_area.select_location')}
</button>
)}
</div>
</>
)}
Expand Down
18 changes: 18 additions & 0 deletions src/utils/common/userLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function getUserLocation() {
return new Promise<[number, number]>((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation is not supported by this browser.'));
return;
}

navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
resolve([longitude, latitude]);
},
(error) => {
reject(new Error(`Error getting location: ${error.message}`));
},
);
});
}

0 comments on commit 9637565

Please sign in to comment.