mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] Revert layer-state correctly on cancel (#30451)
This commit is contained in:
parent
6bf127e107
commit
de130211d8
16 changed files with 315 additions and 116 deletions
|
@ -6,15 +6,13 @@
|
|||
|
||||
import turf from 'turf';
|
||||
import turfBooleanContains from '@turf/boolean-contains';
|
||||
|
||||
import { GIS_API_PATH } from '../../common/constants';
|
||||
import {
|
||||
getLayerList,
|
||||
getLayerListRaw,
|
||||
getDataFilters,
|
||||
getSelectedLayer,
|
||||
getSelectedLayerId,
|
||||
getMapReady,
|
||||
getWaitingForMapReadyLayerListRaw,
|
||||
getWaitingForMapReadyLayerListRaw
|
||||
} from '../selectors/map_selectors';
|
||||
|
||||
export const SET_SELECTED_LAYER = 'SET_SELECTED_LAYER';
|
||||
|
@ -25,7 +23,6 @@ export const ADD_WAITING_FOR_MAP_READY_LAYER = 'ADD_WAITING_FOR_MAP_READY_LAYER'
|
|||
export const CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST = 'CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST';
|
||||
export const REMOVE_LAYER = 'REMOVE_LAYER';
|
||||
export const PROMOTE_TEMPORARY_LAYERS = 'PROMOTE_TEMPORARY_LAYERS';
|
||||
export const SET_META = 'SET_META';
|
||||
export const TOGGLE_LAYER_VISIBLE = 'TOGGLE_LAYER_VISIBLE';
|
||||
export const MAP_EXTENT_CHANGED = 'MAP_EXTENT_CHANGED';
|
||||
export const MAP_READY = 'MAP_READY';
|
||||
|
@ -38,8 +35,6 @@ export const SET_QUERY = 'SET_QUERY';
|
|||
export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER';
|
||||
export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP';
|
||||
export const UPDATE_LAYER_STYLE = 'UPDATE_LAYER_STYLE';
|
||||
export const PROMOTE_TEMPORARY_STYLES = 'PROMOTE_TEMPORARY_STYLES';
|
||||
export const CLEAR_TEMPORARY_STYLES = 'CLEAR_TEMPORARY_STYLES';
|
||||
export const TOUCH_LAYER = 'TOUCH_LAYER';
|
||||
export const UPDATE_SOURCE_PROP = 'UPDATE_SOURCE_PROP';
|
||||
export const SET_REFRESH_CONFIG = 'SET_REFRESH_CONFIG';
|
||||
|
@ -47,8 +42,9 @@ export const SET_MOUSE_COORDINATES = 'SET_MOUSE_COORDINATES';
|
|||
export const CLEAR_MOUSE_COORDINATES = 'CLEAR_MOUSE_COORDINATES';
|
||||
export const SET_GOTO = 'SET_GOTO';
|
||||
export const CLEAR_GOTO = 'CLEAR_GOTO';
|
||||
|
||||
const GIS_API_RELATIVE = `../${GIS_API_PATH}`;
|
||||
export const TRACK_CURRENT_LAYER_STATE = 'TRACK_CURRENT_LAYER_STATE';
|
||||
export const ROLLBACK_TO_TRACKED_LAYER_STATE = 'ROLLBACK_TO_TRACKED_LAYER_STATE';
|
||||
export const REMOVE_TRACKED_LAYER_STATE = 'REMOVE_TRACKED_LAYER_STATE';
|
||||
|
||||
function getLayerLoadingCallbacks(dispatch, layerId) {
|
||||
return {
|
||||
|
@ -74,6 +70,34 @@ async function syncDataForAllLayers(getState, dispatch, dataFilters) {
|
|||
await Promise.all(syncs);
|
||||
}
|
||||
|
||||
export function trackCurrentLayerState(layerId) {
|
||||
return {
|
||||
type: TRACK_CURRENT_LAYER_STATE,
|
||||
layerId: layerId
|
||||
};
|
||||
}
|
||||
|
||||
export function rollbackToTrackedLayerStateForSelectedLayer() {
|
||||
return async (dispatch, getState) => {
|
||||
const layerId = getSelectedLayerId(getState());
|
||||
await dispatch({
|
||||
type: ROLLBACK_TO_TRACKED_LAYER_STATE,
|
||||
layerId: layerId
|
||||
});
|
||||
dispatch(syncDataForLayer(layerId));
|
||||
};
|
||||
}
|
||||
|
||||
export function removeTrackedLayerStateForSelectedLayer() {
|
||||
return (dispatch, getState) => {
|
||||
const layerId = getSelectedLayerId(getState());
|
||||
dispatch({
|
||||
type: REMOVE_TRACKED_LAYER_STATE,
|
||||
layerId: layerId
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function replaceLayerList(newLayerList) {
|
||||
return (dispatch, getState) => {
|
||||
getLayerListRaw(getState()).forEach(({ id }) => {
|
||||
|
@ -140,9 +164,19 @@ export function toggleLayerVisible(layerId) {
|
|||
}
|
||||
|
||||
export function setSelectedLayer(layerId) {
|
||||
return {
|
||||
type: SET_SELECTED_LAYER,
|
||||
selectedLayerId: layerId
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const oldSelectedLayer = getSelectedLayerId(getState());
|
||||
if (oldSelectedLayer) {
|
||||
await dispatch(rollbackToTrackedLayerStateForSelectedLayer());
|
||||
}
|
||||
if (layerId) {
|
||||
dispatch(trackCurrentLayerState(layerId));
|
||||
}
|
||||
dispatch({
|
||||
type: SET_SELECTED_LAYER,
|
||||
selectedLayerId: layerId
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -153,24 +187,12 @@ export function updateLayerOrder(newLayerOrder) {
|
|||
};
|
||||
}
|
||||
|
||||
export function promoteTemporaryStyles() {
|
||||
return {
|
||||
type: PROMOTE_TEMPORARY_STYLES
|
||||
};
|
||||
}
|
||||
|
||||
export function promoteTemporaryLayers() {
|
||||
return {
|
||||
type: PROMOTE_TEMPORARY_LAYERS
|
||||
};
|
||||
}
|
||||
|
||||
export function clearTemporaryStyles() {
|
||||
return {
|
||||
type: CLEAR_TEMPORARY_STYLES
|
||||
};
|
||||
}
|
||||
|
||||
export function clearTemporaryLayers() {
|
||||
return (dispatch, getState) => {
|
||||
getLayerListRaw(getState()).forEach(({ temporary, id }) => {
|
||||
|
@ -413,8 +435,8 @@ export function updateLayerAlpha(id, alpha) {
|
|||
export function removeSelectedLayer() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const layer = getSelectedLayer(state);
|
||||
dispatch(removeLayer(layer.getId()));
|
||||
const layerId = getSelectedLayerId(state);
|
||||
dispatch(removeLayer(layerId));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -434,13 +456,6 @@ export function removeLayer(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setMeta(metaJson) {
|
||||
return {
|
||||
type: SET_META,
|
||||
meta: metaJson
|
||||
};
|
||||
}
|
||||
|
||||
export function setQuery({ query, timeFilters }) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
@ -516,11 +531,11 @@ export function updateLayerStyle(layerId, styleDescriptor, temporary = true) {
|
|||
|
||||
export function updateLayerStyleForSelectedLayer(styleDescriptor, temporary = true) {
|
||||
return (dispatch, getState) => {
|
||||
const selectedLayer = getSelectedLayer(getState());
|
||||
if (!selectedLayer) {
|
||||
const selectedLayerId = getSelectedLayerId(getState());
|
||||
if (!selectedLayerId) {
|
||||
return;
|
||||
}
|
||||
dispatch(updateLayerStyle(selectedLayer.getId(), styleDescriptor, temporary));
|
||||
dispatch(updateLayerStyle(selectedLayerId, styleDescriptor, temporary));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -536,9 +551,3 @@ export function setJoinsForLayer(layer, joins) {
|
|||
dispatch(syncDataForLayer(layer.getId()));
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadMetaResources(dispatch) {
|
||||
const meta = await fetch(`${GIS_API_RELATIVE}/meta`);
|
||||
const metaJson = await meta.json();
|
||||
await dispatch(setMeta(metaJson));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
getQuery,
|
||||
} from '../../selectors/map_selectors';
|
||||
import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils';
|
||||
import { copyPersistentState } from '../../store/util';
|
||||
|
||||
const module = uiModules.get('app/maps');
|
||||
|
||||
|
@ -86,16 +87,3 @@ module.factory('SavedGisMap', function (Private) {
|
|||
return SavedGisMap;
|
||||
});
|
||||
|
||||
|
||||
function copyPersistentState(input) {
|
||||
if (typeof input !== 'object' && input !== null) {//primitive
|
||||
return input;
|
||||
}
|
||||
const copyInput = Array.isArray(input) ? [] : {};
|
||||
for(const key in input) {
|
||||
if (!key.startsWith('__')) {
|
||||
copyInput[key] = copyPersistentState(input[key]);
|
||||
}
|
||||
}
|
||||
return copyInput;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,14 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { FlyoutFooter } from './view';
|
||||
import { updateFlyout, FLYOUT_STATE } from '../../../store/ui';
|
||||
import { promoteTemporaryStyles, clearTemporaryStyles, clearTemporaryLayers,
|
||||
setSelectedLayer, removeSelectedLayer, promoteTemporaryLayers } from '../../../actions/store_actions';
|
||||
import {
|
||||
clearTemporaryLayers,
|
||||
setSelectedLayer,
|
||||
removeSelectedLayer,
|
||||
promoteTemporaryLayers,
|
||||
rollbackToTrackedLayerStateForSelectedLayer,
|
||||
removeTrackedLayerStateForSelectedLayer
|
||||
} from '../../../actions/store_actions';
|
||||
import { getSelectedLayer } from '../../../selectors/map_selectors';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
|
@ -18,19 +24,20 @@ const mapStateToProps = state => {
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
cancelLayerPanel: () => {
|
||||
dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
dispatch(clearTemporaryStyles());
|
||||
dispatch(clearTemporaryLayers());
|
||||
cancelLayerPanel: async () => {
|
||||
await dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
await dispatch(clearTemporaryLayers());
|
||||
await dispatch(rollbackToTrackedLayerStateForSelectedLayer());
|
||||
await dispatch(setSelectedLayer(null));
|
||||
},
|
||||
saveLayerEdits: isNewLayer => {
|
||||
dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
dispatch(promoteTemporaryStyles());
|
||||
if (isNewLayer) {
|
||||
dispatch(promoteTemporaryLayers());
|
||||
}
|
||||
dispatch(removeTrackedLayerStateForSelectedLayer());
|
||||
dispatch(setSelectedLayer(null));
|
||||
},
|
||||
removeLayer: () => {
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
import { StyleTabs } from './view';
|
||||
import { updateLayerStyleForSelectedLayer, clearTemporaryStyles } from '../../../actions/store_actions';
|
||||
import { updateLayerStyleForSelectedLayer } from '../../../actions/store_actions';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
updateStyle: styleDescriptor => {
|
||||
dispatch(updateLayerStyleForSelectedLayer(styleDescriptor));
|
||||
},
|
||||
reset: () => dispatch(clearTemporaryStyles())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
EuiText
|
||||
} from '@elastic/eui';
|
||||
|
||||
export function StyleTabs({ layer, reset, updateStyle }) {
|
||||
export function StyleTabs({ layer, updateStyle }) {
|
||||
return layer.getSupportedStyles().map((Style, index) => {
|
||||
let description;
|
||||
if (Style.description) {
|
||||
|
@ -29,8 +29,7 @@ export function StyleTabs({ layer, reset, updateStyle }) {
|
|||
handleStyleChange: (styleDescriptor) => {
|
||||
updateStyle(styleDescriptor);
|
||||
},
|
||||
style: (Style.canEdit(currentStyle)) ? currentStyle : null,
|
||||
resetStyle: () => reset()
|
||||
style: (Style.canEdit(currentStyle)) ? currentStyle : null
|
||||
});
|
||||
|
||||
if (!styleEditor) {
|
||||
|
|
|
@ -10,9 +10,17 @@ import { TOCEntry } from './view';
|
|||
import { updateFlyout, FLYOUT_STATE } from '../../../../../store/ui';
|
||||
import { fitToLayerExtent, setSelectedLayer, toggleLayerVisible } from '../../../../../actions/store_actions';
|
||||
|
||||
import { hasDirtyState, getSelectedLayer } from '../../../../../selectors/map_selectors';
|
||||
|
||||
function mapStateToProps(state = {}) {
|
||||
return {
|
||||
zoom: _.get(state, 'map.mapState.zoom', 0)
|
||||
zoom: _.get(state, 'map.mapState.zoom', 0),
|
||||
getSelectedLayerSelector: () => {
|
||||
return getSelectedLayer(state);
|
||||
},
|
||||
hasDirtyStateSelector: () => {
|
||||
return hasDirtyState(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,15 +9,22 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiSpacer
|
||||
EuiSpacer,
|
||||
EuiOverlayMask,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { LayerTocActions } from '../../../../../shared/components/layer_toc_actions';
|
||||
|
||||
export class TOCEntry extends React.Component {
|
||||
|
||||
state = {
|
||||
displayName: null
|
||||
}
|
||||
displayName: null,
|
||||
shouldShowModal: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
|
@ -43,6 +50,51 @@ export class TOCEntry extends React.Component {
|
|||
this._updateDisplayName();
|
||||
}
|
||||
|
||||
_renderCancelModal() {
|
||||
if (!this.state.shouldShowModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
this.setState({
|
||||
shouldShowModal: false
|
||||
});
|
||||
};
|
||||
|
||||
const openPanel = () => {
|
||||
closeModal();
|
||||
this.props.openLayerPanel(this.props.layer.getId());
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiModal
|
||||
onClose={closeModal}
|
||||
>
|
||||
<EuiModalBody>
|
||||
There are unsaved changes to your layer. Are you sure you want to proceed?
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty
|
||||
onClick={closeModal}
|
||||
>
|
||||
Do not proceed
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiButton
|
||||
onClick={openPanel}
|
||||
fill
|
||||
>
|
||||
Proceed and discard changes
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
const { layer, openLayerPanel, zoom, toggleVisible, fitToBounds } = this.props;
|
||||
|
@ -69,12 +121,29 @@ export class TOCEntry extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
const cancelModal = this._renderCancelModal();
|
||||
|
||||
const openLayerPanelWithCheck = () => {
|
||||
const selectedLayer = this.props.getSelectedLayerSelector();
|
||||
if (selectedLayer && selectedLayer.getId() === this.props.layer.getId()) {
|
||||
return;
|
||||
}
|
||||
if (this.props.hasDirtyStateSelector()) {
|
||||
this.setState({
|
||||
shouldShowModal: true
|
||||
});
|
||||
} else {
|
||||
openLayerPanel(layer.getId());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mapTocEntry"
|
||||
id={layer.getId()}
|
||||
data-layerid={layer.getId()}
|
||||
>
|
||||
{cancelModal}
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
|
@ -89,7 +158,7 @@ export class TOCEntry extends React.Component {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<button
|
||||
onClick={() => openLayerPanel(layer.getId())}
|
||||
onClick={openLayerPanelWithCheck}
|
||||
data-test-subj={`mapOpenLayerButton${this.state.displayName}`}
|
||||
>
|
||||
<div style={{ width: 180 }} className="eui-textTruncate eui-textLeft">
|
||||
|
|
|
@ -15,6 +15,8 @@ import { HeatmapStyle } from '../shared/layers/styles/heatmap_style';
|
|||
import { TileStyle } from '../shared/layers/styles/tile_style';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../store/util';
|
||||
|
||||
function createLayerInstance(layerDescriptor) {
|
||||
const source = createSourceInstance(layerDescriptor.sourceDescriptor);
|
||||
const style = createStyleInstance(layerDescriptor.style);
|
||||
|
@ -63,7 +65,7 @@ export const getMapReady = ({ map }) => map && map.ready;
|
|||
|
||||
export const getGoto = ({ map }) => map && map.goto;
|
||||
|
||||
const getSelectedLayerId = ({ map }) => {
|
||||
export const getSelectedLayerId = ({ map }) => {
|
||||
return (!map.selectedLayerId || !map.layerList) ? null : map.selectedLayerId;
|
||||
};
|
||||
|
||||
|
@ -161,3 +163,11 @@ export const getUniqueIndexPatternIds = createSelector(
|
|||
);
|
||||
|
||||
export const getTemporaryLayers = createSelector(getLayerList, (layerList) => layerList.filter(layer => layer.isTemporary()));
|
||||
|
||||
export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => {
|
||||
return layerListRaw.some(layerDescriptor => {
|
||||
const currentState = copyPersistentState(layerDescriptor);
|
||||
const trackedState = layerDescriptor[TRACKED_LAYER_DESCRIPTOR];
|
||||
return (trackedState) ? !_.isEqual(currentState, trackedState) : false;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
syncLayerWithMB(mbMap) {
|
||||
|
||||
const mbSource = mbMap.getSource(this.getId());
|
||||
const heatmapLayerId = this.getId() + '_heatmap';
|
||||
const mbLayerId = this.getId() + '_heatmap';
|
||||
|
||||
if (!mbSource) {
|
||||
mbMap.addSource(this.getId(), {
|
||||
|
@ -58,7 +58,7 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
|
||||
|
||||
mbMap.addLayer({
|
||||
id: heatmapLayerId,
|
||||
id: mbLayerId,
|
||||
type: 'heatmap',
|
||||
source: this.getId(),
|
||||
paint: {}
|
||||
|
@ -86,15 +86,15 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
mbSourceAfter.setData(featureCollection);
|
||||
}
|
||||
|
||||
mbMap.setLayoutProperty(heatmapLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
|
||||
mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
|
||||
this._style.setMBPaintProperties({
|
||||
mbMap,
|
||||
layerId: heatmapLayerId,
|
||||
layerId: mbLayerId,
|
||||
propertyName: SCALED_PROPERTY_NAME,
|
||||
alpha: this.getAlpha(),
|
||||
resolution: this._source.getGridResolution()
|
||||
});
|
||||
mbMap.setLayerZoomRange(heatmapLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
|
||||
mbMap.setPaintProperty(mbLayerId, 'heatmap-opacity', this.getAlpha());
|
||||
mbMap.setLayerZoomRange(mbLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
|
||||
}
|
||||
|
||||
async getBounds(filters) {
|
||||
|
|
|
@ -41,7 +41,7 @@ export class HeatmapStyle extends AbstractStyle {
|
|||
return null;
|
||||
}
|
||||
|
||||
setMBPaintProperties({ alpha, mbMap, layerId, propertyName, resolution }) {
|
||||
setMBPaintProperties({ mbMap, layerId, propertyName, resolution }) {
|
||||
let radius;
|
||||
if (resolution === GRID_RESOLUTION.COARSE) {
|
||||
radius = 128;
|
||||
|
@ -57,7 +57,6 @@ export class HeatmapStyle extends AbstractStyle {
|
|||
type: 'identity',
|
||||
property: propertyName
|
||||
});
|
||||
mbMap.setPaintProperty(layerId, 'heatmap-opacity', alpha);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,8 +31,4 @@ export class TileStyle extends AbstractStyle {
|
|||
static getDisplayName() {
|
||||
return 'Tile style';
|
||||
}
|
||||
|
||||
setMBPaintProperties({ alpha, mbMap, layerId }) {
|
||||
mbMap.setPaintProperty(layerId, 'raster-opacity', alpha);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,11 +90,7 @@ export class TileLayer extends AbstractLayer {
|
|||
_setTileLayerProperties(mbMap, mbLayerId) {
|
||||
mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
|
||||
mbMap.setLayerZoomRange(mbLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
|
||||
this._style && this._style.setMBPaintProperties({
|
||||
alpha: this.getAlpha(),
|
||||
mbMap,
|
||||
layerId: mbLayerId,
|
||||
});
|
||||
mbMap.setPaintProperty(mbLayerId, 'raster-opacity', this.getAlpha());
|
||||
}
|
||||
|
||||
getLayerTypeIconName() {
|
||||
|
|
|
@ -475,7 +475,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
if (!mbSource) {
|
||||
mbMap.addSource(this.getId(), {
|
||||
type: 'geojson',
|
||||
data: { 'type': 'FeatureCollection', 'features': [] }
|
||||
data: EMPTY_FEATURE_COLLECTION
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -486,8 +486,8 @@ export class VectorLayer extends AbstractLayer {
|
|||
this._syncStylePropertiesWithMb(mbMap);
|
||||
}
|
||||
|
||||
renderStyleEditor(style, options) {
|
||||
return style.renderEditor({
|
||||
renderStyleEditor(Style, options) {
|
||||
return Style.renderEditor({
|
||||
layer: this,
|
||||
...options
|
||||
});
|
||||
|
@ -497,7 +497,6 @@ export class VectorLayer extends AbstractLayer {
|
|||
return this._source.canFormatFeatureProperties();
|
||||
}
|
||||
|
||||
|
||||
async _getPropertiesForTooltip(feature) {
|
||||
const tooltipsFromSource = await this._source.filterAndFormatProperties(feature.properties);
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@ import {
|
|||
SET_QUERY,
|
||||
UPDATE_LAYER_PROP,
|
||||
UPDATE_LAYER_STYLE,
|
||||
PROMOTE_TEMPORARY_STYLES,
|
||||
CLEAR_TEMPORARY_STYLES,
|
||||
SET_JOINS,
|
||||
TOUCH_LAYER,
|
||||
UPDATE_SOURCE_PROP,
|
||||
|
@ -34,13 +32,21 @@ import {
|
|||
CLEAR_MOUSE_COORDINATES,
|
||||
SET_GOTO,
|
||||
CLEAR_GOTO,
|
||||
TRACK_CURRENT_LAYER_STATE,
|
||||
ROLLBACK_TO_TRACKED_LAYER_STATE,
|
||||
REMOVE_TRACKED_LAYER_STATE
|
||||
} from "../actions/store_actions";
|
||||
|
||||
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from './util';
|
||||
|
||||
const getLayerIndex = (list, layerId) => list.findIndex(({ id }) => layerId === id);
|
||||
|
||||
const updateLayerInList = (state, id, attribute, newValue) => {
|
||||
const updateLayerInList = (state, layerId, attribute, newValue) => {
|
||||
if (!layerId) {
|
||||
return state;
|
||||
}
|
||||
const { layerList } = state;
|
||||
const layerIdx = getLayerIndex(layerList, id);
|
||||
const layerIdx = getLayerIndex(layerList, layerId);
|
||||
const updatedLayer = {
|
||||
...layerList[layerIdx],
|
||||
// Update layer w/ new value. If no value provided, toggle boolean value
|
||||
|
@ -94,8 +100,16 @@ const INITIAL_STATE = {
|
|||
waitingForMapReadyLayerList: [],
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function map(state = INITIAL_STATE, action) {
|
||||
switch (action.type) {
|
||||
case REMOVE_TRACKED_LAYER_STATE:
|
||||
return removeTrackedLayerState(state, action.layerId);
|
||||
case TRACK_CURRENT_LAYER_STATE:
|
||||
return trackCurrentLayerState(state, action.layerId);
|
||||
case ROLLBACK_TO_TRACKED_LAYER_STATE:
|
||||
return rollbackTrackedLayerState(state, action.layerId);
|
||||
case SET_MOUSE_COORDINATES:
|
||||
return {
|
||||
...state,
|
||||
|
@ -216,7 +230,6 @@ export function map(state = INITIAL_STATE, action) {
|
|||
case UPDATE_SOURCE_PROP:
|
||||
return updateLayerSourceDescriptorProp(state, action.layerId, action.propName, action.value);
|
||||
case SET_JOINS:
|
||||
console.warn('when setting joins, must remove all corresponding datarequests as well');
|
||||
const layerDescriptor = state.layerList.find(descriptor => descriptor.id === action.layer.getId());
|
||||
if (layerDescriptor) {
|
||||
const newLayerDescriptor = { ...layerDescriptor, joins: action.joins.slice() };
|
||||
|
@ -263,22 +276,8 @@ export function map(state = INITIAL_STATE, action) {
|
|||
return updateLayerInList(state, action.layerId, 'visible');
|
||||
case UPDATE_LAYER_STYLE:
|
||||
const styleLayerId = action.layerId;
|
||||
const styleLayerIdx = getLayerIndex(state.layerList, styleLayerId);
|
||||
const layerStyle = state.layerList[styleLayerIdx].style;
|
||||
const layerPrevStyle = layerStyle.__previousStyle || layerStyle;
|
||||
return updateLayerInList(state, styleLayerId, 'style',
|
||||
{ ...action.style, __previousStyle: { ...layerPrevStyle } });
|
||||
case PROMOTE_TEMPORARY_STYLES:
|
||||
const stylePromoteIdx = getLayerIndex(state.layerList, state.selectedLayerId);
|
||||
const styleToSet = {
|
||||
...state.layerList[stylePromoteIdx].style,
|
||||
__previousStyle: null
|
||||
};
|
||||
return updateLayerInList(state, state.selectedLayerId, 'style', styleToSet);
|
||||
case CLEAR_TEMPORARY_STYLES:
|
||||
const styleClearIdx = getLayerIndex(state.layerList, state.selectedLayerId);
|
||||
const prevStyleToLoad = state.layerList[styleClearIdx].style.__previousStyle || state.layerList[styleClearIdx].style || {};
|
||||
return updateLayerInList(state, state.selectedLayerId, 'style', prevStyleToLoad);
|
||||
{ ...action.style });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -359,3 +358,50 @@ function getValidDataRequest(state, action, checkRequestToken = true) {
|
|||
function findLayerById(state, id) {
|
||||
return state.layerList.find(layer => layer.id === id);
|
||||
}
|
||||
|
||||
function trackCurrentLayerState(state, layerId) {
|
||||
const layer = findLayerById(state, layerId);
|
||||
const layerCopy = copyPersistentState(layer);
|
||||
return updateLayerInList(state, layerId, TRACKED_LAYER_DESCRIPTOR, layerCopy);
|
||||
}
|
||||
|
||||
function removeTrackedLayerState(state, layerId) {
|
||||
const layer = findLayerById(state, layerId);
|
||||
if (!layer) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const copyLayer = { ...layer };
|
||||
delete copyLayer[TRACKED_LAYER_DESCRIPTOR];
|
||||
|
||||
return {
|
||||
...state,
|
||||
layerList: replaceInLayerList(state.layerList, layerId, copyLayer)
|
||||
};
|
||||
}
|
||||
|
||||
function rollbackTrackedLayerState(state, layerId) {
|
||||
const layer = findLayerById(state, layerId);
|
||||
if (!layer) {
|
||||
return state;
|
||||
}
|
||||
const trackedLayerDescriptor = layer[TRACKED_LAYER_DESCRIPTOR];
|
||||
|
||||
//this assumes that any nested temp-state in the layer-descriptor (e.g. of styles), is not relevant and can be recovered easily (e.g. this is not the case for __dataRequests)
|
||||
//That assumption is true in the context of this app, but not generalizable.
|
||||
//consider rewriting copyPersistentState to only strip the first level of temp state.
|
||||
const rolledbackLayer = { ...layer, ...trackedLayerDescriptor };
|
||||
delete rolledbackLayer[TRACKED_LAYER_DESCRIPTOR];
|
||||
|
||||
return {
|
||||
...state,
|
||||
layerList: replaceInLayerList(state.layerList, layerId, rolledbackLayer)
|
||||
};
|
||||
}
|
||||
|
||||
function replaceInLayerList(layerList, layerId, newLayerDescriptor) {
|
||||
const layerIndex = getLayerIndex(layerList, layerId);
|
||||
const newLayerList = [...layerList];
|
||||
newLayerList[layerIndex] = newLayerDescriptor;
|
||||
return newLayerList;
|
||||
}
|
||||
|
|
21
x-pack/plugins/maps/public/store/util.js
Normal file
21
x-pack/plugins/maps/public/store/util.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
export const TRACKED_LAYER_DESCRIPTOR = '__trackedLayerDescriptor';
|
||||
|
||||
export function copyPersistentState(input) {
|
||||
if (typeof input !== 'object' || input === null) {//primitive
|
||||
return input;
|
||||
}
|
||||
const copyInput = Array.isArray(input) ? [] : {};
|
||||
for(const key in input) {
|
||||
if (!key.startsWith('__')) {
|
||||
copyInput[key] = copyPersistentState(input[key]);
|
||||
}
|
||||
}
|
||||
return copyInput;
|
||||
}
|
53
x-pack/plugins/maps/public/store/util.test.js
Normal file
53
x-pack/plugins/maps/public/store/util.test.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { copyPersistentState } from './util';
|
||||
|
||||
describe('store/util', () => {
|
||||
|
||||
|
||||
|
||||
describe('copyPersistentState', () => {
|
||||
|
||||
it('should ignore state preceded by double underscores', async () => {
|
||||
const copy = copyPersistentState({
|
||||
foo: 'bar',
|
||||
nested: {
|
||||
bar: 'foo',
|
||||
__bar: 'foo__'
|
||||
}
|
||||
});
|
||||
|
||||
expect(copy).toEqual({
|
||||
foo: 'bar',
|
||||
nested: {
|
||||
bar: 'foo'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should copy null value correctly', async () => {
|
||||
const copy = copyPersistentState({
|
||||
foo: 'bar',
|
||||
nested: {
|
||||
nullval: null,
|
||||
bar: 'foo',
|
||||
__bar: 'foo__'
|
||||
}
|
||||
});
|
||||
|
||||
expect(copy).toEqual({
|
||||
foo: 'bar',
|
||||
nested: {
|
||||
nullval: null,
|
||||
bar: 'foo'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue