diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index c32d8ed5..d1b8994f 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -12,7 +12,6 @@ import { HomepagePanel } from '../HomepagePanel/HomepagePanel'; import { Loading } from './Loading'; import { FeatureProvider, useFeatureContext } from '../utils/FeatureContext'; import { OsmAuthProvider } from '../utils/OsmAuthContext'; -import { FeaturePreview } from '../FeaturePreview/FeaturePreview'; import { TitleAndMetaTags } from '../../helpers/TitleAndMetaTags'; import { InstallDialog } from '../HomepagePanel/InstallDialog'; import { setIntlForSSR } from '../../services/intl'; @@ -21,6 +20,8 @@ import { ClimbingDialog } from '../FeaturePanel/Climbing/ClimbingDialog'; import { ClimbingContextProvider } from '../FeaturePanel/Climbing/contexts/ClimbingContext'; import { StarsProvider } from '../utils/StarsContext'; import { SnackbarProvider } from '../utils/SnackbarContext'; +import { useMobileMode } from '../helpers'; +import { FeaturePanelInDrawer } from '../FeaturePanel/FeaturePanelInDrawer'; const usePersistMapView = () => { const { view } = useMapStateContext(); @@ -66,7 +67,8 @@ const useUpdateViewFromHash = () => { }; const IndexWithProviders = () => { - const { feature, featureShown, preview } = useFeatureContext(); + const isMobileMode = useMobileMode(); + const { feature, featureShown } = useFeatureContext(); const router = useRouter(); useUpdateViewFromFeature(); usePersistMapView(); @@ -78,9 +80,10 @@ const IndexWithProviders = () => { const photo = router.query.all?.[3]; return ( <> - - {featureShown && } + + {featureShown && !isMobileMode && } + {featureShown && isMobileMode && } {isClimbingDialogShown && ( @@ -90,7 +93,6 @@ const IndexWithProviders = () => { {router.pathname === '/install' && } - {preview && } > ); @@ -98,6 +100,7 @@ const IndexWithProviders = () => { const App = ({ featureFromRouter, initialMapView, cookies }) => { const mapView = getMapViewFromHash() || initialMapView; + return ( diff --git a/src/components/App/Loading.tsx b/src/components/App/Loading.tsx index eb6f16c9..45cb7a88 100644 --- a/src/components/App/Loading.tsx +++ b/src/components/App/Loading.tsx @@ -6,7 +6,7 @@ import { isDesktop, useBoolState } from '../helpers'; const Wrapper = styled.div` position: absolute; - top: 72px; + top: 0; z-index: 1200; width: 100%; diff --git a/src/components/FeaturePanel/Climbing/ClimbingPanel.tsx b/src/components/FeaturePanel/Climbing/ClimbingPanel.tsx index 303bc858..263bee6a 100644 --- a/src/components/FeaturePanel/Climbing/ClimbingPanel.tsx +++ b/src/components/FeaturePanel/Climbing/ClimbingPanel.tsx @@ -1,100 +1,45 @@ import React from 'react'; -import styled from 'styled-components'; -import Router from 'next/router'; -import ZoomInIcon from '@mui/icons-material/ZoomIn'; -import { Button } from '@mui/material'; import { useClimbingContext } from './contexts/ClimbingContext'; -import { PanelScrollbars, PanelWrapper } from '../../utils/PanelHelpers'; import { RouteList } from './RouteList/RouteList'; import { useFeatureContext } from '../../utils/FeatureContext'; -import { getLabel } from '../../../helpers/featureLabel'; -import { getOsmappLink } from '../../../services/helpers'; -import { StarButton } from '../ImageSection/StarButton'; import { OsmError } from '../OsmError'; import { Properties } from '../Properties/Properties'; -import { PoiDescription } from '../ImageSection/PoiDescription'; import { ImageSlider } from '../ImagePane/ImageSlider'; -import { ClimbingParentLink } from '../ParentLink'; import { RouteDistribution } from './RouteDistribution'; -import { YellowedBadge } from './YellowedBadge'; import { getWikimediaCommonsKeys, removeFilePrefix } from './utils/photo'; import { SuggestEdit } from '../SuggestEdit'; - -const HeadingContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const DetailButtonContainer = styled.div` - margin: 8px; - @supports (-webkit-touch-callout: none) { - /* CSS specific to iOS devices */ - margin-bottom: 28px; - } -`; - -const Heading = styled.div` - margin: 12px 8px 4px; - font-size: 36px; - line-height: 0.98; -`; +import { PanelContent, PanelSidePadding } from '../../utils/PanelHelpers'; +import { FeatureHeading } from '../FeatureHeading'; +import { ParentLink } from '../ParentLink'; export const ClimbingPanel = ({ footer, showTagsTable }) => { const { feature } = useFeatureContext(); - const label = getLabel(feature); const { preparePhotosAndSet } = useClimbingContext(); - const onFullScreenClick = () => { - Router.push(`${getOsmappLink(feature)}/climbing${window.location.hash}`); - }; const cragPhotos = getWikimediaCommonsKeys(feature.tags) .map((key) => feature.tags[key]) .map(removeFilePrefix); preparePhotosAndSet(cragPhotos); return ( - <> - - - - - - {label} - - - - - - - - - - - - - - - - - - {/* @TODO unite with parent panel */} - {footer} - - - } - onClick={onFullScreenClick} - fullWidth - variant="contained" - > - Show crag detail - - - - > + + + + + + + + + + + + + + + + {/* @TODO unite with parent panel */} + {footer} + ); }; diff --git a/src/components/FeaturePanel/Climbing/ClimbingView.tsx b/src/components/FeaturePanel/Climbing/ClimbingView.tsx index 7138a0ca..43d1bc47 100644 --- a/src/components/FeaturePanel/Climbing/ClimbingView.tsx +++ b/src/components/FeaturePanel/Climbing/ClimbingView.tsx @@ -116,8 +116,8 @@ const ArrowExpanderButton = styled.div<{ $arrowOnTop?: boolean }>` `; const BlurContainer = styled.div<{ $isVisible: boolean }>` - -webkit-backdrop-filter: blur(15px); backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); background-color: rgba(0, 0, 0, 0.6); visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')}; height: 100%; diff --git a/src/components/FeaturePanel/FeatureHeading.tsx b/src/components/FeaturePanel/FeatureHeading.tsx index 84ec59c6..d0c764d6 100644 --- a/src/components/FeaturePanel/FeatureHeading.tsx +++ b/src/components/FeaturePanel/FeatureHeading.tsx @@ -1,27 +1,77 @@ import React from 'react'; import styled from 'styled-components'; -import { EditIconButton } from './helpers/EditIconButton'; +import EditIcon from '@mui/icons-material/Edit'; +import { IconButton } from '@mui/material'; import { useEditDialogContext } from './helpers/EditDialogContext'; +import { PoiDescription } from './ImageSection/PoiDescription'; +import { StarButton } from './ImageSection/StarButton'; +import { getLabel } from '../../helpers/featureLabel'; +import { useFeatureContext } from '../utils/FeatureContext'; +import { t } from '../../services/intl'; -const Wrapper = styled.div<{ $deleted: boolean }>` - font-size: 36px; - line-height: 0.98; +const StyledEditButton = styled(IconButton)` + visibility: hidden; position: relative; - padding-bottom: 30px; - ${({ $deleted }) => $deleted && 'text-decoration: line-through;'} + top: 3px; +`; +const NameWithEdit = styled.div` + display: flex; + gap: 8px; +`; +const Container = styled.div` + margin: 12px 0 20px 0; +`; + +const HeadingContainer = styled.div` + margin: 6px 0 4px 0; - &:hover .show-on-hover { - display: block !important; + display: flex; + justify-content: space-between; + align-items: flex-start; + position: relative; + + &:hover ${StyledEditButton} { + visibility: visible; } `; -export const FeatureHeading = ({ title, deleted, editEnabled }) => { +const Heading = styled.h1<{ $deleted: boolean }>` + font-size: 36px; + line-height: 0.98; + margin: 0; + ${({ $deleted }) => $deleted && 'text-decoration: line-through;'} +`; + +export const FeatureHeading = () => { const { openWithTag } = useEditDialogContext(); + const { feature } = useFeatureContext(); + const label = getLabel(feature); + const { skeleton, deleted } = feature; + const editEnabled = !skeleton; return ( - - {editEnabled && openWithTag('name')} />} - {title} - + + + + + {label} + {/* */} + {editEnabled && ( + + openWithTag('name')} + size="small" + > + + + + )} + + + + ); }; diff --git a/src/components/FeaturePanel/FeaturePanel.tsx b/src/components/FeaturePanel/FeaturePanel.tsx index f7d2b29c..83173c4d 100644 --- a/src/components/FeaturePanel/FeaturePanel.tsx +++ b/src/components/FeaturePanel/FeaturePanel.tsx @@ -1,140 +1,14 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; -import { FeatureHeading } from './FeatureHeading'; -import Coordinates from './Coordinates'; -import { useToggleState } from '../helpers'; -import { getFullOsmappLink, getKey } from '../../services/helpers'; -import { - PanelContent, - PanelFooter, - PanelScrollbars, - PanelSidePadding, - PanelWrapper, -} from '../utils/PanelHelpers'; -import { useFeatureContext } from '../utils/FeatureContext'; -import { t } from '../../services/intl'; -import { FeatureDescription } from './FeatureDescription'; -import { ObjectsAround } from './ObjectsAround'; -import { OsmError } from './OsmError'; -import { Members } from './Members'; -import { FeatureOpenPlaceGuideLink } from './FeatureOpenPlaceGuideLink'; -import { getLabel } from '../../helpers/featureLabel'; -import { ImageSection } from './ImageSection/ImageSection'; -import { PublicTransport } from './PublicTransport/PublicTransport'; -import { Properties } from './Properties/Properties'; -import { MemberFeatures } from './MemberFeatures'; -import { ClimbingPanel } from './Climbing/ClimbingPanel'; -import { ClimbingContextProvider } from './Climbing/contexts/ClimbingContext'; -import { isClimbingRelation } from '../../services/osmApi'; -import { ParentLink } from './ParentLink'; -import { ImageSlider } from './ImagePane/ImageSlider'; -import { SuggestEdit } from './SuggestEdit'; +import React from 'react'; -const Flex = styled.div` - flex: 1; -`; +import { PanelScrollbars, PanelWrapper } from '../utils/PanelHelpers'; +import { FeaturePanelInner } from './FeaturePanelInner'; -export const FeaturePanel = () => { - const { feature } = useFeatureContext(); - - const [advanced, setAdvanced] = useState(false); - const [showAround, toggleShowAround] = useToggleState(false); - const [showTags, toggleShowTags] = useToggleState(false); - - const { point, tags, skeleton, deleted } = feature; - const editEnabled = !skeleton; - const showTagsTable = deleted || showTags || (!skeleton && !feature.schema); - - const osmappLink = getFullOsmappLink(feature); - const label = getLabel(feature); - - const footer = ( - - - - - {osmappLink} - - - {' '} - {t('featurepanel.show_tags')} - {' '} - - {' '} - {t('featurepanel.show_objects_around')} - - {!point && showAround && } - - ); - - if (!feature) { - return null; - } - - if ( - isClimbingRelation(feature) && // only for this condition is memberFeatures fetched - feature.tags.climbing === 'crag' && - !advanced - ) { - return ( - - - - ); - } - - return ( +export const FeaturePanel = () => ( + <> - - - - - - - - - - - {!skeleton && ( - <> - - - - - - - {advanced && } - - - - - - {editEnabled && } - - {point && } - - > - )} - - - {footer} - + - ); -}; + > +); diff --git a/src/components/FeaturePanel/FeaturePanelInDrawer.tsx b/src/components/FeaturePanel/FeaturePanelInDrawer.tsx new file mode 100644 index 00000000..25ba29b1 --- /dev/null +++ b/src/components/FeaturePanel/FeaturePanelInDrawer.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; + +import { SwipeableDrawer } from '@mui/material'; +import styled, { createGlobalStyle } from 'styled-components'; +import { FeaturePanelInner } from './FeaturePanelInner'; +import { Puller } from './helpers/Puller'; + +const DRAWER_PREVIEW_HEIGHT = 86; +const DRAWER_TOP_OFFSET = 8; +const DRAWER_CLASSNAME = 'featurePanelInDrawer'; + +const GlobalStyleForDrawer = createGlobalStyle` + .${DRAWER_CLASSNAME}.MuiDrawer-root > .MuiPaper-root { + height: calc(100% - ${DRAWER_PREVIEW_HEIGHT}px - ${DRAWER_TOP_OFFSET}px); + overflow: visible; + } +`; + +const Container = styled.div` + position: relative; + background: ${({ theme }) => theme.palette.background.paper}; + margin-top: -86px; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + visibility: visible; + right: 0; + left: 0; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); + height: calc(100% + ${DRAWER_PREVIEW_HEIGHT}px + ${DRAWER_TOP_OFFSET}px); +`; + +const ListContainer = styled.div` + max-height: calc(100% - ${DRAWER_TOP_OFFSET}px); + height: 100%; + overflow: auto; +`; + +export const FeaturePanelInDrawer = () => { + const [open, setOpen] = useState(false); + + const handleOnOpen = () => setOpen(true); + const handleOnClose = () => setOpen(false); + + return ( + <> + + + + + + + + + + > + ); +}; diff --git a/src/components/FeaturePanel/FeaturePanelInner.tsx b/src/components/FeaturePanel/FeaturePanelInner.tsx new file mode 100644 index 00000000..80bef9f7 --- /dev/null +++ b/src/components/FeaturePanel/FeaturePanelInner.tsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Box } from '@mui/material'; +import { FeatureHeading } from './FeatureHeading'; +import Coordinates from './Coordinates'; +import { useToggleState } from '../helpers'; +import { getFullOsmappLink, getKey } from '../../services/helpers'; +import { + PanelContent, + PanelFooter, + PanelSidePadding, +} from '../utils/PanelHelpers'; +import { useFeatureContext } from '../utils/FeatureContext'; +import { t } from '../../services/intl'; +import { FeatureDescription } from './FeatureDescription'; +import { ObjectsAround } from './ObjectsAround'; +import { OsmError } from './OsmError'; +import { Members } from './Members'; +import { getLabel } from '../../helpers/featureLabel'; +import { ImageSection } from './ImageSection/ImageSection'; +import { PublicTransport } from './PublicTransport/PublicTransport'; +import { Properties } from './Properties/Properties'; +import { MemberFeatures } from './MemberFeatures'; +import { ClimbingPanel } from './Climbing/ClimbingPanel'; +import { ClimbingContextProvider } from './Climbing/contexts/ClimbingContext'; +import { isClimbingRelation } from '../../services/osmApi'; +import { ParentLink } from './ParentLink'; +import { ImageSlider } from './ImagePane/ImageSlider'; +import { SuggestEdit } from './SuggestEdit'; +import { FeatureOpenPlaceGuideLink } from './FeatureOpenPlaceGuideLink'; + +const Flex = styled.div` + flex: 1; +`; + +export const FeaturePanelInner = () => { + const { feature } = useFeatureContext(); + + const [advanced, setAdvanced] = useState(false); + const [showAround, toggleShowAround] = useToggleState(false); + const [showTags, toggleShowTags] = useToggleState(false); + + const { point, tags, skeleton, deleted } = feature; + const editEnabled = !skeleton; + const showTagsTable = deleted || showTags || (!skeleton && !feature.schema); + + const osmappLink = getFullOsmappLink(feature); + const label = getLabel(feature); + + const footer = ( + + + + + {osmappLink} + + + {' '} + {t('featurepanel.show_tags')} + {' '} + + {' '} + {t('featurepanel.show_objects_around')} + + {!point && showAround && } + + ); + + if (!feature) { + return null; + } + + if ( + isClimbingRelation(feature) && // only for this condition is memberFeatures fetched + feature.tags.climbing === 'crag' && + !advanced + ) { + return ( + + + + ); + } + + return ( + <> + + + + + + + + {tags.climbing !== 'area' && ( + + + + )} + + + {!skeleton && ( + <> + + + + + + + {advanced && } + + + + + + {editEnabled && } + + {point && } + + > + )} + + + {footer} + + > + ); +}; diff --git a/src/components/FeaturePanel/ImagePane/ImageSlider.tsx b/src/components/FeaturePanel/ImagePane/ImageSlider.tsx index 1c52013d..dac47de6 100644 --- a/src/components/FeaturePanel/ImagePane/ImageSlider.tsx +++ b/src/components/FeaturePanel/ImagePane/ImageSlider.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import Router from 'next/router'; import { useFeatureContext } from '../../utils/FeatureContext'; import { Path, PathSvg } from './Path'; @@ -12,8 +12,9 @@ import { Slider } from './Slider'; const HEIGHT = 245; const initialSize: Size = { width: 100, height: HEIGHT }; // until image size is known, the paths are rendered using this (eg. ssr) -const Img = styled.img` - border-radius: 12px; +const Img = styled.img<{ $onlyOneImage: boolean }>` + max-height: 300px; + &.hasPaths { opacity: 0.9; // let the paths be more prominent } @@ -22,26 +23,28 @@ const ImageWrapper = styled.div` position: relative; display: flex; height: 100%; - border-radius: 12px; + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; - /* overflow: hidden; */ - ${({ onClick, theme }) => + ${({ onClick }) => onClick && - ` + css` cursor: pointer; - &:hover { - box-shadow: 0 0 10px ${theme.palette.secondary.main}; - }`} + `} svg { position: absolute; height: 100%; width: 100%; - border-radius: 8px; } `; -const Image = ({ imageTag }: { imageTag: ImageTag }) => { +const Image = ({ + imageTag, + onlyOneImage, +}: { + imageTag: ImageTag; + onlyOneImage: boolean; +}) => { const [size, setSize] = React.useState(initialSize); const { feature } = useFeatureContext(); const isCrag = feature.tags.climbing === 'crag'; @@ -67,7 +70,9 @@ const Image = ({ imageTag }: { imageTag: ImageTag }) => { { export const ImageSlider = () => { const { feature } = useFeatureContext(); - // temporary - until ImageSlider is finished - if ( - !feature.imageTags?.some( - ({ path, memberPaths }) => path?.length || memberPaths?.length, - ) - ) { - return null; - } + const onlyOneImage = + (feature.imageTags?.filter((imageTag) => imageTag.imageUrl) ?? []) + .length === 1; return ( - - {feature.imageTags.map((imageTag) => ( - + + {feature.imageTags?.map((imageTag) => ( + ))} {/* Fody */} {/* Mapillary */} diff --git a/src/components/FeaturePanel/ImagePane/Slider.tsx b/src/components/FeaturePanel/ImagePane/Slider.tsx index c4262f42..7031d8f2 100644 --- a/src/components/FeaturePanel/ImagePane/Slider.tsx +++ b/src/components/FeaturePanel/ImagePane/Slider.tsx @@ -1,68 +1,36 @@ import React from 'react'; import styled from 'styled-components'; -import { useTheme } from '@mui/material'; -import { useScrollShadow } from '../Climbing/utils/useScrollShadow'; // from https://css-tricks.com/css-only-carousel/ -const Wrapper = styled.div` +const Wrapper = styled.div<{ $onlyOneImage: boolean }>` width: 100%; text-align: center; - overflow: hidden; + /* overflow: hidden; */ margin-bottom: 1rem; position: relative; .slides { display: flex; - gap: 12px; + gap: 8px; + ${({ $onlyOneImage }) => $onlyOneImage && `justify-content: center;`} align-items: center; overflow-x: auto; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; - padding-left: 16px; - padding-right: 16px; + padding: ${({ $onlyOneImage }) => ($onlyOneImage ? '12px 0' : '12px 16px')}; } .slides > div { display: flex; justify-content: center; align-items: center; - position: relative; - margin-top: 16px; - margin-bottom: 16px; } `; -export const Slider = ({ children }) => { - const theme = useTheme(); - - const { - scrollElementRef, - onScroll, - ShadowContainer, - ShadowLeft, - ShadowRight, - } = useScrollShadow(); - - return ( - - - - - - {children} - - - - - ); -}; +export const Slider = ({ children, onlyOneImage }) => ( + + {children} + +); diff --git a/src/components/FeaturePanel/ImageSection/FeatureImage.tsx b/src/components/FeaturePanel/ImageSection/FeatureImage.tsx index 0255fbfd..c83759ec 100644 --- a/src/components/FeaturePanel/ImageSection/FeatureImage.tsx +++ b/src/components/FeaturePanel/ImageSection/FeatureImage.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React from 'react'; import styled from 'styled-components'; @@ -21,33 +21,6 @@ const Wrapper = styled.div<{ $uncertainImage: boolean }>` : ''} `; -const GradientCover = styled.div` - pointer-events: none; - position: absolute; - width: 100%; - height: 100%; - top: 0; - bottom: 0; - left: 0; - right: 0; - opacity: 0.6; - background: linear-gradient( - to bottom, - rgba(0, 0, 0, 0) 70%, - rgba(0, 0, 0, 0.15) 76%, - #000000 - ); -`; - -const Bottom = styled.div` - position: absolute; - bottom: 0; - display: flex; - align-items: center; - width: 100%; - height: 44px; -`; - const IconWrapper = styled.div` padding-top: 40px; text-align: center; @@ -83,10 +56,9 @@ const AttributionLink = styled.a<{ $portrait: boolean }>` interface Props { feature: Feature; ico: string; - children: ReactNode; } -export const FeatureImage = ({ feature, ico, children }: Props) => { +export const FeatureImage = ({ feature, ico }: Props) => { const [image, setImage] = React.useState(feature.ssrFeatureImage ?? LOADING); React.useEffect(() => { @@ -116,7 +88,6 @@ export const FeatureImage = ({ feature, ico, children }: Props) => { {image && image !== LOADING && ( )} - {(image === undefined || image === LOADING) && ( @@ -135,7 +106,6 @@ export const FeatureImage = ({ feature, ico, children }: Props) => { )} {!feature.skeleton && image === LOADING && } - {children} ); }; diff --git a/src/components/FeaturePanel/ImageSection/ImageSection.tsx b/src/components/FeaturePanel/ImageSection/ImageSection.tsx index 7ec73ee4..a99348ee 100644 --- a/src/components/FeaturePanel/ImageSection/ImageSection.tsx +++ b/src/components/FeaturePanel/ImageSection/ImageSection.tsx @@ -1,45 +1,10 @@ -import Share from '@mui/icons-material/Share'; -import Directions from '@mui/icons-material/Directions'; import React from 'react'; import { useFeatureContext } from '../../utils/FeatureContext'; import { FeatureImage } from './FeatureImage'; -import { t } from '../../../services/intl'; -import { SHOW_PROTOTYPE_UI } from '../../../config'; -import { PoiDescriptionDark } from './PoiDescription'; -import { StarButtonDark } from './StarButton'; -import { StyledActionButton } from './utils'; export const ImageSection = () => { const { feature } = useFeatureContext(); const { properties } = feature; - return ( - - - - {SHOW_PROTOTYPE_UI && ( - <> - - - - > - )} - - - - {SHOW_PROTOTYPE_UI && ( - <> - - - - > - )} - - ); + return ; }; diff --git a/src/components/FeaturePanel/ImageSection/PoiDescription.tsx b/src/components/FeaturePanel/ImageSection/PoiDescription.tsx index 331f69af..8a64bd80 100644 --- a/src/components/FeaturePanel/ImageSection/PoiDescription.tsx +++ b/src/components/FeaturePanel/ImageSection/PoiDescription.tsx @@ -5,16 +5,16 @@ import { useFeatureContext } from '../../utils/FeatureContext'; import Maki from '../../utils/Maki'; import { useUserThemeContext } from '../../../helpers/theme'; -const PoiType = styled.div<{ $isSkeleton: Boolean; $forceWhite?: Boolean }>` - color: ${({ $forceWhite, theme }) => - $forceWhite ? '#fff' : theme.palette.secondary.main}; - margin: 0 auto 0 15px; +const PoiType = styled.div<{ $isSkeleton: Boolean }>` + color: ${({ theme }) => theme.palette.secondary.main}; + font-size: 13px; position: relative; - ${({ $forceWhite }) => $forceWhite && 'flex: 1;'} - svg { - vertical-align: bottom; + img { + position: relative; + top: -1px; + left: 1px; } span { @@ -22,19 +22,6 @@ const PoiType = styled.div<{ $isSkeleton: Boolean; $forceWhite?: Boolean }>` } `; -export const PoiDescriptionDark = () => { - const { feature } = useFeatureContext(); - const { properties } = feature; - const poiType = getHumanPoiType(feature); - - return ( - - - {poiType} - - ); -}; - export const PoiDescription = () => { const { currentTheme } = useUserThemeContext(); const { feature } = useFeatureContext(); diff --git a/src/components/FeaturePanel/MemberFeatures.tsx b/src/components/FeaturePanel/MemberFeatures.tsx index abb1494e..32c186ee 100644 --- a/src/components/FeaturePanel/MemberFeatures.tsx +++ b/src/components/FeaturePanel/MemberFeatures.tsx @@ -93,8 +93,7 @@ const Item = ({ feature }: { feature: Feature }) => { setPreview(null); Router.push(`/${getUrlOsmId(osmMeta)}${window.location.hash}`); }; - const handleHover = () => - feature.center && setPreview({ ...feature, noPreviewButton: true }); + const handleHover = () => feature.center && setPreview(feature); return ( @@ -128,8 +127,7 @@ const CragItem = ({ feature }: { feature: Feature }) => { setPreview(null); Router.push(`/${getUrlOsmId(osmMeta)}${window.location.hash}`); }; - const handleHover = () => - feature.center && setPreview({ ...feature, noPreviewButton: true }); + const handleHover = () => feature.center && setPreview(feature); const cragPhotoKeys = getWikimediaCommonsKeys(feature.tags); @@ -174,7 +172,7 @@ const CragItem = ({ feature }: { feature: Feature }) => { {cragPhotoKeys.map((cragPhotoTag) => { const photoPath = feature.tags[cragPhotoTag]; - const url = getCommonsImageUrl(photoPath, 400); + const url = getCommonsImageUrl(photoPath, 410); return ; })} diff --git a/src/components/FeaturePanel/ObjectsAround.tsx b/src/components/FeaturePanel/ObjectsAround.tsx index 8b35a36f..387b4c06 100644 --- a/src/components/FeaturePanel/ObjectsAround.tsx +++ b/src/components/FeaturePanel/ObjectsAround.tsx @@ -41,8 +41,7 @@ const AroundItem = ({ feature }: { feature: Feature }) => { setPreview(null); Router.push(`/${getUrlOsmId(osmMeta)}${window.location.hash}`); }; - const handleHover = () => - feature.center && setPreview({ ...feature, noPreviewButton: true }); + const handleHover = () => feature.center && setPreview(feature); return ( diff --git a/src/components/FeaturePanel/ParentLink.tsx b/src/components/FeaturePanel/ParentLink.tsx index cb530323..d955c0c1 100644 --- a/src/components/FeaturePanel/ParentLink.tsx +++ b/src/components/FeaturePanel/ParentLink.tsx @@ -19,18 +19,18 @@ const Row = styled.div` const IconWrapper = styled.div` position: relative; - top: 1px; + top: 2px; `; -const ClimbingParentItem = styled.div` - margin: 12px 8px -4px 8px; +const ParentItem = styled.div` + margin: 12px 0 4px 0; a { color: ${({ theme }) => theme.palette.secondary.main}; font-size: 13px; display: block; + text-decoration: none; &:hover { - text-decoration: none; color: ${({ theme }) => theme.palette.text.secondary}; } &:hover svg { @@ -40,16 +40,6 @@ const ClimbingParentItem = styled.div` } `; -const ParentItem = styled.div` - margin: 12px 0 4px 0; - - a { - color: ${({ theme }) => theme.palette.secondary.main}; - font-size: 13px; - display: block; - } -`; - export const Arrow = ({ children }) => ( @@ -86,14 +76,15 @@ export const ParentLinkContent = () => { ); }; -export const ParentLink = () => ( - - - -); +export const ParentLink = () => { + const { feature } = useFeatureContext(); + const hasParentLink = feature.parentFeatures?.length; -export const ClimbingParentLink = () => ( - - - -); + if (!hasParentLink) return null; + + return ( + + + + ); +}; diff --git a/src/components/FeaturePanel/helpers/Puller.tsx b/src/components/FeaturePanel/helpers/Puller.tsx new file mode 100644 index 00000000..36adaeb7 --- /dev/null +++ b/src/components/FeaturePanel/helpers/Puller.tsx @@ -0,0 +1,68 @@ +import styled, { css } from 'styled-components'; +import { grey } from '@mui/material/colors'; +import React from 'react'; +import { isMobileDevice } from '../../helpers'; + +const HANDLE_WIDTH = 30; +const HANDLE_HIP_SLOP = 10; + +const Handle = styled.div` + width: ${HANDLE_WIDTH}px; + height: 6px; + margin: auto; + background-color: ${({ theme }) => + theme.palette.mode === 'light' ? grey[300] : grey[900]}; + border-radius: 3px; + -webkit-tap-highlight-color: transparent; +`; + +const PullerContainer = styled.div<{ + $isDesktop: boolean; + $isClosed: boolean; +}>` + position: absolute; + top: 0; + left: calc(50% - ${HANDLE_WIDTH / 2}px - ${HANDLE_HIP_SLOP}px); + z-index: 1; + padding: ${HANDLE_HIP_SLOP}px; + + ${({ $isDesktop, $isClosed }) => + $isDesktop && + css` + cursor: pointer; + + &:hover ${Handle} { + opacity: 0.5; + } + + ${$isClosed && + css` + left: 0; + width: 100%; + height: 100%; + pointer-events: all; + `} + `} +`; + +type Props = { + setOpen: React.Dispatch>; + open: boolean; +}; + +export const Puller = ({ setOpen, open }: Props) => { + const isDesktop = !isMobileDevice(); + const toggleDrawer = () => { + setOpen((value) => !value); + }; + + return ( + + + + ); +}; diff --git a/src/components/FeaturePreview/FeaturePreview.tsx b/src/components/FeaturePreview/FeaturePreview.tsx deleted file mode 100644 index da28ebd7..00000000 --- a/src/components/FeaturePreview/FeaturePreview.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { Button } from '@mui/material'; -import Router from 'next/router'; -import { useFeatureContext } from '../utils/FeatureContext'; -import { ClosePanelButton } from '../utils/ClosePanelButton'; -import { getOsmappLink } from '../../services/helpers'; -import Maki from '../utils/Maki'; -import { getLabel } from '../../helpers/featureLabel'; - -const Wrapper = styled.div` - position: absolute; - z-index: 1200; // 1300 is mui-dialog - bottom: 40px; - left: 45%; - max-width: 45vw; - - .MuiButtonBase-root { - text-transform: none; - } -`; - -export const FeaturePreview = () => { - const { preview, setPreview, setFeature } = useFeatureContext(); - - if (!preview || preview.noPreviewButton) { - return null; - } - - const handleClick = () => { - setPreview(null); - setFeature({ ...preview, skeleton: true }); // skeleton needed so map doesnt move (Router will create new coordsFeature) - Router.push(`${getOsmappLink(preview)}${window.location.hash}`); // this will create brand new coordsFeature() - }; - - const onClose = () => { - setPreview(null); - }; - - return ( - - } - > - {getLabel(preview)} - - - - ); -}; diff --git a/src/components/HomepagePanel/HomepagePanel.tsx b/src/components/HomepagePanel/HomepagePanel.tsx index 1906fbad..3415109c 100644 --- a/src/components/HomepagePanel/HomepagePanel.tsx +++ b/src/components/HomepagePanel/HomepagePanel.tsx @@ -22,9 +22,10 @@ import { PROJECT_NAME, } from '../../services/project'; import { useMobileMode } from '../helpers'; +import { SEARCH_BOX_HEIGHT } from '../SearchBox/consts'; export const Content = styled.div` - height: calc(100vh - 72px); // 100% - TopPanel - FeatureImage + height: calc(100% - ${SEARCH_BOX_HEIGHT}px); padding: 20px 2em 0 2em; a.maptiler { @@ -83,14 +84,14 @@ const ExamplesClimbing = () => ( ); export const HomepagePanel = () => { - const { feature, preview, homepageShown, hideHomepage, persistHideHomepage } = + const { feature, homepageShown, hideHomepage, persistHideHomepage } = useFeatureContext(); const isMobileMode = useMobileMode(); - // hide after first shown feature or preview + // hide after first shown feature useEffect(() => { - if (feature || preview) hideHomepage(); - }, [feature, preview]); + if (feature) hideHomepage(); + }, [feature]); if (!homepageShown) { return null; diff --git a/src/components/LayerSwitcher/LayerSwitcherButton.tsx b/src/components/LayerSwitcher/LayerSwitcherButton.tsx index 6839349c..6e8f7d96 100644 --- a/src/components/LayerSwitcher/LayerSwitcherButton.tsx +++ b/src/components/LayerSwitcher/LayerSwitcherButton.tsx @@ -1,17 +1,31 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import LayersIcon from './LayersIcon'; import { t } from '../../services/intl'; +import { useMobileMode } from '../helpers'; +import { convertHexToRgba } from '../utils/colorUtils'; -const StyledLayerSwitcher = styled.button` +const StyledLayerSwitcher = styled.button<{ isMobileMode: boolean }>` margin: 0; padding: 0; - width: 52px; - height: 69px; - border-radius: 5px; + ${({ isMobileMode }) => + isMobileMode + ? css` + width: 44px; + height: 44px; + border-radius: 50%; + ` + : css` + width: 52px; + height: 69px; + border-radius: 5px; + `} + border: 0; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); - background-color: ${({ theme }) => theme.palette.background.paper}; + background-color: ${({ theme }) => + convertHexToRgba(theme.palette.background.paper, 0.7)}; + backdrop-filter: blur(15px); font-size: 12px; color: ${({ theme }) => theme.palette.text.primary}; outline: 0; @@ -19,7 +33,8 @@ const StyledLayerSwitcher = styled.button` &:hover, &:focus { - background-color: ${({ theme }) => theme.palette.background.hover}; + background-color: ${({ theme }) => + convertHexToRgba(theme.palette.background.paper, 0.75)}; } svg { @@ -27,9 +42,12 @@ const StyledLayerSwitcher = styled.button` } `; -export const LayerSwitcherButton = ({ onClick }: { onClick?: any }) => ( - - - {t('layerswitcher.button')} - -); +export const LayerSwitcherButton = ({ onClick }: { onClick?: any }) => { + const isMobileMode = useMobileMode(); + return ( + + + {!isMobileMode && t('layerswitcher.button')} + + ); +}; diff --git a/src/components/LayerSwitcher/LayersIcon.tsx b/src/components/LayerSwitcher/LayersIcon.tsx index bed2b257..92a2d921 100644 --- a/src/components/LayerSwitcher/LayersIcon.tsx +++ b/src/components/LayerSwitcher/LayersIcon.tsx @@ -2,7 +2,7 @@ import React from 'react'; const SvgComponent = (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + diff --git a/src/components/Map/BrowserMap.tsx b/src/components/Map/BrowserMap.tsx index 8274df98..9ce5a527 100644 --- a/src/components/Map/BrowserMap.tsx +++ b/src/components/Map/BrowserMap.tsx @@ -45,11 +45,11 @@ const BrowserMap = ({ onMapLoaded }) => { } const mobileMode = useMobileMode(); - const { setFeature, setPreview } = useFeatureContext(); + const { setFeature } = useFeatureContext(); const [map, mapRef] = useInitMap(); useAddTopRightControls(map, mobileMode); - useOnMapClicked(map, setFeature, setPreview, mobileMode); - useOnMapLongPressed(map, setPreview); + useOnMapClicked(map, setFeature); + useOnMapLongPressed(map, setFeature); useOnMapLoaded(map, onMapLoaded); useFeatureMarker(map); diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx index b4cbef71..a5345146 100644 --- a/src/components/Map/Map.tsx +++ b/src/components/Map/Map.tsx @@ -36,7 +36,7 @@ const TopRight = styled.div` z-index: 1000; padding: 10px; right: 0; - top: 72px; + top: 62px; @media ${isDesktop} { top: 0; diff --git a/src/components/Map/MapFooter/MapFooter.tsx b/src/components/Map/MapFooter/MapFooter.tsx index d595511a..53a43042 100644 --- a/src/components/Map/MapFooter/MapFooter.tsx +++ b/src/components/Map/MapFooter/MapFooter.tsx @@ -7,7 +7,8 @@ import { ClimbingLegend } from './ClimbingLegend'; import { convertHexToRgba } from '../../utils/colorUtils'; import { AttributionLinks } from './AttributionLinks'; import { useMapStateContext } from '../../utils/MapStateContext'; -import { useIsClient } from '../../helpers'; +import { useIsClient, useMobileMode } from '../../helpers'; +import { useFeatureContext } from '../../utils/FeatureContext'; const IconContainer = styled.div` width: 20px; @@ -27,8 +28,8 @@ const FooterContainer = styled.div<{ $hasShadow: boolean }>` color: ${({ theme }) => theme.palette.text.primary}; background-color: ${({ theme }) => convertHexToRgba(theme.palette.background.paper, 0.5)}; - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); ${({ $hasShadow }) => $hasShadow ? 'box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);' : ''} `; @@ -70,7 +71,10 @@ const LegendExpandButton = ({ isVisible, setLegendShown }) => ( export const MapFooter = () => { const { activeLayers } = useMapStateContext(); + const isMobileMode = useMobileMode(); + const { featureShown } = useFeatureContext(); const hasClimbingLayer = activeLayers.includes('climbing'); + const hasLegend = isMobileMode && featureShown ? false : hasClimbingLayer; const [legendShown, setLegendShown] = usePersistedState( 'isLegendVisible', true, @@ -83,8 +87,8 @@ export const MapFooter = () => { } return ( - - {hasClimbingLayer && ( + + {hasLegend && ( { )} - {hasClimbingLayer && ( + {hasLegend && ( ( - - - - -); +export const TopMenu = () => { + const isMobileMode = useMobileMode(); + + if (isMobileMode) return null; + + return ( + + + + + ); +}; diff --git a/src/components/Map/behaviour/useOnMapClicked.tsx b/src/components/Map/behaviour/useOnMapClicked.tsx index b04112fc..61c7e6a9 100644 --- a/src/components/Map/behaviour/useOnMapClicked.tsx +++ b/src/components/Map/behaviour/useOnMapClicked.tsx @@ -29,51 +29,43 @@ export const getSkeleton = (feature, clickCoords) => { }; }; -const getCoordsPreview = (coords, zoom) => { +const coordsFeatureSetter = (coords, zoom) => { if (isMobileDevice()) { return null; // handled by useOnMapLongPressed } - return (preview) => - preview ? null : getCoordsFeature(getRoundedPosition(coords, zoom)); + return (previousFeature) => + previousFeature ? null : getCoordsFeature(getRoundedPosition(coords, zoom)); }; -export const useOnMapClicked = createMapEventHook( - (map, setFeature, setPreview, mobileMode) => ({ - eventType: 'click', - eventHandler: async ({ point }) => { - const coords = map.unproject(point).toArray(); - const features = map.queryRenderedFeatures(point); - if (!features.length) { - setPreview(getCoordsPreview(coords, map.getZoom())); - return; - } +export const useOnMapClicked = createMapEventHook((map, setFeature) => ({ + eventType: 'click', + eventHandler: async ({ point }) => { + const coords = map.unproject(point).toArray(); + const features = map.queryRenderedFeatures(point); + if (!features.length) { + setFeature(coordsFeatureSetter(coords, map.getZoom())); + return; + } - const skeleton = getSkeleton(features[0], coords); - console.log(`clicked map feature (id=${features[0].id}): `, features[0]); // eslint-disable-line no-console - publishDbgObject('last skeleton', skeleton); + const skeleton = getSkeleton(features[0], coords); + console.log(`clicked map feature (id=${features[0].id}): `, features[0]); // eslint-disable-line no-console + publishDbgObject('last skeleton', skeleton); - if (skeleton.nonOsmObject) { - setPreview(getCoordsPreview(coords, map.getZoom())); - return; - } + if (skeleton.nonOsmObject) { + setFeature(coordsFeatureSetter(coords, map.getZoom())); + return; + } - if (mobileMode) { - setPreview(skeleton); - return; - } + // router wouldnt overwrite the skeleton if same url is already loaded + setFeature((feature) => + isSameOsmId(feature, skeleton) ? feature : skeleton, + ); - // router wouldnt overwrite the skeleton if same url is already loaded - setFeature((feature) => - isSameOsmId(feature, skeleton) ? feature : skeleton, - ); - setPreview(null); - - const result = await maptilerFix(features[0], skeleton, features[0].id); - addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center); // for ways/relations we dont receive center from OSM API - addFeatureCenterToCache(getShortId(result.osmMeta), skeleton.center); - const url = `/${getUrlOsmId(result.osmMeta)}${window.location.hash}`; - Router.push(url, undefined, { locale: 'default' }); - }, - }), -); + const result = await maptilerFix(features[0], skeleton, features[0].id); + addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center); // for ways/relations we dont receive center from OSM API + addFeatureCenterToCache(getShortId(result.osmMeta), skeleton.center); + const url = `/${getUrlOsmId(result.osmMeta)}${window.location.hash}`; + Router.push(url, undefined, { locale: 'default' }); + }, +})); diff --git a/src/components/Map/behaviour/useOnMapLongPressed.tsx b/src/components/Map/behaviour/useOnMapLongPressed.tsx index 99c98650..14036cd2 100644 --- a/src/components/Map/behaviour/useOnMapLongPressed.tsx +++ b/src/components/Map/behaviour/useOnMapLongPressed.tsx @@ -64,7 +64,7 @@ const emulatedLongPressListeners = (map, eventHandler) => { // return () => map.off('contextmenu', eventHandler); // }; -export const useOnMapLongPressed = (map, setPreview) => { +export const useOnMapLongPressed = (map, setFeature) => { useEffect(() => { if (!map) { return undefined; @@ -77,9 +77,9 @@ export const useOnMapLongPressed = (map, setPreview) => { const showCoords = ({ point }) => { const coords = map.unproject(point).toArray(); const roundedPosition = getRoundedPosition(coords, map.getZoom()); - setPreview(getCoordsFeature(roundedPosition)); + setFeature(getCoordsFeature(roundedPosition)); }; return emulatedLongPressListeners(map, showCoords); - }, [map, setPreview]); + }, [map, setFeature]); }; diff --git a/src/components/SearchBox/AutocompleteInput.tsx b/src/components/SearchBox/AutocompleteInput.tsx index a855642b..661c2011 100644 --- a/src/components/SearchBox/AutocompleteInput.tsx +++ b/src/components/SearchBox/AutocompleteInput.tsx @@ -4,7 +4,6 @@ import { useFeatureContext } from '../utils/FeatureContext'; import { renderOptionFactory } from './renderOptionFactory'; import { t } from '../../services/intl'; import { onSelectedFactory } from './onSelectedFactory'; -import { useMobileMode } from '../helpers'; import { useUserThemeContext } from '../../helpers/theme'; import { useMapStateContext } from '../utils/MapStateContext'; import { onHighlightFactory } from './onHighlightFactory'; @@ -67,7 +66,6 @@ export const AutocompleteInput = ({ const { setFeature, setPreview } = useFeatureContext(); const { bbox, showToast } = useMapStateContext(); const mapCenter = useMapCenter(); - const mobileMode = useMobileMode(); const { currentTheme } = useUserThemeContext(); return ( ` position: absolute; height: ${SEARCH_BOX_HEIGHT}px; - box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.12); - background-color: ${({ theme }) => theme.palette.background.searchBox}; - padding: 10px; + ${({ $isMobileMode }) => + !$isMobileMode && + css` + box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.12); + background-color: ${({ theme }) => theme.palette.background.searchBox}; + `} + + padding: 8px; box-sizing: border-box; - z-index: 1200; + z-index: 1; width: 100%; @media ${isDesktop} { @@ -32,6 +39,9 @@ const StyledPaper = styled(Paper)` padding: 2px 4px; display: flex; align-items: center; + background-color: ${({ theme }) => theme.palette.background.searchInput}; + -webkit-backdrop-filter: blur(35px); + backdrop-filter: blur(35px); .MuiAutocomplete-root { flex: 1; @@ -53,19 +63,16 @@ const OverpassCircularProgress = styled(CircularProgress)` // https://docs.mapbox.com/help/troubleshooting/working-with-large-geojson-data/ const useOnClosePanel = () => { - const { feature, setFeature, setPreview } = useFeatureContext(); - const mobileMode = useMobileMode(); + const { setFeature } = useFeatureContext(); return () => { - if (mobileMode) { - setPreview(feature); - } setFeature(null); Router.push(`/${window.location.hash}`); }; }; const SearchBox = () => { + const isMobileMode = useMobileMode(); const { featureShown } = useFeatureContext(); const { inputValue, setInputValue } = useInputValueState(); const [options, setOptions] = useState([]); @@ -76,7 +83,7 @@ const SearchBox = () => { useOptions(inputValue, setOptions); return ( - + @@ -90,7 +97,15 @@ const SearchBox = () => { setOverpassLoading={setOverpassLoading} /> - {featureShown && } + {featureShown && !isMobileMode && ( + + )} + {isMobileMode && ( + <> + + + > + )} {overpassLoading && } diff --git a/src/components/SearchBox/consts.ts b/src/components/SearchBox/consts.ts index 9c8bb3d3..e3c2b32c 100644 --- a/src/components/SearchBox/consts.ts +++ b/src/components/SearchBox/consts.ts @@ -1 +1 @@ -export const SEARCH_BOX_HEIGHT = 72; +export const SEARCH_BOX_HEIGHT = 68; diff --git a/src/components/SearchBox/onHighlightFactory.ts b/src/components/SearchBox/onHighlightFactory.ts index c98984a5..5834a565 100644 --- a/src/components/SearchBox/onHighlightFactory.ts +++ b/src/components/SearchBox/onHighlightFactory.ts @@ -31,10 +31,11 @@ export const getSkeleton = (option) => { export const onHighlightFactory = (setPreview) => (e, option) => { if (option?.star?.center) { const { center } = option.star; - setPreview({ center, noPreviewButton: true }); + setPreview({ center }); return; } - if (!option?.geometry?.coordinates) return; - setPreview({ ...getSkeleton(option), noPreviewButton: true }); + if (option?.geometry?.coordinates) { + setPreview(getSkeleton(option)); + } }; diff --git a/src/components/SearchBox/onSelectedFactory.ts b/src/components/SearchBox/onSelectedFactory.ts index f241ad11..e9ff746d 100644 --- a/src/components/SearchBox/onSelectedFactory.ts +++ b/src/components/SearchBox/onSelectedFactory.ts @@ -45,7 +45,7 @@ const starOptionSelected = (option) => { Router.push(`/${getUrlOsmId(apiId)}`); }; -const geocoderOptionSelected = (option, mobileMode, setPreview, setFeature) => { +const geocoderOptionSelected = (option, setFeature) => { if (!option?.geometry.coordinates) return; const skeleton = getSkeleton(option); @@ -53,21 +53,15 @@ const geocoderOptionSelected = (option, mobileMode, setPreview, setFeature) => { addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center); - if (mobileMode) { - setPreview(skeleton); - fitBounds(option); - return; - } - setFeature(skeleton); fitBounds(option, true); Router.push(`/${getUrlOsmId(skeleton.osmMeta)}`); }; export const onSelectedFactory = - (setFeature, setPreview, mobileMode, bbox, showToast, setOverpassLoading) => + (setFeature, setPreview, bbox, showToast, setOverpassLoading) => (_, option) => { - setPreview(null); + setPreview(null); // it could be stuck from onHighlight if (option.star) { starOptionSelected(option); @@ -79,5 +73,5 @@ export const onSelectedFactory = return; } - geocoderOptionSelected(option, mobileMode, setPreview, setFeature); + geocoderOptionSelected(option, setFeature); }; diff --git a/src/components/helpers.tsx b/src/components/helpers.tsx index edf476be..52aecda6 100644 --- a/src/components/helpers.tsx +++ b/src/components/helpers.tsx @@ -87,12 +87,17 @@ export const dotToOptionalBr = (url = '') => export const trimText = (text, limit) => text?.length > limit ? `${text?.substring(0, limit)}…` : text; -// (<= tablet size) MobileMode shows preview instead of panel -export const useMobileMode = () => useMediaQuery('(max-width: 700px)'); +// (<= tablet size) MobileMode shows FeaturePanel in Drawer (instead of side) +export const isMobileMode = '(max-width: 700px)'; +export const useMobileMode = () => useMediaQuery(isMobileMode); -// (>= mobile size) This changes just the app layout +// (>= mobile size) SearchBox stops growing export const isDesktop = '(min-width: 500px)'; +// TODO refactor breakpoints later +export const isTabletResolution = '(min-width: 501px)'; +export const isDesktopResolution = '(min-width: 701px)'; + // is mobile device - specific behaviour like longpress or geouri export const isMobileDevice = () => isBrowser() && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); // TODO this can be isomorphic ? otherwise we have hydration error @@ -113,6 +118,7 @@ export const DotLoader = () => ( > ); +// TODO import { NoSsr } from '@mui/base'; export const ClientOnly = ({ children }) => { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); diff --git a/src/components/utils/PanelHelpers.tsx b/src/components/utils/PanelHelpers.tsx index 3d4cf7d3..98b89268 100644 --- a/src/components/utils/PanelHelpers.tsx +++ b/src/components/utils/PanelHelpers.tsx @@ -12,7 +12,7 @@ import { SEARCH_BOX_HEIGHT } from '../SearchBox/consts'; export const PanelWrapper = styled.div` position: absolute; left: 0; - top: 72px; // TopPanel + top: ${SEARCH_BOX_HEIGHT}px; bottom: 0; background: ${({ theme }) => theme.palette.background.paper}; color: ${({ theme }) => theme.palette.text.primary}; @@ -68,10 +68,7 @@ export const PanelScrollbars = ({ children }) => { export const PanelContent = styled.div` display: flex; flex-direction: column; - height: calc( - 100vh - ${SEARCH_BOX_HEIGHT}px - 238px - ); // 100% - TopPanel - FeatureImage - padding: 20px 0 0 0; + height: 100%; `; export const PanelFooter = styled.div` @@ -83,5 +80,5 @@ export const PanelFooter = styled.div` `; export const PanelSidePadding = styled.div` - padding: 0 15px; + padding: 0 12px; `; diff --git a/src/helpers/GlobalStyle.tsx b/src/helpers/GlobalStyle.tsx index 02f32d4b..629fd5be 100644 --- a/src/helpers/GlobalStyle.tsx +++ b/src/helpers/GlobalStyle.tsx @@ -1,5 +1,10 @@ import { createGlobalStyle } from 'styled-components'; -import { isDesktop } from '../components/helpers'; +import { + isDesktopResolution, + isMobileMode, + isTabletResolution, +} from '../components/helpers'; +import { convertHexToRgba } from '../components/utils/colorUtils'; export const GlobalStyle = createGlobalStyle` html, body, #__next { @@ -54,23 +59,39 @@ export const GlobalStyle = createGlobalStyle` } .maplibregl-ctrl-group { - background: ${({ theme }) => theme.palette.background.paper} !important; + background-color: ${({ theme }) => + convertHexToRgba(theme.palette.background.paper, 0.7)} !important; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); .maplibregl-ctrl-icon { filter: ${({ theme }) => theme.palette.invertFilter}; } + @media ${isMobileMode} { + border-radius: 50%; + + button { + width: 44px; + height: 44px; + } + } button + button { border-top: 1px solid ${({ theme }) => theme.palette.divider}; } } .maplibregl-ctrl-top-right { - top: ${83 + 72}px !important; + top: 114px !important; + + @media ${isTabletResolution} { + top: 54px !important; + } - @media ${isDesktop} { + @media ${isDesktopResolution} { top: 83px !important; } + } .maplibregl-canvas:not(:focus) { @@ -99,8 +120,9 @@ export const GlobalStyle = createGlobalStyle` background-color: rgba(0, 0, 0, 0.2) !important; } - /* Hide compass by default */ -.hidden-compass .maplibregl-ctrl-compass { - display: none; -} + /* Hide compass by default - selects .maplibregl-ctrl which holds [+,-,compass] */ + .hidden-compass .maplibregl-ctrl:has(> .maplibregl-ctrl-compass) { + display: none; + } + `; diff --git a/src/helpers/theme.tsx b/src/helpers/theme.tsx index be66d4f4..392274ae 100644 --- a/src/helpers/theme.tsx +++ b/src/helpers/theme.tsx @@ -27,6 +27,7 @@ const lightTheme = createTheme({ paper: '#fafafa', hover: '#f2f3f2', searchBox: '#eb5757', + searchInput: 'rgba(255,255,255,0.7)', }, invertFilter: 'invert(0)', climbing: { @@ -66,6 +67,7 @@ const darkTheme = createTheme({ paper: '#424242', hover: grey['700'], searchBox: '#963838', + searchInput: 'rgba(0,0,0,0.6)', }, invertFilter: 'invert(1)', climbing: { diff --git a/src/services/__tests__/osmToFeature.test.ts b/src/services/__tests__/osmToFeature.test.ts index c1529f8d..49078e20 100644 --- a/src/services/__tests__/osmToFeature.test.ts +++ b/src/services/__tests__/osmToFeature.test.ts @@ -40,7 +40,7 @@ const geojson = { imageTags: [ { imageUrl: - 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Lomy_nad_Velkou_-_Borová_věž.jpg/400px-Lomy_nad_Velkou_-_Borová_věž.jpg', + 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Lomy_nad_Velkou_-_Borová_věž.jpg/410px-Lomy_nad_Velkou_-_Borová_věž.jpg', k: 'wikimedia_commons', pathTag: '0.32,0.902|0.371,0.537B|0.406,0.173A', path: [ @@ -53,7 +53,7 @@ const geojson = { }, { imageUrl: - 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Lomy_nad_Velkou_-_Borová_věž3.jpg/400px-Lomy_nad_Velkou_-_Borová_věž3.jpg', + 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Lomy_nad_Velkou_-_Borová_věž3.jpg/410px-Lomy_nad_Velkou_-_Borová_věž3.jpg', k: 'wikimedia_commons:2', pathTag: '0.924,0.797|0.773,0.428B|0.562,0.056A', path: [ diff --git a/src/services/images/getImageTags.ts b/src/services/images/getImageTags.ts index 5ab05b57..500b737c 100644 --- a/src/services/images/getImageTags.ts +++ b/src/services/images/getImageTags.ts @@ -28,11 +28,11 @@ const parsePathTag = (pathString?: string): PathType | undefined => { const getImageUrl = (type: ImageTag['type'], v: string): string | null => { if (type === 'image') { - return v.match(/^File:/) ? getCommonsImageUrl(v, 400) : v; + return v.match(/^File:/) ? getCommonsImageUrl(v, 410) : v; } if (type === 'wikimedia_commons') { - return getCommonsImageUrl(v, 400); + return getCommonsImageUrl(v, 410); } return null; // API call needed diff --git a/src/services/types.ts b/src/services/types.ts index ffadff7c..67dc44e1 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -125,9 +125,6 @@ export interface Feature { state?: { hover: boolean }; skeleton?: boolean; nonOsmObject?: boolean; - - // preview - noPreviewButton?: boolean; } export type MessagesType = typeof Vocabulary; diff --git a/types.d.ts b/types.d.ts index 0c0ff079..35181281 100644 --- a/types.d.ts +++ b/types.d.ts @@ -39,6 +39,7 @@ declare module '@mui/material/styles' { elevation?: string; hover?: string; searchBox?: string; + searchInput?: string; } interface Theme {