Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve viewpoint hd feature #1229

Merged
merged 7 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions frontend/src/components/BackToMapButton/BackToMapButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,37 @@ import { useCallback } from 'react';
import { Map } from 'components/Icons/Map';
import { FormattedMessage } from 'react-intl';
import { cn } from 'services/utils/cn';
import { ViewPoint } from 'components/Icons/ViewPoint';

export const BackToMapButton: React.FC<
React.ButtonHTMLAttributes<HTMLButtonElement> & {
displayMap?: () => void;
setMapId?: (key: string) => void;
mapId?: string;
}
> = ({ displayMap, setMapId, ...nativeButtonProps }) => {
> = ({ displayMap, setMapId, mapId = 'default', ...nativeButtonProps }) => {
const handleClick = useCallback(() => {
displayMap?.();
setMapId?.('default');
}, [displayMap, setMapId]);
setMapId?.(mapId);
}, [displayMap, mapId, setMapId]);

const isDefaultMap = mapId === 'default';

return (
<button
id="backToMapButton"
type="button"
className={cn(
`flex items-center fixed z-mapButton bottom-6 left-1/2 -translate-x-1/2
py-3 px-4
`flex items-center fixed z-mapButton bottom-6 left-1/2 -translate-x-1/2
py-3 px-4 gap-1
shadow-sm rounded-full text-sm
text-primary1 bg-white hover:bg-primary2 focus:bg-primary2 transition-all`,
)}
{...nativeButtonProps}
onClick={handleClick}
>
<FormattedMessage id="search.seeMap" />
<Map size={24} className="ml-1" />
<FormattedMessage id={isDefaultMap ? 'search.seeMap' : 'search.seeViewPoint'} />
{isDefaultMap ? <Map size={24} aria-hidden /> : <ViewPoint size={24} aria-hidden />}
</button>
);
};
11 changes: 2 additions & 9 deletions frontend/src/components/Icons/Map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import React from 'react';
import { GenericIconProps } from '../types';

export const Map: React.FC<GenericIconProps> = ({
color = 'currentColor',
opacity,
className,
size,
}) => {
export const Map: React.FC<GenericIconProps> = ({ color = 'currentColor', size, ...props }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
opacity={opacity}
{...props}
>
<path
clipRule="evenodd"
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/Map/DetailsMap/DetailsMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
if (visibleSection === 'report' && reportVisibility) {
props.setMapId?.('default');
}
}, [visibleSection, props.setMapId]);

Check warning on line 163 in frontend/src/components/Map/DetailsMap/DetailsMap.tsx

View workflow job for this annotation

GitHub Actions / install-and-test

React Hook useEffect has missing dependencies: 'props' and 'reportVisibility'. Either include them or remove the dependency array. However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the useEffect call and refer to those specific props inside useEffect

const hasTitle = Boolean(props.title);

Expand Down Expand Up @@ -329,6 +329,13 @@
/>
</div>
)}
{Number(props.viewPoints?.length) > 0 && (
<BackToMapButton
displayMap={props.displayMap}
setMapId={props.setMapId}
mapId={props.viewPoints?.[0]?.id}
/>
)}
</>
)}
</MapContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
import L from 'leaflet';
import SVG from 'react-inlinesvg';
import { GeoJsonProperties, Geometry } from 'geojson';
import { Circle, CircleMarker, Polygon, Polyline, Tooltip, useMap } from 'react-leaflet';
import { Circle, CircleMarker, Marker, Polygon, Polyline, Tooltip, useMap } from 'react-leaflet';
import Image from 'next/image';
import { TrekMarker } from 'components/Map/Markers/TrekMarker';
import { optimizeAndDefineColor } from 'stylesheet';

type Props = {
geometry: Geometry;
properties: GeoJsonProperties;
id: string;
};

const Icon = ({ pictogramUri }: { pictogramUri: string }) => {
if (!pictogramUri) {
return null;
}
return pictogramUri.endsWith('.svg') ? (
<SVG src={pictogramUri} className="size-6" preProcessor={optimizeAndDefineColor()} />
) : (
<Image loading="lazy" src={pictogramUri} width={16} height={16} alt="" />
);
};
dtrucs marked this conversation as resolved.
Show resolved Hide resolved

const MetaData = ({ properties }: { properties: GeoJsonProperties }) => {
if (properties === null || !properties.name) {
return null;
}
const pictogramUri = properties?.category?.pictogramUri;

return (
<Tooltip>
<span className="text-base">{properties.name}</span>
<span className="flex flex-wrap items-center gap-2">
{Boolean(properties.category?.label) && (
<>
{Boolean(pictogramUri) && <Icon pictogramUri={pictogramUri} />}
dtrucs marked this conversation as resolved.
Show resolved Hide resolved
<span>{properties.category.label}</span>
</>
)}

<span>{properties.name}</span>
</span>
</Tooltip>
);
};
Expand All @@ -39,10 +65,24 @@ export const AnnotationItem = ({ geometry, properties, id }: Props) => {
if (geometry.type === 'Point' || geometry.type === 'MultiPoint') {
const coordinatesAsMultiPoint =
geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates;

const pictogramUri = properties?.category?.pictogramUri;

return (
<>
{coordinatesAsMultiPoint.map((coordinates, index) => {
const [lat, lng] = coordinates;
if (pictogramUri) {
return (
<Marker
key={`point-${id}-${index}`}
position={[lng, lat]}
icon={TrekMarker(pictogramUri as string, 1)}
>
<MetaData properties={properties} />
</Marker>
);
}
return (
<CircleMarker
className="annotation annotation-point"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/details/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const getDetails = async (
getAccessibilities(language),
]);

const viewPoints = await adaptViewPoints(rawDetails.properties.view_points ?? []);
const viewPoints = await adaptViewPoints(language, rawDetails.properties.view_points ?? []);

const [
activity,
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/modules/outdoorSite/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ export const getOutdoorSiteDetails = async (
: [],
]);

const viewPoints = await adaptViewPoints(rawOutdoorSiteDetails.properties.view_points ?? []);
const viewPoints = await adaptViewPoints(
language,
rawOutdoorSiteDetails.properties.view_points ?? [],
);

return adaptOutdoorSiteDetails({
rawOutdoorSiteDetails,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/modules/poi/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import { adaptViewPoints } from 'modules/viewPoint/adapter';
import { Poi, RawPoi } from './interface';

export const adaptPoi = ({
language,
rawPoisResults,
poiTypes,
}: {
language: string;
rawPoisResults: RawPoi[];
poiTypes: PoiTypeDictionnary;
}): Promise<Poi[]> =>
Promise.all(
rawPoisResults.map(async rawPoi => {
const viewPoints =
rawPoi.view_points?.length > 0 ? await adaptViewPoints(rawPoi.view_points) : [];
rawPoi.view_points?.length > 0 ? await adaptViewPoints(language, rawPoi.view_points) : [];

return {
id: `${rawPoi.id}`,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/poi/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export const getPois = async (id: number, language: string, key = 'near_trek'):
fetchPois({ language, [key]: id, page_size: pageSize }),
getPoiTypes(language),
]);
return adaptPoi({ rawPoisResults: rawPois.results, poiTypes });
return adaptPoi({ language, rawPoisResults: rawPois.results, poiTypes });
};
35 changes: 30 additions & 5 deletions frontend/src/modules/viewPoint/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
import { getViewPointMetadata } from './connector';
import { RawViewPoint, ViewPoint } from './interface';
import { getViewPointCategories, getViewPointMetadata } from './connector';
import { RawViewPoint, RawViewPointCategories, ViewPoint, ViewPointCategories } from './interface';

export const adaptViewPoints = async (rawViewpoints: RawViewPoint[]): Promise<ViewPoint[]> => {
export const adaptViewPointsCategories = (
viewPointsCategories: RawViewPointCategories[],
): ViewPointCategories =>
Object.fromEntries(
viewPointsCategories.map(({ pictogram, ...item }) => [
item.id,
{ ...item, pictogramUri: pictogram },
]),
);

export const adaptViewPoints = async (
language: string,
rawViewpoints: RawViewPoint[],
): Promise<ViewPoint[]> => {
if (rawViewpoints.length === 0) {
return [];
}
const viewPoints = await Promise.all(
rawViewpoints.map(async viewpoint => {
const metadata = await getViewPointMetadata(viewpoint.metadata_url);
const [metadata, categories] = await Promise.all([
getViewPointMetadata(viewpoint.metadata_url),
getViewPointCategories(language),
]);
return {
annotations: viewpoint.annotations,
annotations: {
...viewpoint.annotations,
features: viewpoint.annotations.features.map(feature => ({
...feature,
properties: {
...feature.properties,
category: categories?.[feature.properties?.category] || null,
},
})),
},
id: String(viewpoint.id),
author: viewpoint.author,
geometry: viewpoint.geometry ?? null,
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/modules/viewPoint/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GeotrekAPI } from 'services/api/client';
import { ViewPoint } from './interface';
import { APIQuery, APIResponseForList } from 'services/api/interface';
import { RawViewPointCategories, ViewPoint } from './interface';

export const fetchViewPointMetadata = (url: string): Promise<ViewPoint['metadata']> => {
try {
Expand All @@ -9,3 +10,10 @@ export const fetchViewPointMetadata = (url: string): Promise<ViewPoint['metadata
throw e;
}
};

export const fetchViewPointCategories = (
query: APIQuery,
): Promise<APIResponseForList<RawViewPointCategories>> =>
GeotrekAPI.get('/annotation_category/', {
params: { ...query },
}).then(r => r.data);
16 changes: 14 additions & 2 deletions frontend/src/modules/viewPoint/connector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fetchViewPointMetadata } from './api';
import { ViewPoint } from './interface';
import { adaptViewPointsCategories } from './adapter';
import { fetchViewPointCategories, fetchViewPointMetadata } from './api';
import { ViewPoint, ViewPointCategories } from './interface';

export const getViewPointMetadata = async (url: string): Promise<ViewPoint['metadata'] | null> => {
try {
Expand All @@ -8,3 +9,14 @@ export const getViewPointMetadata = async (url: string): Promise<ViewPoint['meta
return null;
}
};

export const getViewPointCategories = async (
language: string,
): Promise<ViewPointCategories | null> => {
try {
const rawViewPointsCategories = await fetchViewPointCategories({ language });
return adaptViewPointsCategories(rawViewPointsCategories.results);
} catch (e) {
mabhub marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
};
15 changes: 15 additions & 0 deletions frontend/src/modules/viewPoint/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export interface RawViewPoint {
geometry?: RawPointGeometry2D;
}

export interface RawViewPointCategories {
id: string;
name: string;
pictogram: string;
}

export interface ViewPoint {
annotations: FeatureCollection;
id: string;
Expand All @@ -32,3 +38,12 @@ export interface ViewPoint {
thumbnailUrl: string;
geometry: RawPointGeometry2D | null;
}

export interface ViewPointCategory {
id: string;
name: string;
pictogramUri: string;
}
export interface ViewPointCategories {
[id: string]: ViewPointCategory;
}
3 changes: 2 additions & 1 deletion frontend/src/translations/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"resultsFound": "{count, plural, =0 {# resultat trobat} one {# resultat trobat} other {# resultats trobats}}",
"resultsFoundShort": "{count, plural, =0 {# resultat} one {# resultat} other {# resultats}}",
"forThe": "Per a",
"seeMap": "mostra el mapa",
"seeMap": "Mostra el mapa",
"seeViewPoint": "Mostra en imatge HD",
"filter": "Filtrar",
"closeFilters": "Tanca els filtres",
"reload": "Tornar a carregar",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"resultsFoundShort": "{count, plural, =0 {# Ergebnis} one {# Ergebnis} other {# Ergebnisse}}",
"forThe": "Für das",
"seeMap": "Siehe die Karte",
"seeViewPoint": "Ansicht in HD-Bild",
"filter": "Filter",
"closeFilters": "Filter schließen",
"reload": "Neuladen",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"resultsFoundShort": "{count, plural, =0 {# result} one {# result} other {# results}}",
"forThe": "For the",
"seeMap": "Display Map",
"seeViewPoint": "Display in HD picture",
"filter": "Filter",
"closeFilters": "Close filters",
"reload": "Reload",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"resultsFound": "{count, plural, =0 {# resultado encontrado} one {# resultado encontrado} other {# resultados encontrados}}",
"resultsFoundShort": "{count, plural, =0 {# resultado} one {# resultado} other {# resultados}}",
"forThe": "Para",
"seeMap": "mostrar el mapa",
"seeMap": "Mostrar el mapa",
"seeViewPoint": "Mostrar en imagen HD",
"filter": "Filtrar",
"closeFilters": "Cerrar filtros",
"reload": "Volver a cargar",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"resultsFoundShort": "{count, plural, =0 {# résultat} one {# résultat} other {# résultats}}",
"forThe": "Pour le",
"seeMap": "Voir la carte",
"seeViewPoint": "Voir en image HD",
"filter": "Filtrer",
"closeFilters": "Fermer les filtres",
"reload": "Recharger",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"resultsFoundShort": "{count, plural, =0 {# Nessun risultato} one {# risultato} other {# risultati}}",
"forThe": "Per il",
"seeMap": "Vedere la mappa",
"seeViewPoint": "Vedere in immagine HD",
"filter": "Filtro",
"closeFilters": "Chiudere i filtri",
"reload": "Ricaricare",
Expand Down
Loading