[Maps] Move set view to toolbar control, show zoom level in view control (#38925)

* break out ToolsControl from ToolbarOverlay component

* move set view control to toolbar overlay component

* fix i18n

* use cchaos approved top calculation in scss

* fix functional tests by updating lat,lon, and zoom state when props change

* do not set state from props in constructor, only set state from props in getDerivedStateFromProps

* provide default value for isSetViewOpen in store

* just pass needed params to connect
This commit is contained in:
Nathan Reese 2019-06-19 10:53:25 -06:00 committed by GitHub
parent 1ca0953832
commit 7b5b6e4e02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 505 additions and 458 deletions

View file

@ -1,6 +1,6 @@
.mapToolbarOverlay {
position: absolute;
top: ($euiSizeM * 2) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin
top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin
left: $euiSizeM;
z-index: 2; // Sit on top of mapbox controls shadow
}
@ -8,6 +8,7 @@
.mapToolbarOverlay__button {
@include size($euiSizeXL);
background-color: $euiColorEmptyShade !important;
pointer-events: all;
&:enabled,
&:enabled:hover,

View file

@ -5,27 +5,16 @@
*/
import { connect } from 'react-redux';
import { ToolbarOverlay } from './view';
import { getDrawState, getUniqueIndexPatternIds } from '../../selectors/map_selectors';
import { ToolbarOverlay } from './toolbar_overlay';
import { getUniqueIndexPatternIds } from '../../selectors/map_selectors';
import { getIsFilterable } from '../../store/ui';
import { updateDrawState } from '../../actions/store_actions';
function mapStateToProps(state = {}) {
return {
isFilterable: getIsFilterable(state),
drawState: getDrawState(state),
uniqueIndexPatternIds: getUniqueIndexPatternIds(state)
};
}
function mapDispatchToProps(dispatch) {
return {
initiateDraw: (options) => {
dispatch(updateDrawState(options));
}
};
}
const connectedToolbarOverlay = connect(mapStateToProps, mapDispatchToProps)(ToolbarOverlay);
const connectedToolbarOverlay = connect(mapStateToProps, null)(ToolbarOverlay);
export { connectedToolbarOverlay as ToolbarOverlay };

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import { SetViewControl } from './set_view_control';
import { setGotoWithCenter } from '../../../actions/store_actions';
import { getMapZoom, getMapCenter } from '../../../selectors/map_selectors';
import {
getIsSetViewOpen,
closeSetView,
openSetView,
} from '../../../store/ui';
function mapStateToProps(state = {}) {
return {
isSetViewOpen: getIsSetViewOpen(state),
zoom: getMapZoom(state),
center: getMapCenter(state),
};
}
function mapDispatchToProps(dispatch) {
return {
onSubmit: ({ lat, lon, zoom }) => {
dispatch(closeSetView());
dispatch(setGotoWithCenter({ lat, lon, zoom }));
},
closeSetView: () => {
dispatch(closeSetView());
},
openSetView: () => {
dispatch(openSetView());
}
};
}
const connectedSetViewControl = connect(mapStateToProps, mapDispatchToProps)(SetViewControl);
export { connectedSetViewControl as SetViewControl };

View file

@ -4,23 +4,49 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
EuiForm,
EuiFormRow,
EuiButton,
EuiFieldNumber,
EuiButtonIcon,
EuiPopover,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export class SetView extends React.Component {
function getViewString(lat, lon, zoom) {
return `${lat},${lon},${zoom}`;
}
state = {
lat: this.props.center.lat,
lon: this.props.center.lon,
zoom: this.props.zoom,
export class SetViewControl extends Component {
state = {}
static getDerivedStateFromProps(nextProps, prevState) {
const nextView = getViewString(nextProps.center.lat, nextProps.center.lon, nextProps.zoom);
if (nextView !== prevState.prevView) {
return {
lat: nextProps.center.lat,
lon: nextProps.center.lon,
zoom: nextProps.zoom,
prevView: nextView,
};
}
return null;
}
_togglePopover = () => {
if (this.props.isSetViewOpen) {
this.props.closeSetView();
return;
}
this.props.openSetView();
};
_onLatChange = evt => {
this._onChange('lat', evt);
};
@ -63,7 +89,7 @@ export class SetView extends React.Component {
};
}
onSubmit = () => {
_onSubmit = () => {
const {
lat,
lon,
@ -72,7 +98,7 @@ export class SetView extends React.Component {
this.props.onSubmit({ lat, lon, zoom });
}
render() {
_renderSetViewForm() {
const { isInvalid: isLatInvalid, component: latFormRow } = this._renderNumberFormRow({
value: this.state.lat,
min: -90,
@ -113,7 +139,7 @@ export class SetView extends React.Component {
<EuiButton
size="s"
disabled={isLatInvalid || isLonInvalid || isZoomInvalid}
onClick={this.onSubmit}
onClick={this._onSubmit}
data-test-subj="submitViewButton"
>
Go
@ -123,13 +149,43 @@ export class SetView extends React.Component {
</EuiForm>
);
}
render() {
return (
<EuiPopover
anchorPosition="leftUp"
button={(
<EuiButtonIcon
className="mapToolbarOverlay__button"
onClick={this._togglePopover}
data-test-subj="toggleSetViewVisibilityButton"
iconType="crosshairs"
color="text"
aria-label={i18n.translate('xpack.maps.viewControl.goToButtonLabel', {
defaultMessage: 'Go to'
})}
title={i18n.translate('xpack.maps.viewControl.goToButtonLabel', {
defaultMessage: 'Go to'
})}
/>
)}
isOpen={this.props.isSetViewOpen}
closePopover={this.props.closeSetView}
>
{this._renderSetViewForm()}
</EuiPopover>
);
}
}
SetView.propTypes = {
SetViewControl.propTypes = {
isSetViewOpen: PropTypes.bool.isRequired,
zoom: PropTypes.number.isRequired,
center: PropTypes.shape({
lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired
}),
onSubmit: PropTypes.func.isRequired,
closeSetView: PropTypes.func.isRequired,
openSetView: PropTypes.func.isRequired,
};

View file

@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import React from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { getIndexPatternsFromIds } from '../../index_pattern_util';
import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
import { SetViewControl } from './set_view_control';
import { ToolsControl } from './tools_control';
export class ToolbarOverlay extends React.Component {
state = {
prevUniqueIndexPatternIds: [],
uniqueIndexPatternsAndGeoFields: [],
};
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
componentDidUpdate() {
if (this.props.isFilterable) {
const nextUniqueIndexPatternIds = _.get(this.props, 'uniqueIndexPatternIds', []);
this._loadUniqueIndexPatternAndFieldCombos(nextUniqueIndexPatternIds);
}
}
_loadUniqueIndexPatternAndFieldCombos = async (nextUniqueIndexPatternIds) => {
if (_.isEqual(nextUniqueIndexPatternIds, this.state.prevUniqueIndexPatternIds)) {
// all ready loaded index pattern ids
return;
}
this.setState({
prevUniqueIndexPatternIds: nextUniqueIndexPatternIds,
});
const uniqueIndexPatternsAndGeoFields = [];
try {
const indexPatterns = await getIndexPatternsFromIds(nextUniqueIndexPatternIds);
indexPatterns.forEach((indexPattern) => {
indexPattern.fields.forEach(field => {
if (field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) {
uniqueIndexPatternsAndGeoFields.push({
geoField: field.name,
geoFieldType: field.type,
indexPatternTitle: indexPattern.title,
indexPatternId: indexPattern.id
});
}
});
});
} catch(e) {
// swallow errors.
// the Layer-TOC will indicate which layers are disfunctional on a per-layer basis
}
if (!this._isMounted) {
return;
}
this.setState({ uniqueIndexPatternsAndGeoFields });
}
_renderToolsControl() {
const { uniqueIndexPatternsAndGeoFields } = this.state;
if (
!this.props.isFilterable ||
!uniqueIndexPatternsAndGeoFields.length
) {
return null;
}
return (
<EuiFlexItem>
<ToolsControl
uniqueIndexPatternsAndGeoFields={uniqueIndexPatternsAndGeoFields}
/>
</EuiFlexItem>
);
}
render() {
return (
<EuiFlexGroup
className="mapToolbarOverlay"
responsive={false}
direction="column"
alignItems="flexEnd"
gutterSize="s"
>
<EuiFlexItem>
<SetViewControl />
</EuiFlexItem>
{this._renderToolsControl()}
</EuiFlexGroup>
);
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import { ToolsControl } from './tools_control';
import { getDrawState } from '../../../selectors/map_selectors';
import { updateDrawState } from '../../../actions/store_actions';
function mapStateToProps(state = {}) {
return {
drawState: getDrawState(state),
};
}
function mapDispatchToProps(dispatch) {
return {
initiateDraw: (options) => {
dispatch(updateDrawState(options));
}
};
}
const connectedToolsControl = connect(mapStateToProps, mapDispatchToProps)(ToolsControl);
export { connectedToolsControl as ToolsControl };

View file

@ -0,0 +1,216 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component, Fragment } from 'react';
import {
EuiButtonIcon,
EuiPopover,
EuiContextMenu,
EuiSelectable,
EuiHighlight,
EuiTextColor,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DRAW_TYPE } from '../../../actions/store_actions';
const RESET_STATE = {
isPopoverOpen: false,
drawType: null
};
export class ToolsControl extends Component {
state = {
...RESET_STATE
};
_togglePopover = () => {
this.setState(prevState => ({
...RESET_STATE,
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
_closePopover = () => {
this.setState(RESET_STATE);
};
_onIndexPatternSelection = (options) => {
const selection = options.find((option) => option.checked);
this._initiateDraw(
this.state.drawType,
selection.value
);
};
_initiateDraw = (drawType, indexContext) => {
this.props.initiateDraw({
drawType,
...indexContext
});
this._closePopover();
};
_selectPolygonDrawType = () => {
this.setState({ drawType: DRAW_TYPE.POLYGON });
}
_selectBoundsDrawType = () => {
this.setState({ drawType: DRAW_TYPE.BOUNDS });
}
_getDrawPanels() {
const needsIndexPatternSelectionPanel = this.props.uniqueIndexPatternsAndGeoFields.length > 1;
const drawPolygonAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', {
defaultMessage: 'Draw shape to filter data',
}),
onClick: needsIndexPatternSelectionPanel
? this._selectPolygonDrawType
: () => {
this._initiateDraw(DRAW_TYPE.POLYGON, this.props.uniqueIndexPatternsAndGeoFields[0]);
},
panel: needsIndexPatternSelectionPanel
? this._getIndexPatternSelectionPanel(1)
: undefined
};
const drawBoundsAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', {
defaultMessage: 'Draw bounds to filter data',
}),
onClick: needsIndexPatternSelectionPanel
? this._selectBoundsDrawType
: () => {
this._initiateDraw(DRAW_TYPE.BOUNDS, this.props.uniqueIndexPatternsAndGeoFields[0]);
},
panel: needsIndexPatternSelectionPanel
? this._getIndexPatternSelectionPanel(2)
: undefined
};
return flattenPanelTree({
id: 0,
title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', {
defaultMessage: 'Tools',
}),
items: [drawPolygonAction, drawBoundsAction]
});
}
_getIndexPatternSelectionPanel(id) {
const options = this.props.uniqueIndexPatternsAndGeoFields.map((indexPatternAndGeoField) => {
return {
label: `${indexPatternAndGeoField.indexPatternTitle} : ${indexPatternAndGeoField.geoField}`,
value: indexPatternAndGeoField
};
});
const renderGeoField = (option, searchValue) => {
return (
<Fragment>
<EuiTextColor color="subdued">
<small>
<EuiHighlight search={searchValue}>{option.value.indexPatternTitle}</EuiHighlight>
</small>
</EuiTextColor>
<br />
<EuiHighlight search={searchValue}>
{option.value.geoField}
</EuiHighlight>
</Fragment>
);
};
const indexPatternSelection = (
<EuiSelectable
searchable
searchProps={{
placeholder: i18n.translate('xpack.maps.toolbarOverlay.indexPattern.filterListTitle', {
defaultMessage: 'Filter list',
}),
compressed: true,
}}
options={options}
onChange={this._onIndexPatternSelection}
renderOption={renderGeoField}
listProps={{
rowHeight: 50,
showIcons: false,
}}
>
{(list, search) => (
<div>
{search}
{list}
</div>
)}
</EuiSelectable>
);
return {
id: id,
title: i18n.translate('xpack.maps.toolbarOverlay.geofield.toolbarTitle', {
defaultMessage: 'Select geo field',
}),
content: indexPatternSelection
};
}
_renderToolsButton() {
return (
<EuiButtonIcon
className="mapToolbarOverlay__button"
color="text"
iconType="wrench"
onClick={this._togglePopover}
aria-label={i18n.translate('xpack.maps.toolbarOverlay.toolsControlTitle', {
defaultMessage: 'Tools',
})}
title={i18n.translate('xpack.maps.toolbarOverlay.toolsControlTitle', {
defaultMessage: 'Tools',
})}
/>
);
}
render() {
return (
<EuiPopover
id="contextMenu"
button={this._renderToolsButton()}
isOpen={this.state.isPopoverOpen}
closePopover={this._closePopover}
panelPaddingSize="none"
withTitle
anchorPosition="leftUp"
>
<EuiContextMenu
initialPanelId={0}
panels={this._getDrawPanels()}
/>
</EuiPopover>
);
}
}
function flattenPanelTree(tree, array = []) {
array.push(tree);
if (tree.items) {
tree.items.forEach(item => {
if (item.panel) {
flattenPanelTree(item.panel, array);
item.panel = item.panel.id;
}
});
}
return array;
}

View file

@ -1,282 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
EuiPopover,
EuiContextMenu,
EuiSelectable,
EuiHighlight,
EuiTextColor,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getIndexPatternsFromIds } from '../../index_pattern_util';
import _ from 'lodash';
import { DRAW_TYPE } from '../../actions/store_actions';
import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
const RESET_STATE = {
isPopoverOpen: false,
drawType: null
};
export class ToolbarOverlay extends React.Component {
state = {
isPopoverOpen: false,
uniqueIndexPatternsAndGeoFields: [],
drawType: null
};
_toggleToolbar = () => {
if (!this._isMounted) {
return;
}
this.setState(prevState => ({
isPopoverOpen: !prevState.isPopoverOpen,
drawType: null
}));
};
_closePopover = () => {
if (!this._isMounted) {
return;
}
this.setState(RESET_STATE);
};
_onIndexPatternSelection = (options) => {
if (!this._isMounted) {
return;
}
const selection = options.find((option) => option.checked);
const drawType = this.state.drawType;
this.setState(RESET_STATE, () => {
if (drawType) {
this.props.initiateDraw({ drawType: drawType, ...selection.value });
}
});
};
_activateDrawForFirstIndexPattern = (drawType) => {
if (!this._isMounted) {
return;
}
const indexPatternAndGeofield = this.state.uniqueIndexPatternsAndGeoFields[0];
this.setState(RESET_STATE, () => {
this.props.initiateDraw({ drawType: drawType, ...indexPatternAndGeofield });
});
};
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
async _getuniqueIndexPatternAndFieldCombos() {
try {
const indexPatterns = await getIndexPatternsFromIds(this.props.uniqueIndexPatternIds);
const uniqueIndexPatternsAndGeofields = [];
indexPatterns.forEach((indexPattern) => {
indexPattern.fields.forEach(field => {
if (field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) {
uniqueIndexPatternsAndGeofields.push({
geoField: field.name,
geoFieldType: field.type,
indexPatternTitle: indexPattern.title,
indexPatternId: indexPattern.id
});
}
});
});
if (this._isMounted && !_.isEqual(this.state.uniqueIndexPatternsAndGeoFields, uniqueIndexPatternsAndGeofields)) {
this.setState({
uniqueIndexPatternsAndGeoFields: uniqueIndexPatternsAndGeofields
});
}
} catch(e) {
// swallow errors.
// the Layer-TOC will indicate which layers are disfunctional on a per-layer basis
return [];
}
}
componentDidUpdate() {
this._getuniqueIndexPatternAndFieldCombos();
}
_getDrawActionsPanel() {
const drawPolygonAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', {
defaultMessage: 'Draw shape to filter data',
}),
};
const drawBoundsAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', {
defaultMessage: 'Draw bounds to filter data',
}),
};
if (this.state.uniqueIndexPatternsAndGeoFields.length === 1) {
drawPolygonAction.onClick = () => this._activateDrawForFirstIndexPattern(DRAW_TYPE.POLYGON);
drawBoundsAction.onClick = () => this._activateDrawForFirstIndexPattern(DRAW_TYPE.BOUNDS);
} else {
drawPolygonAction.panel = this._getIndexPatternSelectionPanel(1);
drawPolygonAction.onClick = () => {
this.setState({ drawType: DRAW_TYPE.POLYGON });
};
drawBoundsAction.panel = this._getIndexPatternSelectionPanel(2);
drawBoundsAction.onClick = () => {
this.setState({ drawType: DRAW_TYPE.BOUNDS });
};
}
return flattenPanelTree({
id: 0,
title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', {
defaultMessage: 'Tools',
}),
items: [drawPolygonAction, drawBoundsAction]
});
}
_getIndexPatternSelectionPanel(id) {
const options = this.state.uniqueIndexPatternsAndGeoFields.map((indexPatternAndGeoField) => {
return {
label: `${indexPatternAndGeoField.indexPatternTitle} : ${indexPatternAndGeoField.geoField}`,
value: indexPatternAndGeoField
};
});
const renderGeoField = (option, searchValue) => {
return (
<Fragment>
<EuiTextColor color="subdued">
<small>
<EuiHighlight search={searchValue}>{option.value.indexPatternTitle}</EuiHighlight>
</small>
</EuiTextColor>
<br />
<EuiHighlight search={searchValue}>
{option.value.geoField}
</EuiHighlight>
</Fragment>
);
};
const indexPatternSelection = (
<EuiSelectable
searchable
searchProps={{
placeholder: i18n.translate('xpack.maps.toolbarOverlay.indexPattern.filterListTitle', {
defaultMessage: 'Filter list',
}),
compressed: true,
}}
options={options}
/**
* *TODO*: FIX this handler as EuiSelectable passes back the full options
* list with the selected option set with `checked: 'on'`
*/
onChange={this._onIndexPatternSelection}
renderOption={renderGeoField}
listProps={{
rowHeight: 50,
showIcons: false,
}}
>
{(list, search) => (
<div>
{search}
{list}
</div>
)}
</EuiSelectable>
);
return {
id: id,
title: i18n.translate('xpack.maps.toolbarOverlay.geofield.toolbarTitle', {
defaultMessage: 'Select geo field',
}),
content: indexPatternSelection
};
}
_renderToolbarButton() {
return (
<EuiButtonIcon
className="mapToolbarOverlay__button"
color="text"
iconType="wrench"
onClick={this._toggleToolbar}
aria-label={i18n.translate('xpack.maps.toolbarOverlay.toolbarIconTitle', {
defaultMessage: 'Tools',
})}
title={i18n.translate('xpack.maps.toolbarOverlay.toolbarIconTitle', {
defaultMessage: 'Tools',
})}
/>
);
}
render() {
if (
!this.props.isFilterable ||
!this.state.uniqueIndexPatternsAndGeoFields.length
) {
return null;
}
return (
<EuiFlexGroup className="mapToolbarOverlay" responsive={false} direction="row" alignItems="flexEnd" gutterSize="s">
<EuiFlexItem>
<EuiPopover
id="contextMenu"
button={this._renderToolbarButton()}
isOpen={this.state.isPopoverOpen}
closePopover={this._closePopover}
panelPaddingSize="none"
withTitle
anchorPosition="leftUp"
>
<EuiContextMenu
initialPanelId={0}
panels={this._getDrawActionsPanel()}
/>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
);
}
}
function flattenPanelTree(tree, array = []) {
array.push(tree);
if (tree.items) {
tree.items.forEach(item => {
if (item.panel) {
flattenPanelTree(item.panel, array);
item.panel = item.panel.id;
}
});
}
return array;
}

