[Maps] Map settings: min and max zoom (#63714)

* [Maps] Map settings: min and max zoom

* eslint

* header and footer

* zoom range UI

* save session state in mapStateJSON

* disable button when flyout is open

* tslint

* update layer_control jest test

* tslint

* move settings button to top map chrome

* move map_settings_panel to NP

* remove merge conflict artifact

* fix import for NP migration

* remove unused CSS class

* fix path from NP move

* review feedback

* load map settings in embeddable
This commit is contained in:
Nathan Reese 2020-04-21 16:27:00 -06:00 committed by GitHub
parent f254ee682c
commit ea4eb3385b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 516 additions and 47 deletions

View file

@ -39,6 +39,7 @@ import {
replaceLayerList,
setQuery,
clearTransientLayerStateAndCloseFlyout,
setMapSettings,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../plugins/maps/public/actions/map_actions';
import {
@ -52,10 +53,14 @@ import {
setReadOnly,
setIsLayerTOCOpen,
setOpenTOCDetails,
openMapSettings,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../plugins/maps/public/actions/ui_actions';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getIsFullScreen } from '../../../../../plugins/maps/public/selectors/ui_selectors';
import {
getIsFullScreen,
getFlyoutDisplay,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../plugins/maps/public/selectors/ui_selectors';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util';
import {
@ -395,6 +400,9 @@ app.controller(
if (mapState.filters) {
savedObjectFilters = mapState.filters;
}
if (mapState.settings) {
store.dispatch(setMapSettings(mapState.settings));
}
}
if (savedMap.uiStateJSON) {
@ -453,6 +461,7 @@ app.controller(
$scope.isFullScreen = false;
$scope.isSaveDisabled = false;
$scope.isOpenSettingsDisabled = false;
function handleStoreChanges(store) {
const nextIsFullScreen = getIsFullScreen(store.getState());
if (nextIsFullScreen !== $scope.isFullScreen) {
@ -474,6 +483,14 @@ app.controller(
$scope.isSaveDisabled = nextIsSaveDisabled;
});
}
const flyoutDisplay = getFlyoutDisplay(store.getState());
const nextIsOpenSettingsDisabled = flyoutDisplay !== FLYOUT_STATE.NONE;
if (nextIsOpenSettingsDisabled !== $scope.isOpenSettingsDisabled) {
$scope.$evalAsync(() => {
$scope.isOpenSettingsDisabled = nextIsOpenSettingsDisabled;
});
}
}
$scope.$on('$destroy', () => {
@ -591,6 +608,22 @@ app.controller(
getInspector().open(inspectorAdapters, {});
},
},
{
id: 'mapSettings',
label: i18n.translate('xpack.maps.mapController.openSettingsButtonLabel', {
defaultMessage: `Map settings`,
}),
description: i18n.translate('xpack.maps.mapController.openSettingsDescription', {
defaultMessage: `Open map settings`,
}),
testId: 'openSettingsButton',
disableButton() {
return $scope.isOpenSettingsDisabled;
},
run() {
store.dispatch(openMapSettings());
},
},
...(getMapsCapabilities().save
? [
{

View file

@ -14,6 +14,7 @@ import {
MapCenterAndZoom,
MapRefreshConfig,
} from '../../common/descriptor_types';
import { MapSettings } from '../reducers/map';
export type SyncContext = {
startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void;
@ -62,3 +63,14 @@ export function hideViewControl(): AnyAction;
export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction;
export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction;
export function setMapSettings(settings: MapSettings): AnyAction;
export function rollbackMapSettings(): AnyAction;
export function trackMapSettings(): AnyAction;
export function updateMapSetting(
settingKey: string,
settingValue: string | boolean | number
): AnyAction;

View file

@ -76,6 +76,10 @@ export const HIDE_TOOLBAR_OVERLAY = 'HIDE_TOOLBAR_OVERLAY';
export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL';
export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL';
export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS';
export const SET_MAP_SETTINGS = 'SET_MAP_SETTINGS';
export const ROLLBACK_MAP_SETTINGS = 'ROLLBACK_MAP_SETTINGS';
export const TRACK_MAP_SETTINGS = 'TRACK_MAP_SETTINGS';
export const UPDATE_MAP_SETTING = 'UPDATE_MAP_SETTING';
function getLayerLoadingCallbacks(dispatch, getState, layerId) {
return {
@ -145,6 +149,29 @@ export function setMapInitError(errorMessage) {
};
}
export function setMapSettings(settings) {
return {
type: SET_MAP_SETTINGS,
settings,
};
}
export function rollbackMapSettings() {
return { type: ROLLBACK_MAP_SETTINGS };
}
export function trackMapSettings() {
return { type: TRACK_MAP_SETTINGS };
}
export function updateMapSetting(settingKey, settingValue) {
return {
type: UPDATE_MAP_SETTING,
settingKey,
settingValue,
};
}
export function trackCurrentLayerState(layerId) {
return {
type: TRACK_CURRENT_LAYER_STATE,

View file

@ -5,6 +5,7 @@
*/
import { AnyAction } from 'redux';
import { FLYOUT_STATE } from '../reducers/ui';
export const UPDATE_FLYOUT: string;
export const CLOSE_SET_VIEW: string;
@ -17,6 +18,8 @@ export const SHOW_TOC_DETAILS: string;
export const HIDE_TOC_DETAILS: string;
export const UPDATE_INDEXING_STAGE: string;
export function updateFlyout(display: FLYOUT_STATE): AnyAction;
export function setOpenTOCDetails(layerIds?: string[]): AnyAction;
export function setIsLayerTOCOpen(open: boolean): AnyAction;

View file

@ -4,6 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { getFlyoutDisplay } from '../selectors/ui_selectors';
import { FLYOUT_STATE } from '../reducers/ui';
import { setSelectedLayer, trackMapSettings } from './map_actions';
export const UPDATE_FLYOUT = 'UPDATE_FLYOUT';
export const CLOSE_SET_VIEW = 'CLOSE_SET_VIEW';
export const OPEN_SET_VIEW = 'OPEN_SET_VIEW';
@ -28,6 +32,17 @@ export function updateFlyout(display) {
display,
};
}
export function openMapSettings() {
return (dispatch, getState) => {
const flyoutDisplay = getFlyoutDisplay(getState());
if (flyoutDisplay === FLYOUT_STATE.MAP_SETTINGS_PANEL) {
return;
}
dispatch(setSelectedLayer(null));
dispatch(trackMapSettings());
dispatch(updateFlyout(FLYOUT_STATE.MAP_SETTINGS_PANEL));
};
}
export function closeSetView() {
return {
type: CLOSE_SET_VIEW,

View file

@ -15,6 +15,7 @@ import {
getRefreshConfig,
getQuery,
getFilters,
getMapSettings,
} from '../../selectors/map_selectors';
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors';
@ -98,6 +99,7 @@ export function createSavedGisMapClass(services) {
refreshConfig: getRefreshConfig(state),
query: _.omit(getQuery(state), 'queryLastTriggeredAt'),
filters: getFilters(state),
settings: getMapSettings(state),
});
this.uiStateJSON = JSON.stringify({

View file

@ -6,8 +6,6 @@
import { connect } from 'react-redux';
import { GisMap } from './view';
import { FLYOUT_STATE } from '../../reducers/ui';
import { exitFullScreen } from '../../actions/ui_actions';
import { getFlyoutDisplay, getIsFullScreen } from '../../selectors/ui_selectors';
import { triggerRefreshTimer, cancelAllInFlightRequests } from '../../actions/map_actions';
@ -22,12 +20,9 @@ import {
import { getCoreChrome } from '../../kibana_services';
function mapStateToProps(state = {}) {
const flyoutDisplay = getFlyoutDisplay(state);
return {
areLayersLoaded: areLayersLoaded(state),
layerDetailsVisible: flyoutDisplay === FLYOUT_STATE.LAYER_PANEL,
addLayerVisible: flyoutDisplay === FLYOUT_STATE.ADD_LAYER_WIZARD,
noFlyoutVisible: flyoutDisplay === FLYOUT_STATE.NONE,
flyoutDisplay: getFlyoutDisplay(state),
isFullScreen: getIsFullScreen(state),
refreshConfig: getRefreshConfig(state),
mapInitError: getMapInitError(state),

View file

@ -6,6 +6,7 @@
import _ from 'lodash';
import React, { Component } from 'react';
import classNames from 'classnames';
import { MBMapContainer } from '../map/mb';
import { WidgetOverlay } from '../widget_overlay';
import { ToolbarOverlay } from '../toolbar_overlay';
@ -19,6 +20,8 @@ import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
import { indexPatterns as indexPatternsUtils } from '../../../../../../src/plugins/data/public';
import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { FLYOUT_STATE } from '../../reducers/ui';
import { MapSettingsPanel } from '../map_settings_panel';
const RENDER_COMPLETE_EVENT = 'renderComplete';
@ -147,9 +150,7 @@ export class GisMap extends Component {
render() {
const {
addFilters,
layerDetailsVisible,
addLayerVisible,
noFlyoutVisible,
flyoutDisplay,
isFullScreen,
exitFullScreen,
mapInitError,
@ -174,16 +175,13 @@ export class GisMap extends Component {
);
}
let currentPanel;
let currentPanelClassName;
if (noFlyoutVisible) {
currentPanel = null;
} else if (addLayerVisible) {
currentPanelClassName = 'mapMapLayerPanel-isVisible';
currentPanel = <AddLayerPanel />;
} else if (layerDetailsVisible) {
currentPanelClassName = 'mapMapLayerPanel-isVisible';
currentPanel = <LayerPanel />;
let flyoutPanel = null;
if (flyoutDisplay === FLYOUT_STATE.ADD_LAYER_WIZARD) {
flyoutPanel = <AddLayerPanel />;
} else if (flyoutDisplay === FLYOUT_STATE.LAYER_PANEL) {
flyoutPanel = <LayerPanel />;
} else if (flyoutDisplay === FLYOUT_STATE.MAP_SETTINGS_PANEL) {
flyoutPanel = <MapSettingsPanel />;
}
let exitFullScreenButton;
@ -210,8 +208,13 @@ export class GisMap extends Component {
<WidgetOverlay />
</EuiFlexItem>
<EuiFlexItem className={`mapMapLayerPanel ${currentPanelClassName}`} grow={false}>
{currentPanel}
<EuiFlexItem
className={classNames('mapMapLayerPanel', {
'mapMapLayerPanel-isVisible': !!flyoutPanel,
})}
grow={false}
>
{flyoutPanel}
</EuiFlexItem>
{exitFullScreenButton}

View file

@ -23,6 +23,7 @@ import {
isInteractiveDisabled,
isTooltipControlDisabled,
isViewControlHidden,
getMapSettings,
} from '../../../selectors/map_selectors';
import { getInspectorAdapters } from '../../../reducers/non_serializable_instances';
@ -30,6 +31,7 @@ import { getInspectorAdapters } from '../../../reducers/non_serializable_instanc
function mapStateToProps(state = {}) {
return {
isMapReady: getMapReady(state),
settings: getMapSettings(state),
layerList: getLayerList(state),
goto: getGoto(state),
inspectorAdapters: getInspectorAdapters(state),

View file

@ -12,14 +12,8 @@ import {
removeOrphanedSourcesAndLayers,
addSpritesheetToMap,
} from './utils';
import { getGlyphUrl, isRetina } from '../../../meta';
import {
DECIMAL_DEGREES_PRECISION,
MAX_ZOOM,
MIN_ZOOM,
ZOOM_PRECISION,
} from '../../../../common/constants';
import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants';
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';
@ -80,7 +74,7 @@ export class MBMapContainer extends React.Component {
}
_debouncedSync = _.debounce(() => {
if (this._isMounted) {
if (this._isMounted || !this.props.isMapReady) {
if (!this.state.hasSyncedLayerList) {
this.setState(
{
@ -92,6 +86,7 @@ export class MBMapContainer extends React.Component {
}
);
}
this._syncSettings();
}
}, 256);
@ -133,8 +128,8 @@ export class MBMapContainer extends React.Component {
scrollZoom: this.props.scrollZoom,
preserveDrawingBuffer: getInjectedVarFunc()('preserveDrawingBuffer', false),
interactive: !this.props.disableInteractive,
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
maxZoom: this.props.settings.maxZoom,
minZoom: this.props.settings.minZoom,
};
const initialView = _.get(this.props.goto, 'center');
if (initialView) {
@ -265,17 +260,13 @@ export class MBMapContainer extends React.Component {
};
_syncMbMapWithLayerList = () => {
if (!this.props.isMapReady) {
return;
}
removeOrphanedSourcesAndLayers(this.state.mbMap, this.props.layerList);
this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap));
syncLayerOrderForSingleLayer(this.state.mbMap, this.props.layerList);
};
_syncMbMapWithInspector = () => {
if (!this.props.isMapReady || !this.props.inspectorAdapters.map) {
if (!this.props.inspectorAdapters.map) {
return;
}
@ -289,6 +280,27 @@ export class MBMapContainer extends React.Component {
});
};
_syncSettings() {
let zoomRangeChanged = false;
if (this.props.settings.minZoom !== this.state.mbMap.getMinZoom()) {
this.state.mbMap.setMinZoom(this.props.settings.minZoom);
zoomRangeChanged = true;
}
if (this.props.settings.maxZoom !== this.state.mbMap.getMaxZoom()) {
this.state.mbMap.setMaxZoom(this.props.settings.maxZoom);
zoomRangeChanged = true;
}
// 'moveend' event not fired when map moves from setMinZoom or setMaxZoom
// https://github.com/mapbox/mapbox-gl-js/issues/9610
// hack to update extent after zoom update finishes moving map.
if (zoomRangeChanged) {
setTimeout(() => {
this.props.extentChanged(this._getMapState());
}, 300);
}
}
render() {
let drawControl;
let tooltipControl;

View file

@ -0,0 +1,39 @@
/*
* 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 { AnyAction, Dispatch } from 'redux';
import { connect } from 'react-redux';
import { FLYOUT_STATE } from '../../reducers/ui';
import { MapStoreState } from '../../reducers/store';
import { MapSettingsPanel } from './map_settings_panel';
import { rollbackMapSettings, updateMapSetting } from '../../actions/map_actions';
import { getMapSettings, hasMapSettingsChanges } from '../../selectors/map_selectors';
import { updateFlyout } from '../../actions/ui_actions';
function mapStateToProps(state: MapStoreState) {
return {
settings: getMapSettings(state),
hasMapSettingsChanges: hasMapSettingsChanges(state),
};
}
function mapDispatchToProps(dispatch: Dispatch<AnyAction>) {
return {
cancelChanges: () => {
dispatch(rollbackMapSettings());
dispatch(updateFlyout(FLYOUT_STATE.NONE));
},
keepChanges: () => {
dispatch(updateFlyout(FLYOUT_STATE.NONE));
},
updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => {
dispatch(updateMapSetting(settingKey, settingValue));
},
};
}
const connectedMapSettingsPanel = connect(mapStateToProps, mapDispatchToProps)(MapSettingsPanel);
export { connectedMapSettingsPanel as MapSettingsPanel };

View file

@ -0,0 +1,97 @@
/*
* 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 from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MapSettings } from '../../reducers/map';
import { NavigationPanel } from './navigation_panel';
interface Props {
cancelChanges: () => void;
hasMapSettingsChanges: boolean;
keepChanges: () => void;
settings: MapSettings;
updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void;
}
export function MapSettingsPanel({
cancelChanges,
hasMapSettingsChanges,
keepChanges,
settings,
updateMapSetting,
}: Props) {
// TODO move common text like Cancel and Close to common i18n translation
const closeBtnLabel = hasMapSettingsChanges
? i18n.translate('xpack.maps.mapSettingsPanel.cancelLabel', {
defaultMessage: 'Cancel',
})
: i18n.translate('xpack.maps.mapSettingsPanel.closeLabel', {
defaultMessage: 'Close',
});
return (
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlyoutHeader hasBorder className="mapLayerPanel__header">
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.maps.mapSettingsPanel.title"
defaultMessage="Map settings"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<div className="mapLayerPanel__body">
<div className="mapLayerPanel__bodyOverflow">
<NavigationPanel settings={settings} updateMapSetting={updateMapSetting} />
</div>
</div>
<EuiFlyoutFooter className="mapLayerPanel__footer">
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={cancelChanges}
flush="left"
data-test-subj="layerPanelCancelButton"
>
{closeBtnLabel}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
disabled={!hasMapSettingsChanges}
iconType="check"
onClick={keepChanges}
fill
>
<FormattedMessage
id="xpack.maps.mapSettingsPanel.keepChangesButtonLabel"
defaultMessage="Keep changes"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,55 @@
/*
* 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 from 'react';
import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MapSettings } from '../../reducers/map';
import { ValidatedDualRange, Value } from '../../../../../../src/plugins/kibana_react/public';
import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
interface Props {
settings: MapSettings;
updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void;
}
export function NavigationPanel({ settings, updateMapSetting }: Props) {
const onZoomChange = (value: Value) => {
updateMapSetting('minZoom', Math.max(MIN_ZOOM, parseInt(value[0] as string, 10)));
updateMapSetting('maxZoom', Math.min(MAX_ZOOM, parseInt(value[1] as string, 10)));
};
return (
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.mapSettingsPanel.navigationTitle"
defaultMessage="Navigation"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
<ValidatedDualRange
label={i18n.translate('xpack.maps.mapSettingsPanel.zoomRangeLabel', {
defaultMessage: 'Zoom range',
})}
formRowDisplay="columnCompressed"
min={MIN_ZOOM}
max={MAX_ZOOM}
value={[settings.minZoom, settings.maxZoom]}
showInput="inputWithPopover"
showRange
showLabels
onChange={onZoomChange}
allowEmptyRange={false}
compressed
/>
</EuiPanel>
);
}

View file

@ -7,12 +7,13 @@
import { connect } from 'react-redux';
import { SetViewControl } from './set_view_control';
import { setGotoWithCenter } from '../../../actions/map_actions';
import { getMapZoom, getMapCenter } from '../../../selectors/map_selectors';
import { getMapZoom, getMapCenter, getMapSettings } from '../../../selectors/map_selectors';
import { closeSetView, openSetView } from '../../../actions/ui_actions';
import { getIsSetViewOpen } from '../../../selectors/ui_selectors';
function mapStateToProps(state = {}) {
return {
settings: getMapSettings(state),
isSetViewOpen: getIsSetViewOpen(state),
zoom: getMapZoom(state),
center: getMapCenter(state),

View file

@ -18,7 +18,6 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
function getViewString(lat, lon, zoom) {
return `${lat},${lon},${zoom}`;
@ -118,8 +117,8 @@ export class SetViewControl extends Component {
const { isInvalid: isZoomInvalid, component: zoomFormRow } = this._renderNumberFormRow({
value: this.state.zoom,
min: MIN_ZOOM,
max: MAX_ZOOM,
min: this.props.settings.minZoom,
max: this.props.settings.maxZoom,
onChange: this._onZoomChange,
label: i18n.translate('xpack.maps.setViewControl.zoomLabel', {
defaultMessage: 'Zoom',

View file

@ -65,7 +65,7 @@ exports[`LayerControl is rendered 1`] = `
data-test-subj="addLayerButton"
fill={true}
fullWidth={true}
isDisabled={true}
isDisabled={false}
onClick={[Function]}
>
<FormattedMessage
@ -187,3 +187,80 @@ exports[`LayerControl isReadOnly 1`] = `
</EuiPanel>
</Fragment>
`;
exports[`LayerControl should disable buttons when flyout is open 1`] = `
<Fragment>
<EuiPanel
className="mapWidgetControl mapWidgetControl-hasShadow"
grow={false}
paddingSize="none"
>
<EuiFlexItem
className="mapWidgetControl__headerFlexItem"
grow={false}
>
<EuiFlexGroup
alignItems="center"
gutterSize="none"
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem>
<EuiTitle
className="mapWidgetControl__header"
size="xxxs"
>
<h2>
<FormattedMessage
defaultMessage="Layers"
id="xpack.maps.layerControl.layersTitle"
values={Object {}}
/>
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiToolTip
content="Collapse layers panel"
delay="long"
position="top"
>
<EuiButtonIcon
aria-label="Collapse layers panel"
className="mapLayerControl__closeLayerTOCButton"
color="text"
data-test-subj="mapToggleLegendButton"
iconType="menuRight"
onClick={[Function]}
/>
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem
className="mapLayerControl"
>
<LayerTOC />
</EuiFlexItem>
</EuiPanel>
<EuiSpacer
size="s"
/>
<EuiButton
className="mapLayerControl__addLayerButton"
data-test-subj="addLayerButton"
fill={true}
fullWidth={true}
isDisabled={true}
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Add layer"
id="xpack.maps.layerControl.addLayerButtonLabel"
values={Object {}}
/>
</EuiButton>
</Fragment>
`;

View file

@ -22,7 +22,7 @@ function mapStateToProps(state = {}) {
isReadOnly: getIsReadOnly(state),
isLayerTOCOpen: getIsLayerTOCOpen(state),
layerList: getLayerList(state),
isAddButtonActive: getFlyoutDisplay(state) === FLYOUT_STATE.NONE,
isFlyoutOpen: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE,
};
}

View file

@ -57,7 +57,7 @@ export function LayerControl({
closeLayerTOC,
openLayerTOC,
layerList,
isAddButtonActive,
isFlyoutOpen,
}) {
if (!isLayerTOCOpen) {
const hasErrors = layerList.some(layer => {
@ -86,7 +86,7 @@ export function LayerControl({
<Fragment>
<EuiSpacer size="s" />
<EuiButton
isDisabled={!isAddButtonActive}
isDisabled={isFlyoutOpen}
className="mapLayerControl__addLayerButton"
fill
fullWidth

View file

@ -21,6 +21,7 @@ const defaultProps = {
openLayerTOC: () => {},
isLayerTOCOpen: true,
layerList: [],
isFlyoutOpen: false,
};
describe('LayerControl', () => {
@ -30,6 +31,12 @@ describe('LayerControl', () => {
expect(component).toMatchSnapshot();
});
test('should disable buttons when flyout is open', () => {
const component = shallow(<LayerControl {...defaultProps} isFlyoutOpen={true} />);
expect(component).toMatchSnapshot();
});
test('isReadOnly', () => {
const component = shallow(<LayerControl {...defaultProps} isReadOnly={true} />);

View file

@ -28,6 +28,7 @@ import {
} from '../../../../../src/plugins/data/public';
import { GisMap } from '../connected_components/gis_map';
import { createMapStore, MapStore } from '../reducers/store';
import { MapSettings } from '../reducers/map';
import {
setGotoWithCenter,
replaceLayerList,
@ -40,6 +41,7 @@ import {
hideLayerControl,
hideViewControl,
setHiddenLayers,
setMapSettings,
} from '../actions/map_actions';
import { MapCenterAndZoom } from '../../common/descriptor_types';
import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions';
@ -60,6 +62,7 @@ interface MapEmbeddableConfig {
editable: boolean;
title?: string;
layerList: unknown[];
settings?: MapSettings;
}
export interface MapEmbeddableInput extends EmbeddableInput {
@ -97,6 +100,7 @@ export class MapEmbeddable extends Embeddable<MapEmbeddableInput, MapEmbeddableO
private _prevFilters?: Filter[];
private _domNode?: HTMLElement;
private _unsubscribeFromStore?: Unsubscribe;
private _settings?: MapSettings;
constructor(
config: MapEmbeddableConfig,
@ -119,6 +123,7 @@ export class MapEmbeddable extends Embeddable<MapEmbeddableInput, MapEmbeddableO
this._renderTooltipContent = renderTooltipContent;
this._eventHandlers = eventHandlers;
this._layerList = config.layerList;
this._settings = config.settings;
this._store = createMapStore();
this._subscription = this.getInput$().subscribe(input => this.onContainerStateChanged(input));
@ -194,6 +199,10 @@ export class MapEmbeddable extends Embeddable<MapEmbeddableInput, MapEmbeddableO
this._store.dispatch(setReadOnly(true));
this._store.dispatch(disableScrollZoom());
if (this._settings) {
this._store.dispatch(setMapSettings(this._settings));
}
if (_.has(this.input, 'isLayerTOCOpen')) {
this._store.dispatch(setIsLayerTOCOpen(this.input.isLayerTOCOpen));
}

View file

@ -93,6 +93,14 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
const layerList = getInitialLayers(savedMap.layerListJSON);
const indexPatterns = await this._getIndexPatterns(layerList);
let settings;
if (savedMap.mapStateJSON) {
const mapState = JSON.parse(savedMap.mapStateJSON);
if (mapState.settings) {
settings = mapState.settings;
}
}
const embeddable = new MapEmbeddable(
{
layerList,
@ -100,6 +108,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
editUrl: getHttp().basePath.prepend(createMapPath(savedObjectId)),
indexPatterns,
editable: await this.isEditable(),
settings,
},
input,
parent

View file

@ -0,0 +1,15 @@
/*
* 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 { MAX_ZOOM, MIN_ZOOM } from '../../common/constants';
import { MapSettings } from './map';
export function getDefaultMapSettings(): MapSettings {
return {
maxZoom: MAX_ZOOM,
minZoom: MIN_ZOOM,
};
}

View file

@ -39,6 +39,11 @@ export type MapContext = {
hideViewControl: boolean;
};
export type MapSettings = {
maxZoom: number;
minZoom: number;
};
export type MapState = {
ready: boolean;
mapInitError?: string | null;
@ -49,4 +54,6 @@ export type MapState = {
__transientLayerId: string | null;
layerList: LayerDescriptor[];
waitingForMapReadyLayerList: LayerDescriptor[];
settings: MapSettings;
__rollbackSettings: MapSettings | null;
};

View file

@ -46,8 +46,13 @@ import {
HIDE_LAYER_CONTROL,
HIDE_VIEW_CONTROL,
SET_WAITING_FOR_READY_HIDDEN_LAYERS,
SET_MAP_SETTINGS,
ROLLBACK_MAP_SETTINGS,
TRACK_MAP_SETTINGS,
UPDATE_MAP_SETTING,
} from '../actions/map_actions';
import { getDefaultMapSettings } from './default_map_settings';
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from './util';
import { SOURCE_DATA_ID_ORIGIN } from '../../common/constants';
@ -124,6 +129,8 @@ const INITIAL_STATE = {
__transientLayerId: null,
layerList: [],
waitingForMapReadyLayerList: [],
settings: getDefaultMapSettings(),
__rollbackSettings: null,
};
export function map(state = INITIAL_STATE, action) {
@ -179,6 +186,32 @@ export function map(state = INITIAL_STATE, action) {
...state,
goto: null,
};
case SET_MAP_SETTINGS:
return {
...state,
settings: { ...getDefaultMapSettings(), ...action.settings },
};
case ROLLBACK_MAP_SETTINGS:
return state.__rollbackSettings
? {
...state,
settings: { ...state.__rollbackSettings },
__rollbackSettings: null,
}
: state;
case TRACK_MAP_SETTINGS:
return {
...state,
__rollbackSettings: state.settings,
};
case UPDATE_MAP_SETTING:
return {
...state,
settings: {
...(state.settings ? state.settings : {}),
[action.settingKey]: action.settingValue,
},
};
case SET_LAYER_ERROR_STATUS:
const { layerList } = state;
const layerIdx = getLayerIndex(layerList, action.layerId);

View file

@ -22,6 +22,7 @@ export enum FLYOUT_STATE {
NONE = 'NONE',
LAYER_PANEL = 'LAYER_PANEL',
ADD_LAYER_WIZARD = 'ADD_LAYER_WIZARD',
MAP_SETTINGS_PANEL = 'MAP_SETTINGS_PANEL',
}
export enum INDEXING_STAGE {

View file

@ -6,8 +6,8 @@
import { AnyAction } from 'redux';
import { MapCenter } from '../../common/descriptor_types';
import { MapStoreState } from '../reducers/store';
import { MapSettings } from '../reducers/map';
export function getHiddenLayerIds(state: MapStoreState): string[];
@ -16,3 +16,7 @@ export function getMapZoom(state: MapStoreState): number;
export function getMapCenter(state: MapStoreState): MapCenter;
export function getQueryableUniqueIndexPatternIds(state: MapStoreState): string[];
export function getMapSettings(state: MapStoreState): MapSettings;
export function hasMapSettingsChanges(state: MapStoreState): boolean;

View file

@ -64,6 +64,18 @@ function createSourceInstance(sourceDescriptor, inspectorAdapters) {
return new source.ConstructorFunction(sourceDescriptor, inspectorAdapters);
}
export const getMapSettings = ({ map }) => map.settings;
const getRollbackMapSettings = ({ map }) => map.__rollbackSettings;
export const hasMapSettingsChanges = createSelector(
getMapSettings,
getRollbackMapSettings,
(settings, rollbackSettings) => {
return rollbackSettings ? !_.isEqual(settings, rollbackSettings) : false;
}
);
export const getOpenTooltips = ({ map }) => {
return map && map.openTooltips ? map.openTooltips : [];
};