Skip to content

Commit cb95bfe

Browse files
authored
Merge pull request #1933 from HSLdevcom/develop
Update master
2 parents 9aa38bc + d92879c commit cb95bfe

File tree

19 files changed

+374
-101
lines changed

19 files changed

+374
-101
lines changed

src/components/map/layers/NodeLayer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { LatLngBounds } from 'leaflet';
22
import _ from 'lodash';
33
import { inject, observer } from 'mobx-react';
44
import React, { useEffect, useMemo, useState } from 'react';
5+
import { ISelectNetworkEntityPopupData } from '~/components/map/layers/popups/SelectNetworkEntityPopup';
56
import NodeType from '~/enums/nodeType';
6-
import { ISearchNode } from '~/models/INode';
7+
import { INodeMapHighlight, ISearchNode } from '~/models/INode';
78
import { MapStore } from '~/stores/mapStore';
89
import { NetworkStore } from '~/stores/networkStore';
910
import { RoutePathStore } from '~/stores/routePathStore';
@@ -102,13 +103,17 @@ const NodeLayer = inject(
102103
return renderNodeMarker(nodeCluster[0]);
103104
}
104105
if (nodeCluster.length > 1) {
106+
const popupData: ISelectNetworkEntityPopupData = {
107+
nodes: nodeCluster as INodeMapHighlight[],
108+
links: [],
109+
};
105110
return (
106111
<ClusterNodeMarker
107112
key={`clusterMarker-${index}`}
108113
coordinates={bounds.getCenter()}
109114
nodes={nodeCluster}
110-
onLeftClickMarkerItem={() => void 0}
111-
onRightClickMarkerItem={() => void 0}
115+
popupType={'selectNetworkEntityPopup'}
116+
popupData={popupData}
112117
/>
113118
);
114119
}

src/components/map/layers/PopupLayer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import classnames from 'classnames';
22
import { inject, observer } from 'mobx-react';
33
import React, { Component } from 'react';
44
import { Popup } from 'react-leaflet';
5+
import SelectRoutePathNeighborPopup from '~/components/map/layers/popups/SelectRoutePathNeighborPopup';
56
import { IPopup, PopupStore } from '~/stores/popupStore';
67
import * as s from './popupLayer.scss';
78
import NodePopup from './popups/NodePopup';
@@ -34,6 +35,8 @@ class PopupLayer extends Component<PopupLayerProps> {
3435
return <NodePopup popupId={popup.id} data={popup.data} />;
3536
case 'nodeUsagePopup':
3637
return <NodeUsagePopup popupId={popup.id} data={popup.data} />;
38+
case 'selectRoutePathNeighborPopup':
39+
return <SelectRoutePathNeighborPopup popupId={popup.id} data={popup.data} />;
3740
default:
3841
return popup.content!(popup.id!);
3942
}

src/components/map/layers/edit/RoutePathNeighborLinkLayer.tsx

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import { LatLngBounds } from 'leaflet';
12
import { union } from 'lodash';
23
import { reaction, IReactionDisposer } from 'mobx';
34
import { inject, observer } from 'mobx-react';
45
import React, { Component } from 'react';
56
import { Polyline } from 'react-leaflet';
7+
import ClusterNodeMarker from '~/components/map/layers/markers/ClusterNodeMarker';
8+
import { ISelectRoutePathNeighborPopupData } from '~/components/map/layers/popups/SelectRoutePathNeighborPopup';
69
import NodeSize from '~/enums/nodeSize';
710
import NodeType from '~/enums/nodeType';
11+
import NodeFactory from '~/factories/nodeFactory';
812
import EventListener, { IEditRoutePathNeighborLinkClickParams } from '~/helpers/EventListener';
913
import { IRoutePath } from '~/models';
1014
import INeighborLink from '~/models/INeighborLink';
@@ -35,6 +39,7 @@ interface PolylineRefs {
3539
@observer
3640
class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRoutePathLayerState> {
3741
private linkListener: IReactionDisposer;
42+
private highlightedNeighborLinkListener: IReactionDisposer;
3843
constructor(props: IRoutePathLayerProps) {
3944
super(props);
4045
this.state = {
@@ -44,10 +49,15 @@ class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRouteP
4449
() => this.props.routePathLayerStore!.neighborLinks,
4550
() => this.initializePolylineRefs()
4651
);
52+
this.highlightedNeighborLinkListener = reaction(
53+
() => this.props.routePathLayerStore!.highlightedNeighborLinkId,
54+
() => this.bringNeighborLinkTofront()
55+
);
4756
}
4857

4958
public componentWillUnmount() {
5059
this.linkListener();
60+
this.highlightedNeighborLinkListener();
5161
}
5262

5363
private initializePolylineRefs = () => {
@@ -60,6 +70,13 @@ class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRouteP
6070
});
6171
};
6272

