Skip to content

Commit

Permalink
EditDialog: Add map (#877)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik authored Jan 10, 2025
1 parent 8ad1a72 commit 608d427
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 49 deletions.
29 changes: 21 additions & 8 deletions src/components/FeaturePanel/Climbing/CragMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ const useInitMap = () => {
const containerRef = React.useRef(null);
const mapRef = React.useRef<maplibregl.Map>(null);
const [isMapLoaded, setIsMapLoaded] = useState(false);
const [isFirstMapLoad, setIsFirstMapLoad] = useState(true);

const { feature } = useFeatureContext();
const { photoPaths } = useClimbingContext();

Expand Down Expand Up @@ -230,7 +232,18 @@ const useInitMap = () => {

mapRef.current.on('load', () => {
setIsMapLoaded(true);
if (mapRef.current) {
});

return () => {
if (map) {
map.remove();
}
};
}, [containerRef]);

useEffect(() => {
mapRef.current?.on('load', () => {
if (isFirstMapLoad) {
mapRef.current.jumpTo({
center: feature.center as [number, number],
zoom: 18.5,
Expand All @@ -240,15 +253,15 @@ const useInitMap = () => {
type: 'FeatureCollection' as const,
features: transformMemberFeaturesToGeojson(feature.memberFeatures),
});
setIsFirstMapLoad(false);
}
});

return () => {
if (map) {
map.remove();
}
};
}, [containerRef, feature.center, feature.memberFeatures, getClimbingSource]);
}, [
feature.center,
feature.memberFeatures,
getClimbingSource,
isFirstMapLoad,
]);

return { containerRef, isMapLoaded };
};
Expand Down
68 changes: 35 additions & 33 deletions src/components/FeaturePanel/EditDialog/EditContent/EditContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,46 +26,48 @@ export const EditContent = () => {
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
return (
<>
<DialogContent dividers>
<form autoComplete="off" onSubmit={(e) => e.preventDefault()}>
<OsmUserLoggedOut />
<Stack direction={isSmallScreen ? 'column' : 'row'} gap={2}>
{items.length > 1 && (
<Tabs
orientation={isSmallScreen ? 'horizontal' : 'vertical'}
variant={isSmallScreen ? 'scrollable' : 'standard'}
value={current}
onChange={(
_event: React.SyntheticEvent,
newShortId: string,
) => {
setCurrent(newShortId);
}}
sx={{
borderRight: isSmallScreen ? 0 : 1,
borderBottom: isSmallScreen ? 1 : 0,
borderColor: 'divider',
'&& .MuiTab-root': {
alignItems: isSmallScreen ? undefined : 'baseline',
textAlign: isSmallScreen ? undefined : 'left',
},
}}
>
{items.map(({ shortId, tags }, idx) => (
<Tab key={idx} label={tags.name ?? shortId} value={shortId} />
))}
</Tabs>
)}
<Stack
direction={isSmallScreen ? 'column' : 'row'}
gap={2}
overflow="hidden"
>
{items.length > 1 && (
<Tabs
orientation={isSmallScreen ? 'horizontal' : 'vertical'}
variant={isSmallScreen ? 'scrollable' : 'standard'}
value={current}
onChange={(_event: React.SyntheticEvent, newShortId: string) => {
setCurrent(newShortId);
}}
sx={{
borderRight: isSmallScreen ? 0 : 1,
borderBottom: isSmallScreen ? 1 : 0,
borderColor: 'divider',
'&& .MuiTab-root': {
alignItems: isSmallScreen ? undefined : 'baseline',
textAlign: isSmallScreen ? undefined : 'left',
},
}}
>
{items.map(({ shortId, tags }, idx) => (
<Tab key={idx} label={tags.name ?? shortId} value={shortId} />
))}
</Tabs>
)}
<DialogContent dividers>
<form autoComplete="off" onSubmit={(e) => e.preventDefault()}>
<OsmUserLoggedOut />

<div>
<FeatureEditSection shortId={current} />
<CommentField />
<ContributionInfoBox />
<OsmUserLogged />
<TestApiWarning />
</div>
</Stack>
</form>
</DialogContent>
</form>
</DialogContent>
</Stack>
<EditDialogActions />
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import maplibregl, { LngLat } from 'maplibre-gl';
import {
Accordion,
AccordionDetails,
AccordionSummary,
CircularProgress,
Typography,
} from '@mui/material';
import styled from '@emotion/styled';
import { outdoorStyle } from '../../../../Map/styles/outdoorStyle';
import { COMPASS_TOOLTIP } from '../../../../Map/useAddTopRightControls';
import { createMapEffectHook } from '../../../../helpers';
import { LonLat } from '../../../../../services/types';
import { useFeatureEditData } from './SingleFeatureEditContext';
import { useEditContext } from '../../EditContext';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { t } from '../../../../../services/intl';

const Container = styled.div`
width: 100%;
height: 500px;
position: relative;
`;

const LoadingContainer = styled.div`
height: 100%;
width: 100%;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
`;

const Map = styled.div<{ $isVisible: boolean }>`
visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')};
height: 100%;
width: 100%;
`;

const useUpdateFeatureMarker = createMapEffectHook<
[
{
onMarkerChange: (lngLat: LngLat) => void;
nodeLonLat: LonLat;
markerRef: React.MutableRefObject<maplibregl.Marker>;
},
]
>((map, props) => {
const onDragEnd = () => {
const lngLat = markerRef.current?.getLngLat();
if (lngLat) {
props.onMarkerChange(lngLat);
}
};

const { markerRef, nodeLonLat } = props;

markerRef.current?.remove();
markerRef.current = undefined;

if (nodeLonLat) {
const [lng, lat] = nodeLonLat;
markerRef.current = new maplibregl.Marker({
color: '#556cd6',
draggable: true,
})
.setLngLat({
lng: parseFloat(lng.toFixed(6)),
lat: parseFloat(lat.toFixed(6)),
})
.addTo(map);

markerRef.current?.on('dragend', onDragEnd);
}
});

const useInitMap = () => {
const containerRef = React.useRef(null);
const mapRef = React.useRef<maplibregl.Map>(null);
const [isMapLoaded, setIsMapLoaded] = useState(false);
const [isFirstMapLoad, setIsFirstMapLoad] = useState(true);

const { current, items } = useEditContext();
const currentItem = items.find((item) => item.shortId === current);
const markerRef = useRef<maplibregl.Marker>();

const onMarkerChange = ({ lng, lat }: LngLat) => {
const newLonLat = [lng, lat];

currentItem.setNodeLonLat(newLonLat);
};

useUpdateFeatureMarker(mapRef.current, {
onMarkerChange,
nodeLonLat: currentItem.nodeLonLat,
markerRef,
});

React.useEffect(() => {
const geolocation = new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
},
fitBoundsOptions: {
duration: 4000,
},
trackUserLocation: true,
});

setIsMapLoaded(false);
if (!containerRef.current) return undefined;
const map = new maplibregl.Map({
container: containerRef.current,
style: outdoorStyle,
attributionControl: false,
refreshExpiredTiles: false,
locale: {
'NavigationControl.ResetBearing': COMPASS_TOOLTIP,
},
});

map.scrollZoom.setWheelZoomRate(1 / 200); // 1/450 is default, bigger value = faster
map.addControl(geolocation);
mapRef.current = map;

mapRef.current?.on('load', () => {
setIsMapLoaded(true);
});

return () => {
if (map) {
map.remove();
}
};
}, [containerRef, current]);

