diff --git a/pages/api/og-image.tsx b/pages/api/og-image.tsx index b51d16d99..41b0e4dd6 100644 --- a/pages/api/og-image.tsx +++ b/pages/api/og-image.tsx @@ -76,8 +76,8 @@ const renderSvg = async ( export default async (req: NextApiRequest, res: NextApiResponse) => { const t1 = Date.now(); try { - const shortId = getApiId(req.query.id); - const feature = await fetchWithMemberFeatures(shortId); + const osmId = getApiId(req.query.id); + const feature = await fetchWithMemberFeatures(osmId); const def = feature.imageDefs?.[0]; // TODO iterate when first not found if (!def) { throw new Error('No image definition found'); diff --git a/src/components/FeaturePanel/CragsInArea.tsx b/src/components/FeaturePanel/CragsInArea.tsx index d6bbbdc36..4553bf65d 100644 --- a/src/components/FeaturePanel/CragsInArea.tsx +++ b/src/components/FeaturePanel/CragsInArea.tsx @@ -1,17 +1,17 @@ import styled from '@emotion/styled'; -import { Box, useTheme } from '@mui/material'; +import { Box } from '@mui/material'; import React from 'react'; import Router from 'next/router'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import { useFeatureContext } from '../utils/FeatureContext'; import { getOsmappLink, getUrlOsmId } from '../../services/helpers'; -import { Feature } from '../../services/types'; +import { Feature, isInstant, OsmId } from '../../services/types'; import { useMobileMode } from '../helpers'; -import { getWikimediaCommonsKeys } from './Climbing/utils/photo'; -import { useScrollShadow } from './Climbing/utils/useScrollShadow'; import { getLabel } from '../../helpers/featureLabel'; -import { getCommonsImageUrl } from '../../services/images/getCommonsImageUrl'; +import { Slider, Wrapper } from './ImagePane/FeatureImages'; +import { Image } from './ImagePane/Image/Image'; +import { getInstantImage } from '../../services/images/getImageDefs'; const ArrowIcon = styled(ArrowForwardIosIcon)` opacity: 0.2; @@ -21,6 +21,7 @@ const HeadingRow = styled.div` display: flex; flex-direction: row; align-items: center; + padding: 0 12px; `; const Container = styled.div` overflow: auto; @@ -30,7 +31,7 @@ const Container = styled.div` justify-content: space-between; cursor: pointer; border-radius: 8px; - padding: 12px; + padding: 12px 0; background-color: ${({ theme }) => theme.palette.background.elevation}; &:hover { ${ArrowIcon} { @@ -44,12 +45,13 @@ const CragList = styled.div` flex-direction: column; gap: 12px; `; -const Anchor = styled.a` +const Link = styled.a` text-decoration: none !important; `; const Content = styled.div` flex: 1; `; + const CragName = styled.div` padding: 0; font-weight: 900; @@ -64,108 +66,92 @@ const NumberOfRoutes = styled.div` font-size: 13px; color: ${({ theme }) => theme.palette.secondary.main}; `; -const Gallery = styled.div` - display: flex; - gap: 8px; - border-radius: 8px; - overflow: auto; - margin-top: 12px; -`; -const Image = styled.img` - border-radius: 8px; - height: 200px; - flex: 1; - object-fit: cover; -`; -const CragItem = ({ feature }: { feature: Feature }) => { - const theme: any = useTheme(); +const Header = ({ + imagesCount, + label, + routesCount, +}: { + label: string; + routesCount: number; + imagesCount: number; +}) => ( + + + {label}{' '} + + {routesCount > 0 && ( + {routesCount} routes + )} + {imagesCount > 0 && ( + {imagesCount} photos + )} + + + + +); +const Gallery = ({ images }) => { + return ( + + + {images.map((item) => ( + + ))} + + + ); +}; + +const getOnClickWithHash = (apiId: OsmId) => (e) => { + e.preventDefault(); + Router.push(`/${getUrlOsmId(apiId)}${window.location.hash}`); +}; + +const CragItem = ({ feature }: { feature: Feature }) => { const mobileMode = useMobileMode(); const { setPreview } = useFeatureContext(); - const { osmMeta } = feature; - const handleClick = (e) => { - e.preventDefault(); - setPreview(null); - Router.push(`/${getUrlOsmId(osmMeta)}${window.location.hash}`); - }; const handleHover = () => feature.center && setPreview(feature); - const cragPhotoKeys = getWikimediaCommonsKeys(feature.tags); + const images = + feature?.imageDefs?.filter(isInstant)?.map((def) => ({ + def, + image: getInstantImage(def), + })) ?? []; - const { - scrollElementRef, - onScroll, - ShadowContainer, - ShadowLeft, - ShadowRight, - } = useScrollShadow(); return ( - setPreview(null)} > - - - {getLabel(feature)}{' '} - - {feature.members?.length > 0 && ( - - {feature.members.length} routes{' '} - - )} - {cragPhotoKeys.length > 0 && ( - {cragPhotoKeys.length} photos - )} - - - - - {cragPhotoKeys.length > 0 && ( - - - - {cragPhotoKeys.map((cragPhotoTag) => { - const photoPath = feature.tags[cragPhotoTag]; - const url = getCommonsImageUrl(photoPath, 410); - return ; - })} - - - - )} +
+ {images.length ? : null} - + ); }; + export const CragsInArea = () => { - const { - feature: { memberFeatures, tags }, - } = useFeatureContext(); + const { feature } = useFeatureContext(); - if (!memberFeatures?.length) { + if (!feature.memberFeatures?.length) { return null; } - - const isClimbingArea = tags.climbing === 'area'; - if (!isClimbingArea) { + if (feature.tags.climbing !== 'area') { return null; } return ( - {memberFeatures.map((item) => ( + {feature.memberFeatures.map((item) => ( ))} diff --git a/src/components/FeaturePanel/FeaturePanel.tsx b/src/components/FeaturePanel/FeaturePanel.tsx index ad306ec1c..677a0806d 100644 --- a/src/components/FeaturePanel/FeaturePanel.tsx +++ b/src/components/FeaturePanel/FeaturePanel.tsx @@ -22,6 +22,7 @@ import { RouteDistributionInPanel } from './Climbing/RouteDistribution'; import { RouteListInPanel } from './Climbing/RouteList/RouteList'; import { FeaturePanelFooter } from './FeaturePanelFooter'; import { ClimbingRouteGrade } from './ClimbingRouteGrade'; +import { Box } from '@mui/material'; const Flex = styled.div` flex: 1; @@ -61,7 +62,9 @@ export const FeaturePanel = () => { - + + + diff --git a/src/components/FeaturePanel/ImagePane/FeatureImages.tsx b/src/components/FeaturePanel/ImagePane/FeatureImages.tsx index b77c440df..0fb8cddc2 100644 --- a/src/components/FeaturePanel/ImagePane/FeatureImages.tsx +++ b/src/components/FeaturePanel/ImagePane/FeatureImages.tsx @@ -6,12 +6,10 @@ import { useLoadImages } from './useLoadImages'; import { NoImage } from './NoImage'; import { HEIGHT, ImageSkeleton } from './helpers'; -const Wrapper = styled.div` +export const Wrapper = styled.div` width: 100%; height: calc(${HEIGHT}px + 10px); // 10px for scrollbar min-height: calc(${HEIGHT}px + 10px); // otherwise it shrinks b/c of flex - - margin-bottom: 16px; `; const StyledScrollbars = styled(Scrollbars)` @@ -25,7 +23,7 @@ const StyledScrollbars = styled(Scrollbars)` scroll-behavior: smooth; -webkit-overflow-scrolling: touch; `; -const Slider = ({ children }) => ( +export const Slider = ({ children }) => ( {children} diff --git a/src/components/Map/behaviour/useInitMap.tsx b/src/components/Map/behaviour/useInitMap.tsx index 6b1b8cbeb..a03b53a62 100644 --- a/src/components/Map/behaviour/useInitMap.tsx +++ b/src/components/Map/behaviour/useInitMap.tsx @@ -14,6 +14,7 @@ const filterConsoleLog = () => { // eslint-disable-next-line no-console console.warn = (message, ...optionalParams) => { if ( + typeof message === 'string' && !message.includes( 'Please make sure you have added the image with map.addImage', ) diff --git a/src/components/Map/behaviour/useUpdateStyle.tsx b/src/components/Map/behaviour/useUpdateStyle.tsx index 1efbad5a9..f2e742c39 100644 --- a/src/components/Map/behaviour/useUpdateStyle.tsx +++ b/src/components/Map/behaviour/useUpdateStyle.tsx @@ -81,9 +81,7 @@ export const useUpdateStyle = createMapEffectHook( const style = cloneDeep(getBaseStyle(key)); addOverlaysToStyle(map, style, overlays); - console.log('style', map.loaded(), map.getStyle()); // eslint-disable-line no-console map.setStyle(style, { diff: map.loaded() }); - console.log('style2', map.loaded(), map.getStyle()); // eslint-disable-line no-console setUpHover(map, layersWithOsmId(style)); }, diff --git a/src/services/osmApi.ts b/src/services/osmApi.ts index ab1065de2..b706cfcb6 100644 --- a/src/services/osmApi.ts +++ b/src/services/osmApi.ts @@ -1,5 +1,5 @@ import { resolveCountryCode } from 'next-codegrid'; -import { FetchError, getApiId, getShortId, getUrlOsmId, prod } from './helpers'; +import { FetchError, getShortId, getUrlOsmId, prod } from './helpers'; import { fetchJson } from './fetch'; import { Feature, LonLat, OsmId, Position, SuccessInfo } from './types'; import { removeFetchCache } from './fetchCache'; @@ -12,6 +12,7 @@ import { getImageDefs, mergeMemberImageDefs } from './images/getImageDefs'; import * as Sentry from '@sentry/nextjs'; import { fetchOverpassCenter } from './overpass/fetchOverpassCenter'; import { isClimbingRelation, isClimbingRoute } from '../utils'; +import { getOverpassUrl } from './overpassSearch'; const getOsmUrl = ({ type, id }) => `https://api.openstreetmap.org/api/0.6/${type}/${id}.json`; @@ -114,37 +115,82 @@ const getItemsMap = (elements) => { return map; }; +const getMemberFeatures = (members: Feature['members'], map) => { + return ( + members + ?.map(({ type, ref, role }) => { + const element = map[type][ref]; + if (!element) { + return null; + } + + const feature = addSchemaToFeature(osmToFeature(element)); + feature.osmMeta.role = role; + feature.center = element.center + ? [element.center.lon, element.center.lat] // from overpass "out center" + : undefined; + return feature; + }) + .filter(Boolean) ?? [] + ); +}; + export const fetchWithMemberFeatures = async (apiId: OsmId) => { - // TODO we can compute geometry using cragsToGeojson() and display it in the map - const url = - apiId.type === 'relation' ? getOsmFullUrl(apiId) : getOsmUrl(apiId); - const full = await fetchJson(url); + if (apiId.type !== 'relation') { + const wayOrNode = await fetchJson(getOsmUrl(apiId)); + return addSchemaToFeature(osmToFeature(wayOrNode)); + } + + const full = await fetchJson(getOsmFullUrl(apiId)); const map = getItemsMap(full.elements); - const mainFeature = map[apiId.type][apiId.id]; + const relation = map.relation[apiId.id]; + + const out: Feature = { + ...addSchemaToFeature(osmToFeature(relation)), + memberFeatures: getMemberFeatures(relation.members, map), + }; + mergeMemberImageDefs(out); + return out; +}; +const addMemberFeaturesToArea = async (relation: Feature) => { + const { tags, osmMeta } = relation; + const url = getOverpassUrl(`[out:json];rel(${osmMeta.id});>>;out center qt;`); + const overpass = await fetchJson(url); + const itemsMap = getItemsMap(overpass.elements); + const memberFeatures = getMemberFeatures(relation.members, itemsMap).map( + (memberFeature) => { + const crag: Feature = { + ...memberFeature, + memberFeatures: getMemberFeatures(memberFeature.members, itemsMap), + }; + mergeMemberImageDefs(crag); + return crag; + }, + ); + + return { ...relation, memberFeatures }; +}; + +const addMemberFeaturesToRelation = async (relation: Feature) => { + const { tags, osmMeta: apiId } = relation; if (apiId.type !== 'relation') { - return addSchemaToFeature(osmToFeature(mainFeature)); + throw new Error('addMemberFeaturesToRelation() called with non-relation'); } - const memberFeatures = - mainFeature.members.map(({ type, ref, role }) => { - const element = map[type][ref]; - if (!element) { - return null; - } + if (tags.climbing === 'area') { + return await addMemberFeaturesToArea(relation); + } - const feature = addSchemaToFeature(osmToFeature(element)); - feature.osmMeta.role = role; - return feature; - }) ?? []; + const full = await fetchJson(getOsmFullUrl(apiId)); + const map = getItemsMap(full.elements); - const featureWithMemberFeatures = { - ...addSchemaToFeature(osmToFeature(mainFeature)), - memberFeatures: memberFeatures.filter(Boolean), + const out: Feature = { + ...relation, + memberFeatures: getMemberFeatures(relation.members, map), }; - mergeMemberImageDefs(featureWithMemberFeatures); // TODO test + only for crag - - return featureWithMemberFeatures; + mergeMemberImageDefs(out); + return out; }; // TODO parent should be probably fetched for every feaure in fetchFeatureWithCenter() @@ -162,8 +208,9 @@ export const addMembersAndParents = async ( if (isClimbingRelation(feature)) { const [parentFeatures, featureWithMemberFeatures] = await Promise.all([ fetchParentFeatures(feature.osmMeta), - fetchWithMemberFeatures(feature.osmMeta), + addMemberFeaturesToRelation(feature), ]); + return { ...featureWithMemberFeatures, center: feature.center, // feature contains correct center from centerCache or overpass