73+
private bringNeighborLinkTofront = () => {
74+
const id = this.props.routePathLayerStore!.highlightedNeighborLinkId;
75+
if (id) {
76+
this.state.polylineRefs[id]!.current.leafletElement.bringToFront();
77+
}
78+
};
79+
6380
private showNodePopup = (node: INode, routePaths: IRoutePath[]) => {
6481
const popupData: INodeUsagePopupData = {
6582
routePaths,
@@ -148,6 +165,10 @@ class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRouteP
148165
}
149166
};
150167

168+
private renderNeighborLinks = (neighborLinks: INeighborLink[]) => {
169+
return neighborLinks.map((neighborLink) => this.renderNeighborLink(neighborLink));
170+
};
171+
151172
private renderNeighborLink = (neighborLink: INeighborLink) => {
152173
const onNeighborLinkClick = () => {
153174
const clickParams: IEditRoutePathNeighborLinkClickParams = {
@@ -193,7 +214,6 @@ class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRouteP
193214
if (isHighlighted) {
194215
if (this.props.routePathLayerStore!.highlightedNeighborLinkId !== id) {
195216
this.props.routePathLayerStore!.setHighlightedNeighborLinkId(id);
196-
this.state.polylineRefs[id]!.current.leafletElement.bringToFront();
197217
}
198218
} else {
199219
if (this.props.routePathLayerStore!.highlightedNeighborLinkId === id) {
@@ -203,21 +223,114 @@ class RoutePathNeighborLinkLayer extends Component<IRoutePathLayerProps, IRouteP
203223
};
204224

205225
render() {
206-
const neighborLinks = this.props.routePathLayerStore!.neighborLinks;
207-
return neighborLinks.map((neighborLink, index) => {
208-
const neighborToAddType = this.props.routePathLayerStore!.neighborToAddType;
209-
const nodeToRender =
210-
neighborToAddType === NeighborToAddType.AfterNode
211-
? neighborLink.routePathLink.endNode
212-
: neighborLink.routePathLink.startNode;
213-
return [
214-
this.renderNeighborNode(nodeToRender, neighborLink, index),
215-
this.renderNeighborLink(neighborLink),
216-
];
217-
});
226+
const neighborLinks: INeighborLink[] = this.props.routePathLayerStore!.neighborLinks;
227+
const clusteredNeighborLinksMap: Map<
228+
LatLngBounds,
229+
INeighborLink[]
230+
> = _getClusteredNeighborLinksMap(
231+
neighborLinks,
232+
this.props.routePathLayerStore!.neighborToAddType
233+
);
234+
235+
const clusteredNeighborLinkMapEntries = Array.from(clusteredNeighborLinksMap.entries());
236+
237+
return (
238+
<>
239+
{clusteredNeighborLinkMapEntries.map(([bounds, neighborLinkCluster], index) => {
240+
if (neighborLinkCluster.length === 1) {
241+
const neighborLink = neighborLinkCluster[0];
242+
const nodeToRender =
243+
this.props.routePathLayerStore!.neighborToAddType ===
244+
NeighborToAddType.AfterNode
245+
? neighborLink.routePathLink.endNode
246+
: neighborLink.routePathLink.startNode;
247+
return [
248+
this.renderNeighborNode(nodeToRender, neighborLink, index),
249+
this.renderNeighborLink(neighborLink),
250+
];
251+
}
252+
if (neighborLinkCluster.length > 1) {
253+
const searchNodes = neighborLinkCluster.map(
254+
(neighborLink: INeighborLink) => {
255+
const nodeToRender =
256+
this.props.routePathLayerStore!.neighborToAddType ===
257+
NeighborToAddType.AfterNode
258+
? neighborLink.routePathLink.endNode
259+
: neighborLink.routePathLink.startNode;
260+
return NodeFactory.createSearchNodeFromNode(nodeToRender);
261+
}
262+
);
263+
264+
const popupData: ISelectRoutePathNeighborPopupData = {
265+
neighborNodes: neighborLinkCluster.map(
266+
(neighborLink: INeighborLink) => {
267+
const nodeToRender =
268+
this.props.routePathLayerStore!.neighborToAddType ===
269+
NeighborToAddType.AfterNode
270+
? neighborLink.routePathLink.endNode
271+
: neighborLink.routePathLink.startNode;
272+
return {
273+
neighborLink,
274+
node: nodeToRender,
275+
};
276+
}
277+
),
278+
};
279+
return [
280+
<ClusterNodeMarker
281+
key={`clusterMarker-${index}`}
282+
coordinates={bounds.getCenter()}
283+
nodes={searchNodes}
284+
iconSize={'large'}
285+
popupType={'selectRoutePathNeighborPopup'}
286+
popupData={popupData}
287+
/>,
288+
this.renderNeighborLinks(neighborLinkCluster),
289+
];
290+
}
291+
return null;
292+
})}
293+
</>
294+
);
218295
}
219296
}
220297

298+
const _getClusteredNeighborLinksMap = (
299+
neighborLinks: INeighborLink[],
300+
neighborToAddType: NeighborToAddType
301+
): Map<LatLngBounds, INeighborLink[]> => {
302+
if (neighborLinks.length === 0) {
303+
return new Map();
304+
}
305+
const clusteredNeighborLinksMap = new Map<LatLngBounds, INeighborLink[]>();
306+
307+
for (const neighborLink of neighborLinks) {
308+
let areaBounds;
309+
const node =
310+
neighborToAddType === NeighborToAddType.AfterNode
311+
? neighborLink.routePathLink.endNode
312+
: neighborLink.routePathLink.startNode;
313+
if (clusteredNeighborLinksMap.size !== 0) {
314+
const areaEntries = clusteredNeighborLinksMap.entries();
315+
for (const [area] of areaEntries) {
316+
if (area.contains(node.coordinates)) {
317+
areaBounds = area;
318+
break;
319+
}
320+
}
321+
}
322+
323+
if (!areaBounds) {
324+
areaBounds = node.coordinates.toBounds(3);
325+
}
326+
327+
const neighborLinkGroup = clusteredNeighborLinksMap.get(areaBounds) || [];
328+
neighborLinkGroup.push(neighborLink);
329+
clusteredNeighborLinksMap.set(areaBounds, neighborLinkGroup);
330+
}
331+
return clusteredNeighborLinksMap;
332+
};
333+
221334
const getNeighborLinkColor = (neighborLink: INeighborLink, isNeighborLinkHighlighted: boolean) => {
222335
const isNeighborLinkUsed = neighborLink.nodeUsageRoutePaths.length > 0;
223336
if (isNeighborLinkHighlighted) {

src/components/map/layers/markers/ClusterNodeMarker.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,26 @@ import { inject, observer } from 'mobx-react';
44
import React from 'react';
55
import { Marker as LeafletMarker } from 'react-leaflet';
66
import TransitType from '~/enums/transitType';
7-
import { INodeMapHighlight, ISearchNode } from '~/models/INode';
8-
import { IPopupProps, PopupStore } from '~/stores/popupStore';
7+
import { ISearchNode } from '~/models/INode';
8+
import { IPopupProps, PopupStore, PopupType } from '~/stores/popupStore';
99
import NodeHighlightColor from '~/types/NodeHighlightColor';
1010
import LeafletUtils from '~/utils/leafletUtils';
11-
import { ISelectNetworkEntityPopupData } from '../popups/SelectNetworkEntityPopup';
1211
import * as s from './clusterNodeMarker.scss';
1312

13+
type IconSize = 'large' | 'small';
14+
1415
interface IClusterNodeMarkerProps {
1516
coordinates: L.LatLng;
1617
nodes: ISearchNode[];
17-
onLeftClickMarkerItem: Function;
18-
onRightClickMarkerItem: Function;
18+
popupType: PopupType;
19+
popupData: any; // Depends on which popupType is given
20+
iconSize?: IconSize;
1921
onContextMenu?: Function;
2022
popupStore?: PopupStore;
2123
}
2224

2325
const ClusterNodeMarker = inject('popupStore')(
24-
observer((props: IClusterNodeMarkerProps) => {
26+
observer(({ iconSize = 'small', ...props }: IClusterNodeMarkerProps) => {
2527
const getTransitTypeClassName = () => {
2628
let transitTypes: TransitType[] = [];
2729
props.nodes.forEach((node) => (transitTypes = transitTypes.concat(node.transitTypes)));
@@ -44,9 +46,17 @@ const ClusterNodeMarker = inject('popupStore')(
4446
return s.unusedStop;
4547
};
4648
const renderNodeMarkerIcon = () => {
47-
const iconWidth = 20;
49+
const iconWidth = iconSize === 'small' ? 20 : 24;
50+
const markerHeight = iconSize === 'small' ? s.markerHeightSmall : s.markerHeightLarge;
4851
return LeafletUtils.createDivIcon({
49-
html: <div className={s.clusterNodeMarkerContainer}>{props.nodes.length}</div>,
52+
html: (
53+
<div
54+
className={s.clusterNodeMarkerContainer}
55+
style={{ fontSize: markerHeight, width: markerHeight }}
56+
>
57+
{props.nodes.length}
58+
</div>
59+
),
5060
options: {
5161
iconWidth,
5262
classNames: [s.node, s.clusterNodeMarker, getTransitTypeClassName()],
@@ -57,13 +67,9 @@ const ClusterNodeMarker = inject('popupStore')(
5767
};
5868

5969
const onMarkerClick = (e: L.LeafletEvent) => {
60-
const popupData: ISelectNetworkEntityPopupData = {
61-
nodes: props.nodes as INodeMapHighlight[],
62-
links: [],
63-
};
6470
const popup: IPopupProps = {
65-
type: 'selectNetworkEntityPopup',
66-
data: popupData,
71+
type: props.popupType,
72+
data: props.popupData,
6773
coordinates: props.coordinates,
6874
isCloseButtonVisible: true,
6975
isAutoCloseOn: false,

src/components/map/layers/markers/clusterNodeMarker.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import './nodeMarker.scss';
22

3-
$markerHeight: 14px;
3+
$markerHeightSmall: 14px;
4+
$markerHeightLarge: 17px;
45

56
.clusterNodeMarker {
67
display: flex;
@@ -12,7 +13,10 @@ $markerHeight: 14px;
1213
align-items: center;
1314
justify-content: center;
1415
font-weight: bold;
15-
font-size: $markerHeight;
1616
line-height: 100%;
17-
width: $markerHeight;
1817
}
18+
19+
:export {
20+
markerHeightSmall: $markerHeightSmall;
21+
markerHeightLarge: $markerHeightLarge;
22+
}

src/components/map/layers/markers/nodeMarker.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ $iconFullWidthLarge: 27;
99

1010
.node {
1111
display: block;
12-
opacity: 1;
1312
cursor: pointer;
1413
border-radius: 100px;
1514
border-style: solid;

0 commit comments

Comments
 (0)