Skip to content

Commit

Permalink
Directions: Add markers to directions
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik committed Nov 13, 2024
1 parent fb61de7 commit 324908e
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 40 deletions.
54 changes: 43 additions & 11 deletions src/components/Directions/DirectionsAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ 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, TextMarker } from './TextMarker';

const StyledTextField = styled(TextField)`
input::placeholder {
Expand All @@ -35,6 +37,7 @@ const DirectionsInput = ({
label,
onFocus,
onBlur,
pointIndex,
}) => {
const { InputLabelProps, InputProps, ...restParams } = params;

Expand All @@ -59,8 +62,11 @@ const DirectionsInput = ({
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon fontSize="small" />
<InputAdornment
position="start"
sx={{ position: 'relative', top: 5, left: 5 }}
>
<AlphabeticalMarker hasPin={false} index={pointIndex} height={32} />
</InputAdornment>
),
}}
Expand Down Expand Up @@ -137,14 +143,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<string | null>(null);
Expand All @@ -153,12 +160,27 @@ export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => {
const { userSettings } = useUserSettingsContext();
const { isImperial } = userSettings;

let svgElement;
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
svgElement = document.createElement('div');
svgElement.innerHTML = ReactDOMServer.renderToStaticMarkup(
<AlphabeticalMarker index={pointIndex} hasShadow width={27} />,
);
} else svgElement = undefined;

const ALPHABETICAL_MARKER = {
color: 'salmon',
draggable: true,
element: svgElement,
offset: [0, -10] as PointLike,
};

const markerRef = useRef<maplibregl.Marker>();

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);
}
Expand All @@ -167,6 +189,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) => {
Expand Down Expand Up @@ -224,6 +255,7 @@ export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => {
label={label}
onFocus={onInputFocus}
onBlur={handleBlur}
pointIndex={pointIndex}
/>
)}
renderOption={renderOptionFactory(
Expand Down
2 changes: 2 additions & 0 deletions src/components/Directions/DirectionsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,13 @@ export const DirectionsForm = ({ setResult, hideForm }: Props) => {
value={from}
setValue={setFrom}
label={t('directions.form.start_or_click')}
pointIndex={0}
/>
<DirectionsAutocomplete
value={to}
setValue={setTo}
label={t('directions.form.destination')}
pointIndex={1}
/>
</Stack>

Expand Down
51 changes: 31 additions & 20 deletions src/components/Directions/Instructions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -25,27 +26,37 @@ const StyledListItem = styled.li`
gap: 0.25rem;
`;

const Instruction = ({ instruction }: { instruction: Instruction }) => (
<StyledListItem>
<Stack direction="row" alignItems="center">
<Icon sign={instruction.sign} />
{instruction.street_name || instruction.text}
</Stack>
{instruction.distance > 0 && (
<Stack direction="row" alignItems="center" spacing={0.5}>
<Typography
noWrap
color="textSecondary"
variant="body1"
style={{ overflow: 'visible' }}
>
<Distance distance={instruction.distance} />
const Instruction = ({ instruction }: { instruction: Instruction }) => {
const theme = useTheme();

return (
<StyledListItem>
<Stack direction="row" alignItems="center">
<Box width={theme.spacing(6)}>
<Icon sign={instruction.sign} />
</Box>
<Typography variant="subtitle1" fontWeight={700}>
{instruction.street_name || instruction.text}
</Typography>
<hr style={{ width: '100%' }} />
</Stack>
)}
</StyledListItem>
);
{instruction.distance > 0 && (
<Stack direction="row" alignItems="center" spacing={2} ml={6}>
<Typography
noWrap
color="textSecondary"
variant="body2"
style={{ overflow: 'visible' }}
>
<Distance distance={instruction.distance} />
</Typography>
<Stack flex={1}>
<Divider />
</Stack>
</Stack>
)}
</StyledListItem>
);
};

const StyledList = styled.ul`
list-style: none;
Expand Down
41 changes: 33 additions & 8 deletions src/components/Directions/Result.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -126,13 +126,38 @@ export const Result = ({ result, revealForm }: Props) => {

return (
<StyledPaper elevation={3} $height="100%" $overflow="auto">
{t('directions.result.time')}: <strong>{time}</strong>
<br />
{t('directions.result.distance')}: <strong>{distance}</strong>
<br />
{t('directions.result.ascent')}: <strong>{ascent}</strong>
<br />
<br />
<Stack
direction="row"
spacing={2}
width="100%"
justifyContent="space-between"
>
<div>
<Typography variant="caption">
{t('directions.result.time')}
</Typography>
<Typography fontWeight={900} variant="h6">
{time}
</Typography>
</div>
<div>
<Typography variant="caption">
{t('directions.result.distance')}
</Typography>
<Typography fontWeight={900} variant="h6">
{distance}
</Typography>
</div>
<div>
<Typography variant="caption">
{t('directions.result.ascent')}
</Typography>
<Typography fontWeight={900} variant="h6">
{ascent}
</Typography>
</div>
</Stack>
<Divider sx={{ mt: 2, mb: 3 }} />
{result.instructions && (
<Instructions instructions={result.instructions} />
)}
Expand Down
140 changes: 140 additions & 0 deletions src/components/Directions/TextMarker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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) => (
<svg display="block" height={height} width={width} viewBox="0 0 27 41">
<g fill-rule="nonzero">
{hasShadow && (
<g transform="translate(3.0, 29.0)" fill="#000000">
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="10.5"
ry="5.25002273"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="10.5"
ry="5.25002273"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="9.5"
ry="4.77275007"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="8.5"
ry="4.29549936"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="7.5"
ry="3.81822308"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="6.5"
ry="3.34094679"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="5.5"
ry="2.86367051"
></ellipse>
<ellipse
opacity="0.04"
cx="10.5"
cy="5.80029008"
rx="4.5"
ry="2.38636864"
></ellipse>
</g>
)}
{hasPin ? (
<>
<path
fill="salmon"
d="M27,13.5 C27,19.074644 20.250001,27.000002 14.75,34.500002 C14.016665,35.500004 12.983335,35.500004 12.25,34.500002 C6.7499993,27.000002 0,19.222562 0,13.5 C0,6.0441559 6.0441559,0 13.5,0 C20.955844,0 27,6.0441559 27,13.5 Z"
></path>
<g opacity="0.25" fill="#000000">
<path d="M13.5,0 C6.0441559,0 0,6.0441559 0,13.5 C0,19.222562 6.7499993,27 12.25,34.5 C13,35.522727 14.016664,35.500004 14.75,34.5 C20.250001,27 27,19.074644 27,13.5 C27,6.0441559 20.955844,0 13.5,0 Z M13.5,1 C20.415404,1 26,6.584596 26,13.5 C26,15.898657 24.495584,19.181431 22.220703,22.738281 C19.945823,26.295132 16.705119,30.142167 13.943359,33.908203 C13.743445,34.180814 13.612715,34.322738 13.5,34.441406 C13.387285,34.322738 13.256555,34.180814 13.056641,33.908203 C10.284481,30.127985 7.4148684,26.314159 5.015625,22.773438 C2.6163816,19.232715 1,15.953538 1,13.5 C1,6.584596 6.584596,1 13.5,1 Z"></path>
</g>
</>
) : (
<circle cx="13.5" cy="13.5" r="13.5" fill="salmon"></circle>
)}

<g transform="translate(6.0, 7.0)" fill="#FFFFFF"></g>
<g transform="translate(8.0, 8.0)">
<text
x="0"
y="11"
fontSize={16}
fontWeight={900}
fill="white"
fontFamily="Roboto"
>
{text}
</text>
</g>
</g>
</svg>
);

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) => (
<TextMarker
text={
index < ALPHABET_LETTERS_COUNT
? String.fromCharCode(index + ASCII_NUMBER_A)
: ''
}
hasShadow={hasShadow}
width={width}
height={height}
hasPin={hasPin}
/>
);
6 changes: 5 additions & 1 deletion src/components/SearchBox/options/coords.tsx
Original file line number Diff line number Diff line change
@@ -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())),
},
});

0 comments on commit 324908e

Please sign in to comment.