From e81c66e321fbe0d71bf82a62b0f50e7f705094f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Zbytovsk=C3=BD?= Date: Thu, 27 Feb 2025 14:39:45 +0100 Subject: [PATCH] LayerSwitcher: add secondLine for some layers --- src/components/Directions/Result.tsx | 2 +- .../LayerSwitcher/AddCustomLayer.tsx | 15 +++++++++ src/components/LayerSwitcher/BaseLayers.tsx | 8 +++-- src/components/LayerSwitcher/Overlays.tsx | 14 +++++++-- src/components/LayerSwitcher/osmappLayers.tsx | 10 ++---- src/components/utils/MapStateContext.tsx | 8 ++--- src/components/utils/TooltipButton.tsx | 31 ++++++++----------- 7 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/components/Directions/Result.tsx b/src/components/Directions/Result.tsx index 81c68e4b..68dad0ce 100644 --- a/src/components/Directions/Result.tsx +++ b/src/components/Directions/Result.tsx @@ -79,7 +79,7 @@ const MobileResult = ({ {distance}{time} • ↑{ascent} } - color="secondary" + sx={{ color: 'secondary', fontSize: '16px' }} /> diff --git a/src/components/LayerSwitcher/AddCustomLayer.tsx b/src/components/LayerSwitcher/AddCustomLayer.tsx index 7a889811..8a005a2e 100644 --- a/src/components/LayerSwitcher/AddCustomLayer.tsx +++ b/src/components/LayerSwitcher/AddCustomLayer.tsx @@ -8,6 +8,8 @@ import { CircularProgress, TextField, Autocomplete, + Typography, + Box, } from '@mui/material'; import { Category as CategoryType, @@ -312,6 +314,19 @@ export const AddCustomDialog: React.FC = ({ + + + Layers are sourced from the{' '} + + editor-layer-index + {' '} + list. + + + { if (!newLayer) { diff --git a/src/components/LayerSwitcher/BaseLayers.tsx b/src/components/LayerSwitcher/BaseLayers.tsx index 70c67fee..af72010c 100644 --- a/src/components/LayerSwitcher/BaseLayers.tsx +++ b/src/components/LayerSwitcher/BaseLayers.tsx @@ -14,6 +14,7 @@ import { import React from 'react'; import { ListItemButton, ListItemText, Tooltip } from '@mui/material'; import WarningIcon from '@mui/icons-material/Warning'; +import { TooltipButton } from '../utils/TooltipButton'; const OutsideOfView = () => ( @@ -26,7 +27,7 @@ const getIsOutsideOfView = (bboxes: Bbox[], view: View) => const BaseLayerItem = ({ layer }: { layer: Layer }) => { const { view, activeLayers, setActiveLayers } = useMapStateContext(); - const { key, name, type, url, Icon, bboxes } = layer; + const { key, name, type, url, Icon, bboxes, secondLine } = layer; const handleClick = () => { setActiveLayers((prev) => [key, ...prev.slice(1)]); @@ -38,7 +39,10 @@ const BaseLayerItem = ({ layer }: { layer: Layer }) => { <> - + {isOutsideOfView && } {type === 'user' && } diff --git a/src/components/LayerSwitcher/Overlays.tsx b/src/components/LayerSwitcher/Overlays.tsx index f9c4e955..a8dc48fc 100644 --- a/src/components/LayerSwitcher/Overlays.tsx +++ b/src/components/LayerSwitcher/Overlays.tsx @@ -19,8 +19,12 @@ import { nl2br } from '../utils/nl2br'; const getLocalTime = (lastRefresh: string) => lastRefresh ? new Date(lastRefresh).toLocaleString(intl.lang) : null; +const HOST = process.env.NEXT_PUBLIC_CLIMBING_TILES_LOCAL + ? '/' + : 'https://openclimbing.org/'; + const fetchClimbingStats = () => - fetchJson('/api/climbing-tiles/stats'); + fetchJson(`${HOST}api/climbing-tiles/stats`); const ClimbingSecondary = () => { const { data, error, isFetching } = useQuery([], () => fetchClimbingStats()); @@ -59,7 +63,10 @@ const ClimbingSecondary = () => { return ( <> {getLocalTime(osmDataTimestamp).replace(/:\d+( [APM]+)?$/, '$1')} - + ); }; @@ -77,7 +84,8 @@ const OverlayItem = ({ layer }: { layer: Layer }) => { e.stopPropagation(); }; const selected = activeLayers.includes(key); - const secondary = key === 'climbing' ? : undefined; + const secondary = + key === 'climbing' && selected ? : undefined; return ( diff --git a/src/components/LayerSwitcher/osmappLayers.tsx b/src/components/LayerSwitcher/osmappLayers.tsx index 32b729e5..08518c5f 100644 --- a/src/components/LayerSwitcher/osmappLayers.tsx +++ b/src/components/LayerSwitcher/osmappLayers.tsx @@ -45,14 +45,12 @@ const czBbox: Bbox = [ export const osmappLayers: Layers = { basic: { name: `${t('layers.basic')} Maptiler`, - description: 'maptiler.com', type: 'basemap', Icon: ExploreIcon, attribution: ['maptiler', 'osm'], }, basicOfr: { name: `${t('layers.basic')} OpenFreeMap (beta)`, - description: '', type: 'basemap', Icon: ExploreIcon, attribution: [ @@ -62,7 +60,6 @@ export const osmappLayers: Layers = { }, makinaAfrica: { name: t('layers.makina_africa'), - description: 'OpenPlaceGuide.org', type: 'basemap', Icon: ExploreIcon, attribution: [ @@ -73,15 +70,14 @@ export const osmappLayers: Layers = { }, outdoor: { name: t('layers.outdoor'), - description: 'Maptiler.com', type: 'basemap', Icon: FilterHdrIcon, attribution: ['maptiler', 'osm'], + // https://api.maptiler.com/tiles/outdoor/tiles.json?key=7dlhLl3hiXQ1gsth0kGu .planettime="1703030400000", }, s1: { type: 'spacer' }, carto: { name: t('layers.carto'), - description: 'Default OSM.org style', type: 'basemap', url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', Icon: MapIcon, @@ -117,7 +113,7 @@ export const osmappLayers: Layers = { // }, bike: { name: t('layers.bike'), - description: 'Thunderforest.com', + secondLine: 'Thunderforest.com', type: 'basemap', url: `https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}${retina}.png?apikey=${process.env.NEXT_PUBLIC_API_KEY_THUNDERFOREST}`, Icon: DirectionsBikeIcon, @@ -128,7 +124,7 @@ export const osmappLayers: Layers = { }, transport: { name: t('layers.transport'), - description: 'Thunderforest.com', + secondLine: 'Thunderforest.com', type: 'basemap', url: `https://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}${retina}.png?apikey=${process.env.NEXT_PUBLIC_API_KEY_THUNDERFOREST}`, darkUrl: `https://{s}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}${retina}.png?apikey=${process.env.NEXT_PUBLIC_API_KEY_THUNDERFOREST}`, diff --git a/src/components/utils/MapStateContext.tsx b/src/components/utils/MapStateContext.tsx index 59a9eae1..76c6c85c 100644 --- a/src/components/utils/MapStateContext.tsx +++ b/src/components/utils/MapStateContext.tsx @@ -18,19 +18,19 @@ export type LayerIcon = React.ComponentType<{ fontSize: 'small' }>; // [b.getWest(), b.getNorth(), b.getEast(), b.getSouth()] export type Bbox = [number, number, number, number]; -export interface Layer { +export type Layer = { type: 'basemap' | 'overlay' | 'user' | 'spacer'; name?: string; - description?: string; + secondLine?: string; url?: string; darkUrl?: string; // optional url for dark mode key?: string; Icon?: LayerIcon; - attribution?: string[]; // missing in spacer TODO refactor ugly + attribution?: string[]; // missing in spacer TODO refactor this ugly type maxzoom?: number; minzoom?: number; bboxes?: Bbox[]; -} +}; // [z, lat, lon] - string because we use RoundedPosition export type View = [string, string, string]; diff --git a/src/components/utils/TooltipButton.tsx b/src/components/utils/TooltipButton.tsx index 6f018132..e9a9f52f 100644 --- a/src/components/utils/TooltipButton.tsx +++ b/src/components/utils/TooltipButton.tsx @@ -1,9 +1,7 @@ import React, { useEffect, useRef } from 'react'; -import { IconButton, Tooltip } from '@mui/material'; +import { IconButton, SxProps, Theme, Tooltip } from '@mui/material'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; -import { SvgIconOwnProps } from '@mui/material/SvgIcon/SvgIcon'; import { isMobileDevice, useBoolState } from '../helpers'; -import styled from '@emotion/styled'; const useClickAwayListener = ( tooltipRef: React.MutableRefObject, @@ -29,24 +27,21 @@ const useClickAwayListener = ( }, [hide, isMobile, tooltipRef]); }; -const StyledIconButton = styled(IconButton)<{ fontSize?: number }>` - font-size: ${({ fontSize }) => (fontSize ? `${fontSize}px` : 'inherit')}; -`; - type Props = { tooltip: React.ReactNode; - onClick?: (e: React.MouseEvent) => void; - color?: SvgIconOwnProps['color']; - fontSize?: number; + sx?: SxProps; }; -export const TooltipButton = ({ tooltip, onClick, color, fontSize }: Props) => { +/** + * Button with InfoIcon, which works on both desktop and mobile. + * (Desktop onHover, Mobile onClick) + */ +export const TooltipButton = ({ tooltip, sx }: Props) => { const isMobile = isMobileDevice(); const tooltipRef = useRef(null); const [mobileTooltipShown, show, hide] = useBoolState(false); const handleClick = (e: React.MouseEvent) => { - onClick?.(e); if (isMobile) { show(); } @@ -55,10 +50,10 @@ export const TooltipButton = ({ tooltip, onClick, color, fontSize }: Props) => { useClickAwayListener(tooltipRef, hide, isMobile); - const content = ( - - - + const button = ( + + + ); // There is a bug in MUI, passing `open={undefined}` prop to Tooltip makes it uninteractive TODO check again eg 6/2025, or report @@ -70,7 +65,7 @@ export const TooltipButton = ({ tooltip, onClick, color, fontSize }: Props) => { open={mobileTooltipShown} ref={tooltipRef} > - {content} + {button} ) : ( { //open={isMobile ? mobileTooltipShown : undefined} -- broken, see above ref={tooltipRef} > - {content} + {button} ); };