diff --git a/src/components/Directions/DirectionsAutocomplete.tsx b/src/components/Directions/DirectionsAutocomplete.tsx index 81cb54367..b9687ee0f 100644 --- a/src/components/Directions/DirectionsAutocomplete.tsx +++ b/src/components/Directions/DirectionsAutocomplete.tsx @@ -1,6 +1,6 @@ import { MapClickOverride, useMapStateContext } from '../utils/MapStateContext'; import { useStarsContext } from '../utils/StarsContext'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { abortFetch } from '../../services/fetch'; import { fetchGeocoderOptions, @@ -13,14 +13,15 @@ import { Autocomplete, InputAdornment, TextField } from '@mui/material'; import { useMapCenter } from '../SearchBox/utils'; import { useUserThemeContext } from '../../helpers/theme'; import { renderOptionFactory } from '../SearchBox/renderOptionFactory'; -import PlaceIcon from '@mui/icons-material/Place'; import { Option } from '../SearchBox/types'; import { getOptionLabel } from '../SearchBox/getOptionLabel'; import { useUserSettingsContext } from '../utils/UserSettingsContext'; import { getCoordsOption } from '../SearchBox/options/coords'; import { LonLat } from '../../services/types'; import { getGlobalMap } from '../../services/mapStorage'; -import maplibregl, { LngLatLike } from 'maplibre-gl'; +import maplibregl, { LngLatLike, PointLike } from 'maplibre-gl'; +import ReactDOMServer from 'react-dom/server'; +import { AlphabeticalMarker } from './TextMarker'; const StyledTextField = styled(TextField)` input::placeholder { @@ -35,6 +36,7 @@ const DirectionsInput = ({ label, onFocus, onBlur, + pointIndex, }) => { const { InputLabelProps, InputProps, ...restParams } = params; @@ -59,8 +61,11 @@ const DirectionsInput = ({ fullWidth InputProps={{ startAdornment: ( - - + + ), }} @@ -137,14 +142,15 @@ type Props = { label: string; value: Option; setValue: (value: Option) => void; + pointIndex: number; }; -const PREVIEW_MARKER = { - color: 'salmon', - draggable: false, -}; - -export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => { +export const DirectionsAutocomplete = ({ + label, + value, + setValue, + pointIndex, +}: Props) => { const autocompleteRef = useRef(); const { inputValue, setInputValue } = useInputValueState(); const selectedOptionInputValue = useRef(null); @@ -153,12 +159,29 @@ export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => { const { userSettings } = useUserSettingsContext(); const { isImperial } = userSettings; + const ALPHABETICAL_MARKER = useMemo(() => { + let svgElement; + if (typeof window !== 'undefined' && typeof document !== 'undefined') { + svgElement = document.createElement('div'); + svgElement.innerHTML = ReactDOMServer.renderToStaticMarkup( + , + ); + } else svgElement = undefined; + + return { + color: 'salmon', + draggable: true, + element: svgElement, + offset: [0, -10] as PointLike, + }; + }, [pointIndex]); + const markerRef = useRef(); + useEffect(() => { - console.log('___', value); const map = getGlobalMap(); if (value?.type === 'coords') { - markerRef.current = new maplibregl.Marker(PREVIEW_MARKER) + markerRef.current = new maplibregl.Marker(ALPHABETICAL_MARKER) .setLngLat(value.coords.center as LngLatLike) .addTo(map); } @@ -167,6 +190,15 @@ export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => { }; }, [value]); + const onDragEnd = () => { + const lngLat = markerRef.current?.getLngLat(); + if (lngLat) { + setValue(getCoordsOption([lngLat.lng, lngLat.lat])); + } + }; + + markerRef.current?.on('dragend', onDragEnd); + const options = useOptions(inputValue); const onChange = (_: unknown, option: Option) => { @@ -224,6 +256,7 @@ export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => { label={label} onFocus={onInputFocus} onBlur={handleBlur} + pointIndex={pointIndex} /> )} renderOption={renderOptionFactory( diff --git a/src/components/Directions/DirectionsForm.tsx b/src/components/Directions/DirectionsForm.tsx index 3d3249ee7..b95a53965 100644 --- a/src/components/Directions/DirectionsForm.tsx +++ b/src/components/Directions/DirectionsForm.tsx @@ -165,11 +165,13 @@ export const DirectionsForm = ({ setResult, hideForm }: Props) => { value={from} setValue={setFrom} label={t('directions.form.start_or_click')} + pointIndex={0} /> diff --git a/src/components/Directions/Instructions.tsx b/src/components/Directions/Instructions.tsx index 17f123287..b90679813 100644 --- a/src/components/Directions/Instructions.tsx +++ b/src/components/Directions/Instructions.tsx @@ -3,7 +3,8 @@ import { icon, Sign } from './routing/instructions'; import { RoutingResult } from './routing/types'; import { useUserSettingsContext } from '../utils/UserSettingsContext'; import { toHumanDistance } from './helpers'; -import { Stack, Typography } from '@mui/material'; +import { Box, Divider, Grid2, Stack, Typography } from '@mui/material'; +import { useTheme } from '@emotion/react'; type Instruction = RoutingResult['instructions'][number]; @@ -25,27 +26,37 @@ const StyledListItem = styled.li` gap: 0.25rem; `; -const Instruction = ({ instruction }: { instruction: Instruction }) => ( - - - - {instruction.street_name || instruction.text} - - {instruction.distance > 0 && ( - - - +const Instruction = ({ instruction }: { instruction: Instruction }) => { + const theme = useTheme(); + + return ( + + + + + + + {instruction.street_name || instruction.text} -
- )} -
-); + {instruction.distance > 0 && ( + + + + + + + + + )} +
+ ); +}; const StyledList = styled.ul` list-style: none; diff --git a/src/components/Directions/Result.tsx b/src/components/Directions/Result.tsx index aa007b34a..f30c333ed 100644 --- a/src/components/Directions/Result.tsx +++ b/src/components/Directions/Result.tsx @@ -1,5 +1,5 @@ import { useMobileMode } from '../helpers'; -import { Button, Paper, Stack, Typography } from '@mui/material'; +import { Button, Divider, Paper, Stack, Typography } from '@mui/material'; import React from 'react'; import styled from '@emotion/styled'; import { convertHexToRgba } from '../utils/colorUtils'; @@ -126,13 +126,38 @@ export const Result = ({ result, revealForm }: Props) => { return ( - {t('directions.result.time')}: {time} -
- {t('directions.result.distance')}: {distance} -
- {t('directions.result.ascent')}: {ascent} -
-
+ +
+ + {t('directions.result.time')} + + + {time} + +
+
+ + {t('directions.result.distance')} + + + {distance} + +
+
+ + {t('directions.result.ascent')} + + + {ascent} + +
+
+ {result.instructions && ( )} diff --git a/src/components/Directions/TextMarker.tsx b/src/components/Directions/TextMarker.tsx new file mode 100644 index 000000000..b2e14fff7 --- /dev/null +++ b/src/components/Directions/TextMarker.tsx @@ -0,0 +1,139 @@ +import React from 'react'; + +type TextMarkerProps = { + width?: number; + height?: number; + text: string; + hasShadow?: boolean; + hasPin?: boolean; +}; + +export const TextMarker = ({ + width, + height, + text, + hasShadow = false, + hasPin = true, +}: TextMarkerProps) => ( + + + {hasShadow && ( + + + + + + + + + + + )} + {hasPin ? ( + <> + + + + + + ) : ( + + )} + + + + {text} + + + + +); + +const ASCII_NUMBER_A = 65; +const ALPHABET_LETTERS_COUNT = 41; + +type AlphabeticalMarkerProps = { + index: number; + hasShadow?: boolean; + width?: number; + height?: number; + hasPin?: boolean; +}; + +export const AlphabeticalMarker = ({ + index, + hasShadow, + width, + height, + hasPin = true, +}: AlphabeticalMarkerProps) => ( + +); diff --git a/src/components/SearchBox/options/coords.tsx b/src/components/SearchBox/options/coords.tsx index e57ea6629..5cfd84ce0 100644 --- a/src/components/SearchBox/options/coords.tsx +++ b/src/components/SearchBox/options/coords.tsx @@ -1,10 +1,14 @@ import { LonLat } from '../../../services/types'; import { Option } from '../types'; +import { getRoundedPosition, roundedToDeg } from '../../../utils'; +import { getGlobalMap } from '../../../services/mapStorage'; export const getCoordsOption = (center: LonLat, label?: string): Option => ({ type: 'coords', coords: { center, - label: label || center.toReversed().join(','), + label: + label || + roundedToDeg(getRoundedPosition(center, getGlobalMap().getZoom())), }, });