mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
f254ee682c
commit
ea4eb3385b
27 changed files with 516 additions and 47 deletions
|
@ -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
|
||||
? [
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} />);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
15
x-pack/plugins/maps/public/reducers/default_map_settings.ts
Normal file
15
x-pack/plugins/maps/public/reducers/default_map_settings.ts
Normal 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,
|
||||
};
|
||||
}
|
7
x-pack/plugins/maps/public/reducers/map.d.ts
vendored
7
x-pack/plugins/maps/public/reducers/map.d.ts
vendored
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 : [];
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue