[Maps] convert MBMap component to TS (#85192)

* [Maps] convert MBMap component to TS

* clean up imports messed up by eslint fix

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2020-12-16 11:21:04 -07:00 committed by GitHub
parent 0921a7a1e1
commit 4b2c9de638
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 57 deletions

View file

@ -51,6 +51,7 @@ import { addLayer, addLayerWithoutDataSync } from './layer_actions';
import { MapSettings } from '../reducers/map';
import {
DrawState,
MapCenter,
MapCenterAndZoom,
MapExtent,
MapRefreshConfig,
@ -59,6 +60,12 @@ import { INITIAL_LOCATION } from '../../common/constants';
import { scaleBounds } from '../../common/elasticsearch_util';
import { cleanTooltipStateForLayer } from './tooltip_actions';
export interface MapExtentState {
zoom: number;
extent: MapExtent;
center: MapCenter;
}
export function setMapInitError(errorMessage: string) {
return {
type: SET_MAP_INIT_ERROR,
@ -125,13 +132,13 @@ export function mapDestroyed() {
};
}
export function mapExtentChanged(newMapConstants: { zoom: number; extent: MapExtent }) {
export function mapExtentChanged(mapExtentState: MapExtentState) {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const dataFilters = getDataFilters(getState());
const { extent, zoom: newZoom } = newMapConstants;
const { extent, zoom: newZoom } = mapExtentState;
const { buffer, zoom: currentZoom } = dataFilters;
if (extent) {
@ -162,7 +169,7 @@ export function mapExtentChanged(newMapConstants: { zoom: number; extent: MapExt
type: MAP_EXTENT_CHANGED,
mapState: {
...dataFilters,
...newMapConstants,
...mapExtentState,
},
});

View file

@ -12,7 +12,6 @@ import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { Filter } from 'src/plugins/data/public';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
// @ts-expect-error
import { MBMap } from '../mb_map';
// @ts-expect-error
import { WidgetOverlay } from '../widget_overlay';

View file

@ -9,7 +9,7 @@ import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types';
import { MapSettings } from '../../reducers/map';
export async function getInitialView(
goto: Goto | null,
goto: Goto | null | undefined,
settings: MapSettings
): Promise<MapCenterAndZoom | null> {
if (settings.initialLocation === INITIAL_LOCATION.FIXED_LOCATION) {

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { AnyAction } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { connect } from 'react-redux';
import { MBMap } from './mb_map';
import {
@ -14,6 +16,7 @@ import {
clearMouseCoordinates,
clearGoto,
setMapInitError,
MapExtentState,
} from '../../actions';
import {
getLayerList,
@ -26,10 +29,10 @@ import {
getSpatialFiltersLayer,
getMapSettings,
} from '../../selectors/map_selectors';
import { getInspectorAdapters } from '../../reducers/non_serializable_instances';
import { MapStoreState } from '../../reducers/store';
function mapStateToProps(state = {}) {
function mapStateToProps(state: MapStoreState) {
return {
isMapReady: getMapReady(state),
settings: getMapSettings(state),
@ -44,20 +47,20 @@ function mapStateToProps(state = {}) {
};
}
function mapDispatchToProps(dispatch) {
function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) {
return {
extentChanged: (e) => {
dispatch(mapExtentChanged(e));
extentChanged: (mapExtentState: MapExtentState) => {
dispatch(mapExtentChanged(mapExtentState));
},
onMapReady: (e) => {
onMapReady: (mapExtentState: MapExtentState) => {
dispatch(clearGoto());
dispatch(mapExtentChanged(e));
dispatch(mapExtentChanged(mapExtentState));
dispatch(mapReady());
},
onMapDestroyed: () => {
dispatch(mapDestroyed());
},
setMouseCoordinates: ({ lat, lon }) => {
setMouseCoordinates: ({ lat, lon }: { lat: number; lon: number }) => {
dispatch(setMouseCoordinates({ lat, lon }));
},
clearMouseCoordinates: () => {
@ -66,7 +69,7 @@ function mapDispatchToProps(dispatch) {
clearGoto: () => {
dispatch(clearGoto());
},
setMapInitError(errorMessage) {
setMapInitError(errorMessage: string) {
dispatch(setMapInitError(errorMessage));
},
};

View file

@ -5,39 +5,93 @@
*/
import _ from 'lodash';
import React from 'react';
import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public';
import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils';
import { syncLayerOrder } from './sort_layers';
import { getGlyphUrl, isRetina } from '../../meta';
import {
DECIMAL_DEGREES_PRECISION,
KBN_TOO_MANY_FEATURES_IMAGE_ID,
ZOOM_PRECISION,
} from '../../../common/constants';
import React, { Component } from 'react';
import { Map as MapboxMap, MapboxOptions, MapMouseEvent } from 'mapbox-gl';
// @ts-expect-error
import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp';
import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker';
import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js';
// @ts-expect-error
import { spritesheet } from '@elastic/maki';
import sprites1 from '@elastic/maki/dist/sprite@1.png';
import sprites2 from '@elastic/maki/dist/sprite@2.png';
import { Adapters } from 'src/plugins/inspector/public';
import { Filter } from 'src/plugins/data/public';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
// @ts-expect-error
import { DrawControl } from './draw_control';
// @ts-expect-error
import { TooltipControl } from './tooltip_control';
import { clampToLatBounds, clampToLonBounds } from '../../../common/elasticsearch_util';
import { getInitialView } from './get_initial_view';
import { getPreserveDrawingBuffer } from '../../kibana_services';
import { ILayer } from '../../classes/layers/layer';
import { MapSettings } from '../../reducers/map';
import { Goto } from '../../../common/descriptor_types';
import {
DECIMAL_DEGREES_PRECISION,
KBN_TOO_MANY_FEATURES_IMAGE_ID,
RawValue,
ZOOM_PRECISION,
} from '../../../common/constants';
import { getGlyphUrl, isRetina } from '../../meta';
import { syncLayerOrder } from './sort_layers';
// @ts-expect-error
import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils';
import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public';
import { GeoFieldWithIndex } from '../../components/geo_field_with_index';
import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property';
import { MapExtentState } from '../../actions';
// @ts-expect-error
import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js';
// @ts-expect-error
import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker';
mapboxgl.workerUrl = mbWorkerUrl;
mapboxgl.setRTLTextPlugin(mbRtlPlugin);
export class MBMap extends React.Component {
state = {
interface Props {
isMapReady: boolean;
settings: MapSettings;
layerList: ILayer[];
spatialFiltersLayer: ILayer;
goto?: Goto | null;
inspectorAdapters: Adapters;
scrollZoom: boolean;
disableInteractive: boolean;
disableTooltipControl: boolean;
hideViewControl: boolean;
extentChanged: (mapExtentState: MapExtentState) => void;
onMapReady: (mapExtentState: MapExtentState) => void;
onMapDestroyed: () => void;
setMouseCoordinates: ({ lat, lon }: { lat: number; lon: number }) => void;
clearMouseCoordinates: () => void;
clearGoto: () => void;
setMapInitError: (errorMessage: string) => void;
addFilters: ((filters: Filter[]) => Promise<void>) | null;
getFilterActions?: () => Promise<Action[]>;
getActionContext?: () => ActionExecutionContext;
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
geoFields: GeoFieldWithIndex[];
renderTooltipContent?: RenderToolTipContent;
}
interface State {
prevLayerList: ILayer[] | undefined;
hasSyncedLayerList: boolean;
mbMap: MapboxMap | undefined;
}
export class MBMap extends Component<Props, State> {
private _checker?: ResizeChecker;
private _isMounted: boolean = false;
private _containerRef: HTMLDivElement | null = null;
state: State = {
prevLayerList: undefined,
hasSyncedLayerList: false,
mbMap: undefined,
};
static getDerivedStateFromProps(nextProps, prevState) {
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const nextLayerList = nextProps.layerList;
if (nextLayerList !== prevState.prevLayerList) {
return {
@ -69,13 +123,13 @@ export class MBMap extends React.Component {
}
if (this.state.mbMap) {
this.state.mbMap.remove();
this.state.mbMap = null;
this.state.mbMap = undefined;
}
this.props.onMapDestroyed();
}
_debouncedSync = _.debounce(() => {
if (this._isMounted && this.props.isMapReady) {
if (this._isMounted && this.props.isMapReady && this.state.mbMap) {
if (!this.state.hasSyncedLayerList) {
this.setState(
{
@ -93,9 +147,9 @@ export class MBMap extends React.Component {
}, 256);
_getMapState() {
const zoom = this.state.mbMap.getZoom();
const mbCenter = this.state.mbMap.getCenter();
const mbBounds = this.state.mbMap.getBounds();
const zoom = this.state.mbMap!.getZoom();
const mbCenter = this.state.mbMap!.getCenter();
const mbBounds = this.state.mbMap!.getBounds();
return {
zoom: _.round(zoom, ZOOM_PRECISION),
center: {
@ -111,7 +165,7 @@ export class MBMap extends React.Component {
};
}
async _createMbMapInstance() {
async _createMbMapInstance(): Promise<MapboxMap> {
const initialView = await getInitialView(this.props.goto, this.props.settings);
return new Promise((resolve) => {
const mbStyle = {
@ -121,9 +175,9 @@ export class MBMap extends React.Component {
glyphs: getGlyphUrl(),
};
const options = {
const options: MapboxOptions = {
attributionControl: false,
container: this.refs.mapContainer,
container: this._containerRef!,
style: mbStyle,
scrollZoom: this.props.scrollZoom,
preserveDrawingBuffer: getPreserveDrawingBuffer(),
@ -155,9 +209,10 @@ export class MBMap extends React.Component {
};
tooManyFeaturesImage.src = tooManyFeaturesImageSrc;
let emptyImage;
mbMap.on('styleimagemissing', (e) => {
let emptyImage: HTMLImageElement;
mbMap.on('styleimagemissing', (e: unknown) => {
if (emptyImage) {
// @ts-expect-error
mbMap.addImage(e.id, emptyImage);
}
});
@ -173,7 +228,7 @@ export class MBMap extends React.Component {
}
async _initializeMap() {
let mbMap;
let mbMap: MapboxMap;
try {
mbMap = await this._createMbMapInstance();
} catch (error) {
@ -186,19 +241,19 @@ export class MBMap extends React.Component {
}
this.setState({ mbMap }, () => {
this._loadMakiSprites();
this._loadMakiSprites(mbMap);
this._initResizerChecker();
this._registerMapEventListeners();
this._registerMapEventListeners(mbMap);
this.props.onMapReady(this._getMapState());
});
}
_registerMapEventListeners() {
_registerMapEventListeners(mbMap: MapboxMap) {
// moveend callback is debounced to avoid updating map extent state while map extent is still changing
// moveend is fired while the map extent is still changing in the following scenarios
// 1) During opening/closing of layer details panel, the EUI animation results in 8 moveend events
// 2) Setting map zoom and center from goto is done in 2 API calls, resulting in 2 moveend events
this.state.mbMap.on(
mbMap.on(
'moveend',
_.debounce(() => {
this.props.extentChanged(this._getMapState());
@ -206,14 +261,14 @@ export class MBMap extends React.Component {
);
// Attach event only if view control is visible, which shows lat/lon
if (!this.props.hideViewControl) {
const throttledSetMouseCoordinates = _.throttle((e) => {
const throttledSetMouseCoordinates = _.throttle((e: MapMouseEvent) => {
this.props.setMouseCoordinates({
lat: e.lngLat.lat,
lon: e.lngLat.lng,
});
}, 100);
this.state.mbMap.on('mousemove', throttledSetMouseCoordinates);
this.state.mbMap.on('mouseout', () => {
mbMap.on('mousemove', throttledSetMouseCoordinates);
mbMap.on('mouseout', () => {
throttledSetMouseCoordinates.cancel(); // cancel any delayed setMouseCoordinates invocations
this.props.clearMouseCoordinates();
});
@ -221,29 +276,31 @@ export class MBMap extends React.Component {
}
_initResizerChecker() {
this._checker = new ResizeChecker(this.refs.mapContainer);
this._checker = new ResizeChecker(this._containerRef!);
this._checker.on('resize', () => {
this.state.mbMap.resize();
if (this.state.mbMap) {
this.state.mbMap.resize();
}
});
}
_loadMakiSprites() {
_loadMakiSprites(mbMap: MapboxMap) {
const sprites = isRetina() ? sprites2 : sprites1;
const json = isRetina() ? spritesheet[2] : spritesheet[1];
addSpritesheetToMap(json, sprites, this.state.mbMap);
addSpritesheetToMap(json, sprites, mbMap);
}
_syncMbMapWithMapState = () => {
const { isMapReady, goto, clearGoto } = this.props;
if (!isMapReady || !goto) {
if (!isMapReady || !goto || !this.state.mbMap) {
return;
}
clearGoto();
if (goto.bounds) {
//clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90)
// clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90)
const lnLatBounds = new mapboxgl.LngLatBounds(
new mapboxgl.LngLat(
clampToLonBounds(goto.bounds.minLon),
@ -254,8 +311,8 @@ export class MBMap extends React.Component {
clampToLatBounds(goto.bounds.maxLat)
)
);
//maxZoom ensure we're not zooming in too far on single points or small shapes
//the padding is to avoid too tight of a fit around edges
// maxZoom ensure we're not zooming in too far on single points or small shapes
// the padding is to avoid too tight of a fit around edges
this.state.mbMap.fitBounds(lnLatBounds, { maxZoom: 17, padding: 16 });
} else if (goto.center) {
this.state.mbMap.setZoom(goto.center.zoom);
@ -267,6 +324,10 @@ export class MBMap extends React.Component {
};
_syncMbMapWithLayerList = () => {
if (!this.state.mbMap) {
return;
}
removeOrphanedSourcesAndLayers(
this.state.mbMap,
this.props.layerList,
@ -277,7 +338,7 @@ export class MBMap extends React.Component {
};
_syncMbMapWithInspector = () => {
if (!this.props.inspectorAdapters.map) {
if (!this.props.inspectorAdapters.map || !this.state.mbMap) {
return;
}
@ -292,6 +353,10 @@ export class MBMap extends React.Component {
};
_syncSettings() {
if (!this.state.mbMap) {
return;
}
let zoomRangeChanged = false;
if (this.props.settings.minZoom !== this.state.mbMap.getMinZoom()) {
this.state.mbMap.setMinZoom(this.props.settings.minZoom);
@ -312,6 +377,10 @@ export class MBMap extends React.Component {
}
}
_setContainerRef = (element: HTMLDivElement) => {
this._containerRef = element;
};
render() {
let drawControl;
let tooltipControl;
@ -333,7 +402,7 @@ export class MBMap extends React.Component {
<div
id="mapContainer"
className="mapContainer"
ref="mapContainer"
ref={this._setContainerRef}
data-test-subj="mapContainer"
>
{drawControl}