Skip to content
1 change: 1 addition & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export const emsWorldLayerId = 'world_countries';
export enum WIZARD_ID {
CHOROPLETH = 'choropleth',
GEO_FILE = 'uploadGeoFile',
LAYER_GROUP = 'layerGroup',
NEW_VECTOR = 'newVectorLayer',
OBSERVABILITY = 'observabilityLayer',
SECURITY = 'securityLayer',
Expand Down
39 changes: 34 additions & 5 deletions x-pack/plugins/maps/public/actions/layer_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ export function removePreviewLayers() {
) => {
getLayerList(getState()).forEach((layer) => {
if (layer.isPreviewLayer()) {
if (isLayerGroup(layer)) {
dispatch(ungroupLayer(layer.getId()));
}
dispatch(removeLayer(layer.getId()));
}
});
Expand Down Expand Up @@ -613,11 +616,21 @@ export function setLayerQuery(id: string, query: Query) {
}

export function setLayerParent(id: string, parent: string | undefined) {
return {
type: UPDATE_LAYER_PROP,
id,
propName: 'parent',
newValue: parent,
return (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
dispatch({
type: UPDATE_LAYER_PROP,
id,
propName: 'parent',
newValue: parent,
});

if (parent) {
// Open parent layer details. Without opening parent details, layer disappears from legend and this confuses users
dispatch(showTOCDetails(parent));
}
};
}

Expand Down Expand Up @@ -863,6 +876,22 @@ export function createLayerGroup(draggedLayerId: string, combineLayerId: string)
};
}

function ungroupLayer(layerId: string) {
return (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const layer = getLayerList(getState()).find((findLayer) => findLayer.getId() === layerId);
if (!layer || !isLayerGroup(layer)) {
return;
}

(layer as LayerGroup).getChildren().forEach((childLayer) => {
dispatch(setLayerParent(childLayer.getId(), layer.getParent()));
});
};
}

export function moveLayerToLeftOfTarget(moveLayerId: string, targetLayerId: string) {
return (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export { isLayerGroup, LayerGroup } from './layer_group';
export { DEFAULT_LAYER_GROUP_LABEL, isLayerGroup, LayerGroup } from './layer_group';
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export function isLayerGroup(layer: ILayer) {
return layer instanceof LayerGroup;
}

export const DEFAULT_LAYER_GROUP_LABEL = i18n.translate('xpack.maps.layerGroup.defaultName', {
defaultMessage: 'Layer group',
});

export class LayerGroup implements ILayer {
protected readonly _descriptor: LayerGroupDescriptor;
private _children: ILayer[] = [];
Expand All @@ -48,9 +52,7 @@ export class LayerGroup implements ILayer {
label:
typeof options.label === 'string' && options.label.length
? options.label
: i18n.translate('xpack.maps.layerGroup.defaultName', {
defaultMessage: 'Layer group',
}),
: DEFAULT_LAYER_GROUP_LABEL,
sourceDescriptor: null,
visible: typeof options.visible === 'boolean' ? options.visible : true,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import React from 'react';
import { LayerWizard, RenderWizardArguments } from '../layer_wizard_registry';
import { LayerGroupWizard } from './wizard';
import { WIZARD_ID } from '../../../../../common/constants';

export const layerGroupWizardConfig: LayerWizard = {
id: WIZARD_ID.LAYER_GROUP,
order: 10,
categories: [],
description: i18n.translate('xpack.maps.layerGroupWizard.description', {
defaultMessage: 'Organize related layers in a hierarchy',
}),
icon: 'layers',
renderWizard: (renderWizardArguments: RenderWizardArguments) => {
return <LayerGroupWizard {...renderWizardArguments} />;
},
title: i18n.translate('xpack.maps.layerGroupWizard.title', {
defaultMessage: 'Layer group',
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export { layerGroupWizardConfig } from './config';
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import React, { ChangeEvent, Component } from 'react';
import { EuiFieldText, EuiFormRow, EuiPanel } from '@elastic/eui';
import { RenderWizardArguments } from '../layer_wizard_registry';
import { DEFAULT_LAYER_GROUP_LABEL, LayerGroup } from '../../layer_group';

interface State {
label: string;
}

export class LayerGroupWizard extends Component<RenderWizardArguments, State> {
state: State = {
label: DEFAULT_LAYER_GROUP_LABEL,
};

componentDidMount() {
this._previewLayer();
}

_onLabelChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState(
{
label: e.target.value,
},
this._previewLayer
);
};

_previewLayer() {
const layerDescriptor = LayerGroup.createDescriptor({
label: this.state.label,
});

this.props.previewLayers([layerDescriptor]);
}

render() {
return (
<EuiPanel>
<EuiFormRow
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.layerNameLabel', {
defaultMessage: 'Name',
})}
>
<EuiFieldText value={this.state.label} onChange={this._onLabelChange} />
</EuiFormRow>
</EuiPanel>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { registerLayerWizardInternal } from './layer_wizard_registry';
import { uploadLayerWizardConfig } from './file_upload_wizard';
import { layerGroupWizardConfig } from './layer_group_wizard';
import {
esDocumentsLayerWizardConfig,
esTopHitsLayerWizardConfig,
Expand Down Expand Up @@ -36,6 +37,7 @@ export function registerLayerWizards() {
}

registerLayerWizardInternal(uploadLayerWizardConfig);
registerLayerWizardInternal(layerGroupWizardConfig);
registerLayerWizardInternal(esDocumentsLayerWizardConfig);
registerLayerWizardInternal(choroplethLayerWizardConfig);
registerLayerWizardInternal(clustersLayerWizardConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,26 @@ export class TOCEntry extends Component<Props, State> {
);
};

_hightlightAsSelectedLayer() {
if (this.props.isCombineLayer) {
return false;
}

if (this.props.layer.isPreviewLayer()) {
return true;
}

return (
this.props.selectedLayer && this.props.selectedLayer.getId() === this.props.layer.getId()
);
}

render() {
const classes = classNames('mapTocEntry', {
'mapTocEntry-isDragging': this.props.isDragging,
'mapTocEntry-isDraggingOver': this.props.isDraggingOver,
'mapTocEntry-isCombineLayer': this.props.isCombineLayer,
'mapTocEntry-isSelected':
this.props.layer.isPreviewLayer() ||
(this.props.selectedLayer && this.props.selectedLayer.getId() === this.props.layer.getId()),
'mapTocEntry-isSelected': this._hightlightAsSelectedLayer(),
'mapTocEntry-isInEditingMode': this.props.isFeatureEditorOpenForLayer,
});

Expand Down