View file

@ -11,8 +11,7 @@
}
.mapLayerControl__addLayerButton,
.mapLayerControl__openLayerTOCButton,
.mapViewControl__gotoButton {
.mapLayerControl__openLayerTOCButton {
pointer-events: all;
&:enabled,
@ -23,8 +22,7 @@
}
.mapLayerControl__openLayerTOCButton,
.mapLayerControl__closeLayerTOCButton,
.mapViewControl__gotoButton {
.mapLayerControl__closeLayerTOCButton {
@include size($euiSizeXL);
background-color: $euiColorEmptyShade !important; // During all states
}

View file

@ -1,16 +1,5 @@
/**
* 1. The overlay captures mouse events even if it's empty space. To counter-act this,
* we remove all pointer events from the overlay then add them back on the
* individual widgets.
*/
.mapViewControl__coordinates {
@include mapOverlayIsTextOnly;
justify-content: center;
pointer-events: none;
}
.mapViewControl__gotoButton {
min-width: 0;
pointer-events: all; /* 1 */
}

View file

@ -6,30 +6,14 @@
import { connect } from 'react-redux';
import { ViewControl } from './view_control';
import { getMouseCoordinates } from '../../../selectors/map_selectors';
import {
getIsSetViewOpen,
closeSetView,
openSetView,
} from '../../../store/ui';
import { getMouseCoordinates, getMapZoom } from '../../../selectors/map_selectors';
function mapStateToProps(state = {}) {
return {
isSetViewOpen: getIsSetViewOpen(state),
mouseCoordinates: getMouseCoordinates(state),
zoom: getMapZoom(state),
};
}
function mapDispatchToProps(dispatch) {
return {
closeSetView: () => {
dispatch(closeSetView());
},
openSetView: () => {
dispatch(openSetView());
}
};
}
const connectedViewControl = connect(mapStateToProps, mapDispatchToProps)(ViewControl);
const connectedViewControl = connect(mapStateToProps, null)(ViewControl);
export { connectedViewControl as ViewControl };

View file

@ -1,30 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import { SetView } from './set_view';
import { setGotoWithCenter } from '../../../../actions/store_actions';
import { getMapZoom, getMapCenter } from '../../../../selectors/map_selectors';
import { closeSetView } from '../../../../store/ui';
function mapStateToProps(state = {}) {
return {
zoom: getMapZoom(state),
center: getMapCenter(state),
};
}
function mapDispatchToProps(dispatch) {
return {
onSubmit: ({ lat, lon, zoom }) => {
dispatch(closeSetView());
dispatch(setGotoWithCenter({ lat, lon, zoom }));
}
};
}
const connectedSetView = connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(SetView);
export { connectedSetView as SetView };

View file

@ -6,95 +6,39 @@
import _ from 'lodash';
import React from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
EuiPopover,
EuiText,
} from '@elastic/eui';
import { SetView } from './set_view';
import { EuiText } from '@elastic/eui';
import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoordinates }) {
const toggleSetViewVisibility = () => {
if (isSetViewOpen) {
closeSetView();
return;
}
openSetView();
};
const setView = (
<EuiPopover
anchorPosition="upRight"
button={(
<EuiButtonIcon
className="mapViewControl__gotoButton"
onClick={toggleSetViewVisibility}
data-test-subj="toggleSetViewVisibilityButton"
iconType="crosshairs"
color="text"
aria-label={i18n.translate('xpack.maps.viewControl.goToButtonLabel', {
defaultMessage: 'Go to'
})}
title={i18n.translate('xpack.maps.viewControl.goToButtonLabel', {
defaultMessage: 'Go to'
})}
/>
)}
isOpen={isSetViewOpen}
closePopover={closeSetView}
>
<SetView />
</EuiPopover>
);
function renderMouseCoordinates() {
const lat = mouseCoordinates
? _.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)
: '';
const lon = mouseCoordinates
? _.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)
: '';
return (
<div className="mapViewControl__coordinates">
<EuiText size="xs">
<small>
<strong>
<FormattedMessage
id="xpack.maps.viewControl.latLabel"
defaultMessage="lat:"
/>
</strong> {lat},{' '}
<strong>
<FormattedMessage
id="xpack.maps.viewControl.lonLabel"
defaultMessage="lon:"
/>
</strong> {lon}
</small>
</EuiText>
</div>
);
export function ViewControl({ mouseCoordinates, zoom }) {
if (!mouseCoordinates) {
return null;
}
return (
<EuiFlexGroup
justifyContent="flexEnd"
alignItems="flexEnd"
gutterSize="s"
responsive={false}
>
<EuiFlexItem grow={false}>
{mouseCoordinates && renderMouseCoordinates()}
</EuiFlexItem>
<EuiFlexItem grow={false}>
{setView}
</EuiFlexItem>
</EuiFlexGroup>
<div className="mapViewControl__coordinates">
<EuiText size="xs">
<small>
<strong>
<FormattedMessage
id="xpack.maps.viewControl.latLabel"
defaultMessage="lat:"
/>
</strong> {_.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)},{' '}
<strong>
<FormattedMessage
id="xpack.maps.viewControl.lonLabel"
defaultMessage="lon:"
/>
</strong> {_.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)},{' '}
<strong>
<FormattedMessage
id="xpack.maps.viewControl.zoomLabel"
defaultMessage="zoom:"
/>
</strong> {zoom}
</small>
</EuiText>
</div>
);
}