const updateCenter = useCallback(() => {
if (isFirstMapLoad) {
mapRef.current?.jumpTo({
center: currentItem.nodeLonLat as [number, number],
zoom: 18.5,
});
setIsFirstMapLoad(false);
}
}, [currentItem.nodeLonLat, isFirstMapLoad]);

useEffect(() => {
mapRef.current?.on('load', () => {
updateCenter();
});
}, [currentItem.nodeLonLat, isFirstMapLoad, updateCenter]);

useEffect(() => {
updateCenter();
}, [current, updateCenter]);

// edit data item switched
useEffect(() => {
setIsFirstMapLoad(true);
}, [current]);

return { containerRef, isMapLoaded };
};

const EditFeatureMap = () => {
const { containerRef, isMapLoaded } = useInitMap();
const [expanded, setExpanded] = useState(false);

const { shortId } = useFeatureEditData();
const isNode = shortId[0] === 'n';

if (!isNode) return null;

return (
<Accordion
disableGutters
elevation={0}
square
expanded={expanded}
onChange={() => setExpanded(!expanded)}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="button">{t('editdialog.location')}</Typography>
</AccordionSummary>
<AccordionDetails>
<Container>
{!isMapLoaded && (
<LoadingContainer>
<CircularProgress color="primary" />
</LoadingContainer>
)}
<Map $isVisible={isMapLoaded} ref={containerRef} />
</Container>
</AccordionDetails>
</Accordion>
);
};
export default EditFeatureMap; // dynamic import
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
} from './SingleFeatureEditContext';
import { MembersEditor } from '../MembersEditor';
import { ParentsEditor } from '../ParentsEditor';
import dynamic from 'next/dynamic';

const EditFeatureMapDynamic = dynamic(() => import('./EditFeatureMap'), {
ssr: false,
loading: () => <div />,
});
import { Stack, Typography } from '@mui/material';
import { useEditContext } from '../../EditContext';

Expand Down Expand Up @@ -45,6 +51,7 @@ export const FeatureEditSection = ({ shortId }: Props) => (
<PresetSelect />
<MajorKeysEditor />
<TagsEditor />
<EditFeatureMapDynamic />
<ParentsEditor />
<MembersEditor />
</SingleFeatureEditContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Accordion,
AccordionDetails,
AccordionSummary,
Chip,
List,
Stack,
Typography,
Expand All @@ -30,9 +31,8 @@ export const MembersEditor = () => {
id="panel1-header"
>
<Stack direction="row" spacing={2} alignItems="center">
<Typography variant="button">
{t('editdialog.members')} ({members.length})
</Typography>
<Typography variant="button">{t('editdialog.members')}</Typography>
<Chip size="small" label={members.length} />
</Stack>
</AccordionSummary>
<AccordionDetails>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Accordion,
AccordionDetails,
AccordionSummary,
Chip,
List,
Stack,
Typography,
Expand Down Expand Up @@ -43,9 +44,8 @@ export const ParentsEditor = () => {
id="panel1-header"
>
<Stack direction="row" spacing={2} alignItems="center">
<Typography variant="button">
{t('editdialog.parents')} ({parents.length})
</Typography>
<Typography variant="button">{t('editdialog.parents')}</Typography>
<Chip size="small" label={parents.length} />
</Stack>
</AccordionSummary>
<AccordionDetails>
Expand Down
Loading

0 comments on commit 608d427

Please sign in to comment.