mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Maps] use EuiPopover instead of mapbox tooltip (#45938)
* [Maps] use EuiPopover instead of mapbox tooltip * update draw tooltip to use EuiPopover, remove mapbox popup css hacks * use anchor size of 26px to fix positioning calculations * close tooltip when position is no longer visible * remove reevaluateTooltipPosition, its no longer needed * fix problem with map move and hover tooltip location not also moving
This commit is contained in:
parent
921e356a4b
commit
1ef6373f77
7 changed files with 132 additions and 137 deletions
|
@ -3,22 +3,6 @@
|
|||
.mapContainer {
|
||||
flex-grow: 1;
|
||||
|
||||
.mapboxgl-popup {
|
||||
z-index: $euiZLevel2;
|
||||
border-color: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
.mapboxgl-popup-content {
|
||||
@include euiBottomShadow($color: #000);
|
||||
background-color: $euiColorEmptyShade;
|
||||
padding: $euiSizeS;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mapboxgl-popup-tip {
|
||||
border-top-color: $euiColorEmptyShade !important;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-left .mapboxgl-ctrl {
|
||||
margin-left: $euiSizeM;
|
||||
margin-top: $euiSizeM;
|
||||
|
|
|
@ -16,14 +16,6 @@ import { GeometryFilterForm } from '../../components/geometry_filter_form';
|
|||
|
||||
export class FeatureGeometryFilterForm extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.reevaluateTooltipPosition();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.props.reevaluateTooltipPosition();
|
||||
}
|
||||
|
||||
_createFilter = ({ geometryLabel, indexPatternId, geoFieldName, geoFieldType, relation }) => {
|
||||
const filter = createSpatialFilterWithGeometry({
|
||||
geometry: this.props.feature.geometry,
|
||||
|
|
|
@ -30,7 +30,6 @@ export class FeatureProperties extends React.Component {
|
|||
|
||||
componentDidUpdate() {
|
||||
this._loadProperties();
|
||||
this.props.reevaluateTooltipPosition();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -34,7 +34,6 @@ const defaultProps = {
|
|||
layerId: `layer`,
|
||||
onCloseTooltip: () => {},
|
||||
showFilterButtons: false,
|
||||
reevaluateTooltipPosition: () => {},
|
||||
};
|
||||
|
||||
const mockTooltipProperties = [
|
||||
|
|
|
@ -154,7 +154,6 @@ export class FeatureTooltip extends React.Component {
|
|||
showFilterButtons={this.props.showFilterButtons}
|
||||
onCloseTooltip={this._onCloseTooltip}
|
||||
addFilters={this.props.addFilters}
|
||||
reevaluateTooltipPosition={this.props.reevaluateTooltipPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -352,7 +351,6 @@ export class FeatureTooltip extends React.Component {
|
|||
feature={currentFeature}
|
||||
geoFields={filteredGeoFields}
|
||||
addFilters={this.props.addFilters}
|
||||
reevaluateTooltipPosition={this.props.reevaluateTooltipPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,44 +6,43 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { EuiPopover, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DRAW_TYPE } from '../../../../../common/constants';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export class DrawTooltip extends Component {
|
||||
|
||||
state = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._tooltipContainer = document.createElement('div');
|
||||
this._mbPopup = new mapboxgl.Popup({
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
offset: 12,
|
||||
anchor: 'left',
|
||||
maxWidth: '300px',
|
||||
});
|
||||
this._popoverRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._showTooltip();
|
||||
this.props.mbMap.on('mousemove', this._updateTooltipLocation);
|
||||
this.props.mbMap.on('mouseout', this._hideTooltip);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this._popoverRef.current) {
|
||||
this._popoverRef.current.positionPopoverFluid();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.mbMap.off('mousemove', this._updateTooltipLocation);
|
||||
this.props.mbMap.off('mouseout', this._hideTooltip);
|
||||
this._hideTooltip();
|
||||
this._updateTooltipLocation.cancel();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
_showTooltip = () => {
|
||||
const instructions = this.props.drawState.drawType === DRAW_TYPE.BOUNDS
|
||||
? i18n.translate('xpack.maps.drawTooltip.boundsInstructions', {
|
||||
defaultMessage: 'Click to start rectangle. Click again to finish.'
|
||||
|
@ -51,31 +50,40 @@ export class DrawTooltip extends Component {
|
|||
: i18n.translate('xpack.maps.drawTooltip.polygonInstructions', {
|
||||
defaultMessage: 'Click to add vertex. Double click to finish.'
|
||||
});
|
||||
ReactDOM.render((
|
||||
<I18nProvider>
|
||||
<div>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{instructions}
|
||||
</EuiText>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
), this._tooltipContainer);
|
||||
|
||||
this._mbPopup.setDOMContent(this._tooltipContainer)
|
||||
.addTo(this.props.mbMap);
|
||||
const tooltipAnchor = <div style={{ height: '26px', width: '26px', background: 'transparent' }}/>;
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="drawInstructionsTooltip"
|
||||
button={tooltipAnchor}
|
||||
anchorPosition="rightCenter"
|
||||
isOpen={this.state.isOpen}
|
||||
closePopover={noop}
|
||||
ref={this._popoverRef}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
transform: `translate(${this.state.x - 13}px, ${this.state.y - 13}px)`
|
||||
}}
|
||||
>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{instructions}
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
_hideTooltip = () => {
|
||||
if (this._mbPopup.isOpen()) {
|
||||
this._mbPopup.remove();
|
||||
ReactDOM.unmountComponentAtNode(this._tooltipContainer);
|
||||
}
|
||||
this._updateTooltipLocation.cancel();
|
||||
this.setState({ isOpen: false });
|
||||
}
|
||||
|
||||
_updateTooltipLocation = _.throttle(({ lngLat }) => {
|
||||
if (!this._mbPopup.isOpen()) {
|
||||
this._showTooltip();
|
||||
}
|
||||
this._mbPopup.setLngLat(lngLat);
|
||||
const mouseLocation = this.props.mbMap.project(lngLat);
|
||||
this.setState({
|
||||
isOpen: true,
|
||||
x: mouseLocation.x,
|
||||
y: mouseLocation.y,
|
||||
});
|
||||
}, 100)
|
||||
}
|
||||
|
|
|
@ -6,20 +6,21 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import {
|
||||
FEATURE_ID_PROPERTY_NAME,
|
||||
LAT_INDEX,
|
||||
LON_INDEX,
|
||||
} from '../../../../../common/constants';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { FeatureTooltip } from '../../feature_tooltip';
|
||||
import { EuiPopover, EuiText } from '@elastic/eui';
|
||||
|
||||
const TOOLTIP_TYPE = {
|
||||
HOVER: 'HOVER',
|
||||
LOCKED: 'LOCKED'
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
function justifyAnchorLocation(mbLngLat, targetFeature) {
|
||||
let popupAnchorLocation = [mbLngLat.lng, mbLngLat.lat]; // default popup location to mouse location
|
||||
if (targetFeature.geometry.type === 'Point') {
|
||||
|
@ -39,34 +40,48 @@ function justifyAnchorLocation(mbLngLat, targetFeature) {
|
|||
|
||||
export class TooltipControl extends React.Component {
|
||||
|
||||
state = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._tooltipContainer = document.createElement('div');
|
||||
this._mbPopup = new mapboxgl.Popup({
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
maxWidth: '260px', // width of table columns max-widths plus all padding
|
||||
});
|
||||
this._popoverRef = React.createRef();
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.tooltipState) {
|
||||
const nextPoint = nextProps.mbMap.project(nextProps.tooltipState.location);
|
||||
if (nextPoint.x !== prevState.x || nextPoint.y !== prevState.y) {
|
||||
return {
|
||||
x: nextPoint.x,
|
||||
y: nextPoint.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
|
||||
this.props.mbMap.on('mouseout', this._onMouseout);
|
||||
this.props.mbMap.on('mousemove', this._updateHoverTooltipState);
|
||||
this.props.mbMap.on('move', this._updatePopoverPosition);
|
||||
this.props.mbMap.on('click', this._lockTooltip);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._syncTooltipState();
|
||||
if (this.props.tooltipState && this._popoverRef.current) {
|
||||
this._popoverRef.current.positionPopoverFluid();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
this.props.mbMap.off('mouseout', this._onMouseout);
|
||||
this.props.mbMap.off('mousemove', this._updateHoverTooltipState);
|
||||
this.props.mbMap.off('move', this._updatePopoverPosition);
|
||||
this.props.mbMap.off('click', this._lockTooltip);
|
||||
this._hideTooltip();
|
||||
}
|
||||
|
||||
_onMouseout = () => {
|
||||
|
@ -76,6 +91,29 @@ export class TooltipControl extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_updatePopoverPosition = () => {
|
||||
if (!this.props.tooltipState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lat = this.props.tooltipState.location[LAT_INDEX];
|
||||
const lon = this.props.tooltipState.location[LON_INDEX];
|
||||
const bounds = this.props.mbMap.getBounds();
|
||||
if (lat > bounds.getNorth()
|
||||
|| lat < bounds.getSouth()
|
||||
|| lon < bounds.getWest()
|
||||
|| lon > bounds.getEast()) {
|
||||
this.props.clearTooltipState();
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPoint = this.props.mbMap.project(this.props.tooltipState.location);
|
||||
this.setState({
|
||||
x: nextPoint.x,
|
||||
y: nextPoint.y
|
||||
});
|
||||
}
|
||||
|
||||
_getLayerByMbLayerId(mbLayerId) {
|
||||
return this.props.layerList.find((layer) => {
|
||||
const mbLayerIds = layer.getMbLayerIds();
|
||||
|
@ -113,7 +151,6 @@ export class TooltipControl extends React.Component {
|
|||
}
|
||||
|
||||
_lockTooltip = (e) => {
|
||||
|
||||
if (this.props.isDrawingFilter) {
|
||||
//ignore click events when in draw mode
|
||||
return;
|
||||
|
@ -139,7 +176,6 @@ export class TooltipControl extends React.Component {
|
|||
};
|
||||
|
||||
_updateHoverTooltipState = _.debounce((e) => {
|
||||
|
||||
if (this.props.isDrawingFilter) {
|
||||
//ignore hover events when in draw mode
|
||||
return;
|
||||
|
@ -175,7 +211,6 @@ export class TooltipControl extends React.Component {
|
|||
}, 100);
|
||||
|
||||
_getMbLayerIdsForTooltips() {
|
||||
|
||||
const mbLayerIds = this.props.layerList.reduce((mbLayerIds, layer) => {
|
||||
return layer.canShowTooltip() ? mbLayerIds.concat(layer.getMbLayerIds()) : mbLayerIds;
|
||||
}, []);
|
||||
|
@ -191,7 +226,6 @@ export class TooltipControl extends React.Component {
|
|||
}
|
||||
|
||||
_getFeaturesUnderPointer(mbLngLatPoint) {
|
||||
|
||||
if (!this.props.mbMap) {
|
||||
return [];
|
||||
}
|
||||
|
@ -211,49 +245,6 @@ export class TooltipControl extends React.Component {
|
|||
return this.props.mbMap.queryRenderedFeatures(mbBbox, { layers: mbLayerIds });
|
||||
}
|
||||
|
||||
_reevaluateTooltipPosition = () => {
|
||||
// Force mapbox to ensure tooltip does not clip map boundary and move anchor when clipping occurs
|
||||
requestAnimationFrame(() => {
|
||||
if (this._isMounted && this.props.tooltipState && this.props.tooltipState.location) {
|
||||
this._mbPopup.setLngLat(this.props.tooltipState.location);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_hideTooltip() {
|
||||
if (this._mbPopup.isOpen()) {
|
||||
this._mbPopup.remove();
|
||||
ReactDOM.unmountComponentAtNode(this._tooltipContainer);
|
||||
}
|
||||
}
|
||||
|
||||
_showTooltip() {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
const isLocked = this.props.tooltipState.type === TOOLTIP_TYPE.LOCKED;
|
||||
ReactDOM.render((
|
||||
<I18nProvider>
|
||||
<FeatureTooltip
|
||||
features={this.props.tooltipState.features}
|
||||
anchorLocation={this.props.tooltipState.location}
|
||||
loadFeatureProperties={this._loadFeatureProperties}
|
||||
findLayerById={this._findLayerById}
|
||||
closeTooltip={this.props.clearTooltipState}
|
||||
showFilterButtons={!!this.props.addFilters && isLocked}
|
||||
isLocked={isLocked}
|
||||
addFilters={this.props.addFilters}
|
||||
geoFields={this.props.geoFields}
|
||||
reevaluateTooltipPosition={this._reevaluateTooltipPosition}
|
||||
/>
|
||||
</I18nProvider>
|
||||
), this._tooltipContainer);
|
||||
|
||||
this._mbPopup.setLngLat(this.props.tooltipState.location)
|
||||
.setDOMContent(this._tooltipContainer)
|
||||
.addTo(this.props.mbMap);
|
||||
}
|
||||
|
||||
_loadFeatureProperties = async ({ layerId, featureId }) => {
|
||||
const tooltipLayer = this._findLayerById(layerId);
|
||||
if (!tooltipLayer) {
|
||||
|
@ -272,17 +263,41 @@ export class TooltipControl extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
_syncTooltipState() {
|
||||
if (this.props.tooltipState) {
|
||||
this.props.mbMap.getCanvas().style.cursor = 'pointer';
|
||||
this._showTooltip();
|
||||
} else {
|
||||
this.props.mbMap.getCanvas().style.cursor = '';
|
||||
this._hideTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
if (!this.props.tooltipState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tooltipAnchor = <div style={{ height: '26px', width: '26px', background: 'transparent' }}/>;
|
||||
const isLocked = this.props.tooltipState.type === TOOLTIP_TYPE.LOCKED;
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="mapTooltip"
|
||||
button={tooltipAnchor}
|
||||
anchorPosition="upCenter"
|
||||
isOpen
|
||||
closePopover={noop}
|
||||
ref={this._popoverRef}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
transform: `translate(${this.state.x - 13}px, ${this.state.y - 13}px)`
|
||||
}}
|
||||
>
|
||||
<EuiText size="xs">
|
||||
<FeatureTooltip
|
||||
features={this.props.tooltipState.features}
|
||||
anchorLocation={this.props.tooltipState.location}
|
||||
loadFeatureProperties={this._loadFeatureProperties}
|
||||
findLayerById={this._findLayerById}
|
||||
closeTooltip={this.props.clearTooltipState}
|
||||
showFilterButtons={!!this.props.addFilters && isLocked}
|
||||
isLocked={isLocked}
|
||||
addFilters={this.props.addFilters}
|
||||
geoFields={this.props.geoFields}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue