diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 94ae72a050e21..117bfa0eaaeaf 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -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', diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 5ba9edaee58d0..38cadc8d1363a 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -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())); } }); @@ -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, + 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)); + } }; } @@ -863,6 +876,22 @@ export function createLayerGroup(draggedLayerId: string, combineLayerId: string) }; } +function ungroupLayer(layerId: string) { + return ( + dispatch: ThunkDispatch, + 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, diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts index 3b2848d03f5ff..085ea4e8afea6 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isLayerGroup, LayerGroup } from './layer_group'; +export { DEFAULT_LAYER_GROUP_LABEL, isLayerGroup, LayerGroup } from './layer_group'; diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx index c1a2a2964a315..7fa48628ef0db 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -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[] = []; @@ -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, }; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/config.tsx b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/config.tsx new file mode 100644 index 0000000000000..4418b20cf6613 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/config.tsx @@ -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 ; + }, + title: i18n.translate('xpack.maps.layerGroupWizard.title', { + defaultMessage: 'Layer group', + }), +}; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/index.ts b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/index.ts new file mode 100644 index 0000000000000..06bd343d84a38 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/index.ts @@ -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'; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/wizard.tsx new file mode 100644 index 0000000000000..2668730e694c8 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/wizards/layer_group_wizard/wizard.tsx @@ -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 { + state: State = { + label: DEFAULT_LAYER_GROUP_LABEL, + }; + + componentDidMount() { + this._previewLayer(); + } + + _onLabelChange = (e: ChangeEvent) => { + this.setState( + { + label: e.target.value, + }, + this._previewLayer + ); + }; + + _previewLayer() { + const layerDescriptor = LayerGroup.createDescriptor({ + label: this.state.label, + }); + + this.props.previewLayers([layerDescriptor]); + } + + render() { + return ( + + + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts index aa772d44341e6..fbaba6325bf26 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts @@ -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, @@ -36,6 +37,7 @@ export function registerLayerWizards() { } registerLayerWizardInternal(uploadLayerWizardConfig); + registerLayerWizardInternal(layerGroupWizardConfig); registerLayerWizardInternal(esDocumentsLayerWizardConfig); registerLayerWizardInternal(choroplethLayerWizardConfig); registerLayerWizardInternal(clustersLayerWizardConfig); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx index 012c1e97ad528..ed426011e995a 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx @@ -311,14 +311,26 @@ export class TOCEntry extends Component { ); }; + _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, });