View file

@ -6,7 +6,7 @@
import { indexPatternService } from './kibana_services';
export async function getIndexPatternsFromIds(indexPatternIds) {
export async function getIndexPatternsFromIds(indexPatternIds = []) {
const promises = [];
indexPatternIds.forEach((id) => {

View file

@ -187,7 +187,7 @@ export const getUniqueIndexPatternIds = createSelector(
layerList.forEach(layer => {
indexPatternIds.push(...layer.getIndexPatternIds());
});
return _.uniq(indexPatternIds);
return _.uniq(indexPatternIds).sort();
}
);

View file

@ -36,6 +36,7 @@ const INITIAL_STATE = {
isReadOnly: false,
isLayerTOCOpen: DEFAULT_IS_LAYER_TOC_OPEN,
isFilterable: false,
isSetViewOpen: false,
// storing TOC detail visibility outside of map.layerList because its UI state and not map rendering state.
// This also makes for easy read/write access for embeddables.
openTOCDetails: [],

View file

@ -5452,7 +5452,6 @@
"xpack.maps.toolbarOverlay.drawShapeLabel": "シェイプを描いてデータをフィルタリング",
"xpack.maps.toolbarOverlay.geofield.toolbarTitle": "ジオフィールドを選択",
"xpack.maps.toolbarOverlay.indexPattern.filterListTitle": "フィルターリスト",
"xpack.maps.toolbarOverlay.toolbarIconTitle": "ツール",
"xpack.maps.toolbarOverlay.tools.toolbarTitle": "ツール",
"xpack.maps.tooltip.closeAriaLabel": "ツールヒントを閉じる",
"xpack.maps.tooltip.filterOnPropertyAriaLabel": "プロパティのフィルター",