diff --git a/src/components/details.page.tsx b/src/components/details.page.tsx index a30d20b..2111a09 100644 --- a/src/components/details.page.tsx +++ b/src/components/details.page.tsx @@ -1,16 +1,12 @@ -import Image from 'next/image'; import { Attachement } from '@/api/settings'; import { LatLngTuple } from 'leaflet'; -import { useTranslations } from 'next-intl'; import { convertAttachementsToImages } from '@/lib/utils'; -import { Badge } from '@/components/ui/badge'; -import { Icons, propsForSVGPresentation } from '@/components/icons'; import ButtonCenterView from './button-center-view'; import ButtonClose from './button-close'; import Carousel from './carousel'; -import MeterLength from './length'; +import { MetadataList } from './metadata-list'; type Props = { content: { @@ -35,13 +31,12 @@ type Props = { }; export default function DetailsPageUI({ content }: Props) { - const t = useTranslations('details'); return (
{content.attachments.length > 0 && (
-
- {content.length !== undefined && ( - <> -
- - {t('length')} -
-
- -
- - )} - {content.descent !== undefined && ( - <> -
- - {t('descent')} -
-
- -
- - )} - {content.flow && ( - <> -
- - {t('flow')} -
-
{content.flow}
- - )} - {content.type && ( - <> -
{content.type.category.label} :
-
- - {content.type.pictogram && ( - - )} - {content.type.label} - -
- - )} -
+
diff --git a/src/components/map/geometry-item.tsx b/src/components/map/geometry-item.tsx index fa1dbff..957f1ff 100644 --- a/src/components/map/geometry-item.tsx +++ b/src/components/map/geometry-item.tsx @@ -1,19 +1,16 @@ import { Fragment } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; import { Layer } from '@/api/settings'; import { GeoJsonProperties, Geometry } from 'geojson'; -import { GeoJSONOptions } from 'leaflet'; +import { GeoJSONOptions, PathOptions } from 'leaflet'; import { renderToStaticMarkup } from 'react-dom/server'; -import { - Popup as LeafletPopup, - Marker, - Polygon, - Polyline, - Tooltip, -} from 'react-leaflet'; +import { Marker, Polygon, Polyline } from 'react-leaflet'; +import { cn } from '@/lib/utils'; import { Icons } from '@/components/icons'; import { DefaultMarker } from '@/components/map/default-marker'; -import Popup from '@/components/map/popup'; + +import { GeometryTooltip } from './geometry-tooltip'; type Props = { geometry: Geometry; @@ -23,34 +20,6 @@ type Props = { options: GeoJSONOptions; }; -const MetaData = ({ - properties, - layer, -}: { - properties: GeoJsonProperties; - layer: Layer; -}) => { - if (properties === null || (!properties.name && !properties.category)) { - return null; - } - return ( - <> - {properties.name ?? properties.category} - {layer.type !== undefined && layer.url && properties.id && ( - - - - )} - - ); -}; - export const GeometryItem = ({ geometry, properties, @@ -58,6 +27,8 @@ export const GeometryItem = ({ layer, options = { style: {} }, }: Props) => { + const params = useSearchParams(); + const router = useRouter(); if (geometry.type === 'GeometryCollection') { return ( <> @@ -75,6 +46,18 @@ export const GeometryItem = ({ ); } + const hasDetails = layer.type !== undefined && layer.url && properties?.id; + + const featureEventHandler = { + ...(hasDetails && { + click: () => { + router.push( + `/map/${layer?.type}/${properties?.id}?${params.toString()}`, + ); + }, + }), + }; + if (geometry.type === 'Point' || geometry.type === 'MultiPoint') { const coordinatesAsMultiPoint = geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates; @@ -90,8 +73,9 @@ export const GeometryItem = ({ key={`point-${id}-${index}`} position={[lng, lat]} icon={DefaultMarker(icon, 1)} + eventHandlers={featureEventHandler} > - + ); })} @@ -129,9 +113,17 @@ export const GeometryItem = ({ weight: 10, opacity: 0, }} - className={layer.type} + eventHandlers={{ + mouseover: e => e.target.setStyle({ opacity: 0.5 }), + mouseout: e => e.target.setStyle({ opacity: 0 }), + ...featureEventHandler, + }} + className={cn( + 'streams-hover', + !hasDetails && '!cursor-[unset]', + )} > - + ); @@ -140,9 +132,10 @@ export const GeometryItem = ({ [lng, lat])} pathOptions={options.style as GeoJSONOptions} - className={layer.type} + className={cn(layer.type, !hasDetails && '!cursor-[unset]')} + eventHandlers={featureEventHandler} > - + ); })} @@ -164,9 +157,22 @@ export const GeometryItem = ({ line.map<[number, number]>(([lat, lng]) => [lng, lat]), )} pathOptions={options.style as GeoJSONOptions} + eventHandlers={{ + mouseover: e => e.target.setStyle({ fillOpacity: 0.8 }), + mouseout: e => + e.target.setStyle({ + fillOpacity: + (options.style as PathOptions)?.fillOpacity ?? 0.2, + }), + ...featureEventHandler, + }} + className={cn( + 'transition-[fill-opacity]', + !hasDetails && '!cursor-[unset]', + )} pane="tilePane" > - + ))} diff --git a/src/components/map/geometry-tooltip.tsx b/src/components/map/geometry-tooltip.tsx new file mode 100644 index 0000000..b0ea4cc --- /dev/null +++ b/src/components/map/geometry-tooltip.tsx @@ -0,0 +1,58 @@ +import Image from 'next/image'; +import { Layer } from '@/api/settings'; +import { GeoJsonProperties } from 'geojson'; +import { Tooltip } from 'react-leaflet'; + +import { MetadataList } from '../metadata-list'; + +export const GeometryTooltip = ({ + properties, + layer, +}: { + properties: GeoJsonProperties; + layer: Layer; +}) => { + if (properties === null || (!properties.name && !properties.category)) { + return null; + } + if (layer.type === undefined || !layer.url || !properties.id) { + return {properties.name ?? properties.category}; + } + return ( + +
+ {properties.attachments?.[0]?.thumbnail && ( + + )} +
+

+ {properties.name ?? properties.category} +

+ + {properties.description && ( + + )} +
+
+
+ ); +}; diff --git a/src/components/map/popup.tsx b/src/components/map/popup.tsx deleted file mode 100644 index 03fcfb5..0000000 --- a/src/components/map/popup.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { useSearchParams } from 'next/navigation'; - -import { - Card, - CardDescription, - CardHeader, - CardMedia, - CardTitle, -} from '../ui/card'; - -type Props = { - name: string; - description: string; - attachments?: { thumbnail: string }[]; - type: string; - id: string; -}; - -export default function Popup({ - attachments, - name, - description, - type, - id, -}: Props) { - const params = useSearchParams(); - return ( - - {attachments?.[0]?.thumbnail && ( - - - - )} - - - - {name} - - - {description && ( - -
- - )} - - - ); -} diff --git a/src/components/metadata-list.tsx b/src/components/metadata-list.tsx new file mode 100644 index 0000000..20ce88b --- /dev/null +++ b/src/components/metadata-list.tsx @@ -0,0 +1,110 @@ +import Image from 'next/image'; +import { useTranslations } from 'next-intl'; + +import { cn } from '@/lib/utils'; +import { Badge } from '@/components/ui/badge'; +import { Icon, Icons, propsForSVGPresentation } from '@/components/icons'; + +import MeterLength from './length'; + +export type MetadataListProps = { + length?: number; + descent?: number; + flow?: string; + type?: { + label: string; + category: { + label: string; + }; + pictogram?: string; + }; + small?: boolean; +}; + +type MetadataItemProps = { + show?: boolean; + icon: Icon; + type: string; + value?: string | number; + meters?: number; + small?: boolean; +}; + +const MetadataItem = ({ + show, + icon: Icon, + type, + value, + meters, + small, +}: MetadataItemProps) => { + const t = useTranslations('details'); + if (!show) return null; + + return ( +
+
+ + {t(type)} +
+
{meters !== undefined ? : value}
+
+ ); +}; + +export const MetadataList = ({ + length, + descent, + flow, + type, + small, +}: MetadataListProps) => { + const t = useTranslations('details'); + return ( +
+ + + + {type && ( +
+
{type.category.label} :
+
+ + {type.pictogram && ( + + )} + {type.label} + +
+
+ )} +
+ ); +}; diff --git a/src/styles/globals.css b/src/styles/globals.css index 20dc46b..a480baf 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -104,17 +104,31 @@ body .leaflet-container a.leaflet-popup-close-button { .streams-animation.leaflet-interactive { stroke-width: 2; - stroke: rgba(255, 255, 255, 0.6); - stroke-dasharray: 10; - stroke-dashoffset: 200; + stroke: rgba(255, 255, 255, 0.75); + stroke-dasharray: 1 15; + stroke-dashoffset: 208; animation-name: dash; - animation-duration: 20s; + animation-duration: 40s; animation-iteration-count: infinite; animation-timing-function: linear; } +.streams-hover.leaflet-interactive:focus { + outline: none !important; + stroke-opacity: 0.5; +} + @keyframes dash { to { stroke-dashoffset: 0; } } + +/* +leaflet bug: tooltips stay open if hovering an element while panning. +As new tooltips are added at the end of .leaflet-tooltip-pane, +just hide all tooltips that aren't the last one +*/ +.leaflet-tooltip-pane > .leaflet-tooltip:not(:last-child) { + display: none; +}