Skip to content

Commit

Permalink
Directions: introduce driving directions support 🎉 (#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
zbycz authored Sep 19, 2024
1 parent 67c9b3d commit e06e63e
Show file tree
Hide file tree
Showing 26 changed files with 1,043 additions and 60 deletions.
9 changes: 7 additions & 2 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
ClimbingArea,
getClimbingAreas,
} from '../../services/climbing-areas/getClimbingAreas';
import { DirectionsBox } from '../Directions/DirectionsBox';

const usePersistMapView = () => {
const { view } = useMapStateContext();
Expand Down Expand Up @@ -101,10 +102,13 @@ const IndexWithProviders = ({ climbingAreas }: IndexWithProvidersProps) => {
const routeNumber =
router.query.all?.[3] === 'route' ? router.query.all?.[4] : undefined;

const directions = router.query.all?.[0] === 'directions' && !featureShown;

return (
<>
<Loading />
<SearchBox />
{!directions && <SearchBox />}
{directions && <DirectionsBox />}
{featureShown && !isMobileMode && <FeaturePanelOnSide />}
{featureShown && isMobileMode && <FeaturePanelInDrawer />}
{isClimbingDialogShown && (
Expand Down Expand Up @@ -177,7 +181,8 @@ App.getInitialProps = async (ctx: NextPageContext) => {
}

const cookies = nextCookies(ctx);
const featureFromRouter = await getInitialFeature(ctx);
const featureFromRouter =
ctx.query.all?.[0] === 'directions' ? null : await getInitialFeature(ctx);
if (ctx.res) {
if (featureFromRouter === '404' || featureFromRouter?.error === '404') {
ctx.res.statusCode = 404;
Expand Down
174 changes: 174 additions & 0 deletions src/components/Directions/DirectionsAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useMapStateContext } from '../utils/MapStateContext';
import { useStarsContext } from '../utils/StarsContext';
import React, { useEffect, useRef, useState } from 'react';
import { abortFetch } from '../../services/fetch';
import {
buildPhotonAddress,
fetchGeocoderOptions,
GEOCODER_ABORTABLE_QUEUE,
useInputValueState,
} from '../SearchBox/options/geocoder';
import { getStarsOptions } from '../SearchBox/options/stars';
import styled from '@emotion/styled';
import {
Autocomplete,
InputAdornment,
InputBase,
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 { SearchOption } from '../SearchBox/types';
import { useRouter } from 'next/router';
import { destroyRouting } from './routing/handleRouting';
import { Option, splitByFirstTilda } from './helpers';
import { LonLat } from '../../services/types';

const StyledTextField = styled(TextField)`
input::placeholder {
font-size: 0.9rem;
}
`;

const DirectionsInput = ({
params,
setInputValue,
autocompleteRef,
label,
onBlur,
}) => {
const { InputLabelProps, InputProps, ...restParams } = params;

useEffect(() => {
// @ts-ignore
params.InputProps.ref(autocompleteRef.current);
}, []); // eslint-disable-line react-hooks/exhaustive-deps

const onChange = (e) => setInputValue(e.target.value);
const onFocus = (e) => e.target.select();

return (
<StyledTextField
{...restParams} // eslint-disable-line react/jsx-props-no-spreading
variant="outlined"
size="small"
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon fontSize="small" />
</InputAdornment>
),
}}
placeholder={label}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
);
};
const useOptions = (inputValue: string, setOptions) => {
const { view } = useMapStateContext();
const { stars } = useStarsContext();

useEffect(() => {
(async () => {
abortFetch(GEOCODER_ABORTABLE_QUEUE);

if (inputValue === '') {
setOptions(getStarsOptions(stars));
return;
}

fetchGeocoderOptions(inputValue, view, setOptions, [], []);
})();
}, [inputValue, stars]); // eslint-disable-line react-hooks/exhaustive-deps
};
const Row = styled.div`
width: 100%;
`;

export const getOptionLabel = (option) =>
option == null
? ''
: option.properties?.name ||
(option.star && option.star.label) ||
(option.properties && buildPhotonAddress(option.properties)) ||
'';

export const getOptionToLonLat = (option) => {
const lonLat =
(option.star && option.star.center) || option.geometry.coordinates;
return lonLat as LonLat;
};

type Props = {
label: string;
value: Option;
setValue: (value: Option) => void;
};
export const DirectionsAutocomplete = ({ label, value, setValue }: Props) => {
const autocompleteRef = useRef();
const { inputValue, setInputValue } = useInputValueState();
const selectedOptionInputValue = useRef(null);
const [options, setOptions] = useState<Option[]>([]);
const mapCenter = useMapCenter();
const { currentTheme } = useUserThemeContext();

useOptions(inputValue, setOptions);

const onChange = (_, option: Option) => {
console.log('selected', option); // eslint-disable-line no-console
setInputValue(getOptionLabel(option));
setValue(option);
selectedOptionInputValue.current = getOptionLabel(option);
};

const onBlur = () => {
if (selectedOptionInputValue.current !== inputValue) {
if (options.length > 0 && inputValue) {
onChange(null, options[0]);
} else {
setValue(null);
}
}
};

// react to external value changes
useEffect(() => {
if (getOptionLabel(value) !== selectedOptionInputValue.current) {
setInputValue(getOptionLabel(value));
selectedOptionInputValue.current = getOptionLabel(value);
}
}, [setInputValue, value]);

return (
<Row ref={autocompleteRef}>
<Autocomplete
inputValue={inputValue}
options={options}
value={null}
filterOptions={(x) => x}
getOptionLabel={getOptionLabel}
getOptionKey={(option) => JSON.stringify(option)}
onChange={onChange}
autoComplete
disableClearable
autoHighlight
clearOnEscape
renderInput={(params) => (
<DirectionsInput
params={params}
setInputValue={setInputValue}
autocompleteRef={autocompleteRef}
label={label}
onBlur={onBlur}
/>
)}
renderOption={renderOptionFactory(inputValue, currentTheme, mapCenter)}
/>
</Row>
);
};
Loading

0 comments on commit e06e63e

Please sign in to comment.