-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Maps] Lock tooltip in place with click #32733
Changes from all commits
2591a29
bd71ee9
3f93662
f22dfdc
630b4ae
70fc985
1acf665
f8230ab
715d84c
6c76098
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,16 @@ import React from 'react'; | |
import ReactDOM from 'react-dom'; | ||
import { ResizeChecker } from 'ui/resize_checker'; | ||
import { syncLayerOrder, removeOrphanedSourcesAndLayers, createMbMapInstance } from './utils'; | ||
import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; | ||
import { DECIMAL_DEGREES_PRECISION, FEATURE_ID_PROPERTY_NAME, ZOOM_PRECISION } from '../../../../common/constants'; | ||
import mapboxgl from 'mapbox-gl'; | ||
import { FeatureTooltip } from '../feature_tooltip'; | ||
|
||
|
||
const TOOLTIP_TYPE = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are constants used by the store and actions. I think they should be defined in a more global location under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. theoretically yes, in practice no. the tooltip-state is only evaluated here to handle moves/clicks correctly. I would move these constants once they are actually being evaluated in the redux flow (e.g, tooltip type will be relevant when trying to preserve tooltips across data-refreshes, but it's not something addressed here). |
||
HOVER: 'HOVER', | ||
LOCKED: 'LOCKED' | ||
}; | ||
|
||
export class MBMapContainer extends React.Component { | ||
|
||
constructor() { | ||
|
@@ -25,62 +31,89 @@ export class MBMapContainer extends React.Component { | |
}); | ||
} | ||
|
||
_onTooltipClose = () => { | ||
this.props.setTooltipState(null); | ||
}; | ||
|
||
_debouncedSync = _.debounce(() => { | ||
if (this._isMounted) { | ||
this._syncMbMapWithLayerList(); | ||
this._syncMbMapWithInspector(); | ||
} | ||
}, 256); | ||
|
||
_updateTooltipState = _.debounce(async (e) => { | ||
|
||
const mbLayerIds = this._getMbLayerIdsForTooltips(); | ||
const features = this._mbMap.queryRenderedFeatures(e.point, { layers: mbLayerIds }); | ||
_lockTooltip = (e) => { | ||
|
||
this._updateHoverTooltipState.cancel();//ignore any possible moves | ||
|
||
const features = this._getFeaturesUnderPointer(e.point); | ||
if (!features.length) { | ||
this.props.setTooltipState(null); | ||
return; | ||
} | ||
|
||
const targetFeature = features[0]; | ||
const layer = this._getLayer(targetFeature.layer.id); | ||
const popupAnchorLocation = this._justifyAnchorLocation(e.lngLat, targetFeature); | ||
this.props.setTooltipState({ | ||
thomasneirynck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
type: TOOLTIP_TYPE.LOCKED, | ||
layerId: layer.getId(), | ||
featureId: targetFeature.properties[FEATURE_ID_PROPERTY_NAME], | ||
thomasneirynck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
location: popupAnchorLocation | ||
}); | ||
|
||
}; | ||
|
||
_updateHoverTooltipState = _.debounce((e) => { | ||
|
||
if (this.props.tooltipState && this.props.tooltipState.type === TOOLTIP_TYPE.LOCKED) { | ||
//ignore hover events when tooltip is locked | ||
return; | ||
} | ||
|
||
const features = this._getFeaturesUnderPointer(e.point); | ||
if (!features.length) { | ||
this.props.setTooltipState(null); | ||
return; | ||
} | ||
|
||
const targetFeature = features[0]; | ||
|
||
if (this.props.tooltipState) { | ||
const propertiesUnchanged = _.isEqual(this.props.tooltipState.activeFeature.properties, targetFeature.properties); | ||
const geometryUnchanged = _.isEqual(this.props.tooltipState.activeFeature.geometry, targetFeature.geometry); | ||
if(propertiesUnchanged && geometryUnchanged) { | ||
if (targetFeature.properties[FEATURE_ID_PROPERTY_NAME] === this.props.tooltipState.featureId) { | ||
return; | ||
} | ||
} | ||
|
||
const layer = this._getLayer(targetFeature.layer.id); | ||
const formattedProperties = await layer.getPropertiesForTooltip(targetFeature.properties); | ||
const popupAnchorLocation = this._justifyAnchorLocation(e.lngLat, targetFeature); | ||
|
||
this.props.setTooltipState({ | ||
type: TOOLTIP_TYPE.HOVER, | ||
featureId: targetFeature.properties[FEATURE_ID_PROPERTY_NAME], | ||
layerId: layer.getId(), | ||
location: popupAnchorLocation | ||
}); | ||
|
||
let popupAnchorLocation = [e.lngLat.lng, e.lngLat.lat]; // default popup location to mouse location | ||
}, 100); | ||
|
||
_justifyAnchorLocation(mbLngLat, targetFeature) { | ||
let popupAnchorLocation = [mbLngLat.lng, mbLngLat.lat]; // default popup location to mouse location | ||
if (targetFeature.geometry.type === 'Point') { | ||
const coordinates = targetFeature.geometry.coordinates.slice(); | ||
|
||
// Ensure that if the map is zoomed out such that multiple | ||
// copies of the feature are visible, the popup appears | ||
// over the copy being pointed to. | ||
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { | ||
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; | ||
while (Math.abs(mbLngLat.lng - coordinates[0]) > 180) { | ||
coordinates[0] += mbLngLat.lng > coordinates[0] ? 360 : -360; | ||
} | ||
|
||
popupAnchorLocation = coordinates; | ||
} | ||
|
||
this.props.setTooltipState({ | ||
activeFeature: { | ||
properties: targetFeature.properties, | ||
geometry: targetFeature.geometry | ||
}, | ||
formattedProperties: formattedProperties, | ||
layerId: layer.getId(), | ||
location: popupAnchorLocation | ||
}); | ||
|
||
}, 100); | ||
|
||
|
||
return popupAnchorLocation; | ||
} | ||
_getMbLayerIdsForTooltips() { | ||
return this.props.layerList.reduce((mbLayerIds, layer) => { | ||
return layer.canShowTooltip() ? mbLayerIds.concat(layer.getMbLayerIds()) : mbLayerIds; | ||
|
@@ -106,6 +139,11 @@ export class MBMapContainer extends React.Component { | |
}; | ||
} | ||
|
||
_getFeaturesUnderPointer(mbLngLatPoint) { | ||
const mbLayerIds = this._getMbLayerIdsForTooltips(); | ||
return this._mbMap.queryRenderedFeatures(mbLngLatPoint, { layers: mbLayerIds }); | ||
} | ||
|
||
componentDidUpdate() { | ||
// do not debounce syncing of map-state and tooltip | ||
this._syncMbMapWithMapState(); | ||
|
@@ -162,7 +200,8 @@ export class MBMapContainer extends React.Component { | |
}); | ||
|
||
|
||
this._mbMap.on('mousemove', this._updateTooltipState); | ||
this._mbMap.on('mousemove', this._updateHoverTooltipState); | ||
this._mbMap.on('click', this._lockTooltip); | ||
|
||
this.props.onMapReady(this._getMapState()); | ||
} | ||
|
@@ -181,22 +220,27 @@ export class MBMapContainer extends React.Component { | |
} | ||
} | ||
|
||
_showTooltip() { | ||
//todo: can still be optimized. No need to rerender if content remains identical | ||
ReactDOM.render( | ||
React.createElement( | ||
FeatureTooltip, { | ||
properties: this.props.tooltipState.formattedProperties, | ||
} | ||
), | ||
this._tooltipContainer | ||
); | ||
|
||
this._mbPopup.setLngLat(this.props.tooltipState.location) | ||
_renderContentToTooltip(content, location) { | ||
if (!this._isMounted) { | ||
return; | ||
} | ||
ReactDOM.render((<FeatureTooltip properties={content} onCloseClick={this._onTooltipClose}/>), this._tooltipContainer); | ||
|
||
this._mbPopup.setLngLat(location) | ||
.setDOMContent(this._tooltipContainer) | ||
.addTo(this._mbMap); | ||
} | ||
|
||
|
||
async _showTooltip() { | ||
const tooltipLayer = this.props.layerList.find(layer => { | ||
return layer.getId() === this.props.tooltipState.layerId; | ||
}); | ||
const targetFeature = tooltipLayer.getFeatureByFeatureById(this.props.tooltipState.featureId); | ||
const formattedProperties = await tooltipLayer.getPropertiesForTooltip(targetFeature.properties); | ||
this._renderContentToTooltip(formattedProperties, this.props.tooltipState.location); | ||
} | ||
|
||
_syncTooltipState() { | ||
if (this.props.tooltipState) { | ||
this._showTooltip(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no flexing happens when only single element