From 77a2e90895b44b08ebfbf98ebc5738bf7c7b51b5 Mon Sep 17 00:00:00 2001 From: Junqiu Lei Date: Wed, 12 Oct 2022 17:37:59 -0700 Subject: [PATCH] add base layer functions Signed-off-by: Junqiu Lei --- src/plugins/maps_dashboards/common/index.ts | 12 +- .../add_layer_panel/add_layer_panel.tsx | 18 +- .../base_map_layer_config_panel.tsx | 58 +++++ .../public/components/layer_config/index.ts | 1 + .../layer_config/layer_config_panel.tsx | 72 ++++-- .../MaplibreLayersPanelControl.tsx | 27 --- .../components/layer_control_panel/index.ts | 1 - .../layer_control_panel.tsx | 228 +++++++++++++----- .../map_container/map_container.scss | 24 ++ .../map_container/map_container.tsx | 25 +- src/plugins/maps_dashboards/public/index.scss | 13 - .../public/model/ILayerConfig.ts | 12 +- 12 files changed, 348 insertions(+), 143 deletions(-) create mode 100644 src/plugins/maps_dashboards/public/components/layer_config/base_map_layer_config_panel.tsx delete mode 100644 src/plugins/maps_dashboards/public/components/layer_control_panel/MaplibreLayersPanelControl.tsx create mode 100644 src/plugins/maps_dashboards/public/components/map_container/map_container.scss diff --git a/src/plugins/maps_dashboards/common/index.ts b/src/plugins/maps_dashboards/common/index.ts index 3688f479..e131abe8 100644 --- a/src/plugins/maps_dashboards/common/index.ts +++ b/src/plugins/maps_dashboards/common/index.ts @@ -19,9 +19,11 @@ export const APP_PATH = { CREATE_MAP: '/create-map', }; -export enum LAYER_TYPE { - BASE_MAP = 'base map', - CLUSTER_MAP = 'cluster', - CHOROPLETH_MAP = 'choropleth', - WEB_MAP_SERVICE = 'wms', +export enum DASHBOARDS_MAPS_LAYER_TYPE { + OPENSEARCH_MAP = 'OpenSearch Map', } + +export const LAYER_VISIBILITY = { + NONE: 'none', + VISIBLE: 'visible', +}; diff --git a/src/plugins/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx b/src/plugins/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx index e4a43cf1..45fa843f 100644 --- a/src/plugins/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx +++ b/src/plugins/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx @@ -19,16 +19,24 @@ import { EuiKeyPadMenuItem, } from '@elastic/eui'; import './add_layer_panel.scss'; -import { LAYER_TYPE } from '../../../common'; +import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../../common'; interface Props { setIsLayerConfigVisible: Function; + setSelectedLayerConfig: Function; + addNewLayerToList: Function; } -export const AddLayerPanel = ({ setIsLayerConfigVisible }: Props) => { +export const AddLayerPanel = ({ setIsLayerConfigVisible, setSelectedLayerConfig }: Props) => { const [isAddNewLayerModalVisible, setIsAddNewLayerModalVisible] = useState(false); - const availableLayers = Object.values(LAYER_TYPE).map((layerItem, index) => { + function onClickAddNewLayer(layerItem: string) { + // Will add new layer logic + setIsAddNewLayerModalVisible(false); + setIsLayerConfigVisible(true); + } + + const availableLayers = Object.values(DASHBOARDS_MAPS_LAYER_TYPE).map((layerItem, index) => { return ( @@ -37,8 +45,7 @@ export const AddLayerPanel = ({ setIsLayerConfigVisible }: Props) => { size="xxl" color="primary" onClick={() => { - setIsAddNewLayerModalVisible(false); - setIsLayerConfigVisible(true); + onClickAddNewLayer(layerItem); }} /> @@ -88,7 +95,6 @@ export const AddLayerPanel = ({ setIsLayerConfigVisible }: Props) => {

Add layer

- { + const onZoomChange = (value: number[]) => { + setSelectedLayerConfig({ ...selectedLayerConfig, zoomRange: value }); + }; + + const onOpacityChange = (e) => { + setSelectedLayerConfig({ ...selectedLayerConfig, opacity: Number(e.target.value) }); + }; + + const onNameChange = (e) => { + setSelectedLayerConfig({ ...selectedLayerConfig, name: String(e.target.value) }); + }; + + return ( + + + + + + + + + + + + ); +}; diff --git a/src/plugins/maps_dashboards/public/components/layer_config/index.ts b/src/plugins/maps_dashboards/public/components/layer_config/index.ts index 9f6f2e88..4d043e76 100644 --- a/src/plugins/maps_dashboards/public/components/layer_config/index.ts +++ b/src/plugins/maps_dashboards/public/components/layer_config/index.ts @@ -4,3 +4,4 @@ */ export { LayerConfigPanel } from './layer_config_panel'; +export { BaseMapLayerConfigPanel } from './base_map_layer_config_panel'; diff --git a/src/plugins/maps_dashboards/public/components/layer_config/layer_config_panel.tsx b/src/plugins/maps_dashboards/public/components/layer_config/layer_config_panel.tsx index c64bef52..f1802d4b 100644 --- a/src/plugins/maps_dashboards/public/components/layer_config/layer_config_panel.tsx +++ b/src/plugins/maps_dashboards/public/components/layer_config/layer_config_panel.tsx @@ -10,38 +10,82 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - EuiCodeBlock, EuiTitle, + EuiFlexItem, + EuiButtonEmpty, + EuiFlexGroup, } from '@elastic/eui'; import { ILayerConfig } from '../../model/ILayerConfig'; +import { BaseMapLayerConfigPanel } from './index'; +import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../../common'; interface Props { setIsLayerConfigVisible: Function; selectedLayerConfig: ILayerConfig; + setSelectedLayerConfig: Function; + addNewLayerFunction: Function; + updateLayer: Function; } -export const LayerConfigPanel = ({ setIsLayerConfigVisible, selectedLayerConfig }: Props) => { +export const LayerConfigPanel = ({ + setIsLayerConfigVisible, + selectedLayerConfig, + setSelectedLayerConfig, + updateLayer, +}: Props) => { + const onClose = () => { + setIsLayerConfigVisible(false); + setSelectedLayerConfig({}); + }; + const onUpdate = () => { + updateLayer(); + selectedLayerConfig.update?.(); + onClose(); + }; + return ( - setIsLayerConfigVisible(false)}> +

{selectedLayerConfig.name}

- - {JSON.stringify(selectedLayerConfig)} - + + + +

Layer settings

+
+
+ + {selectedLayerConfig.type === DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP && ( + + )} + +
- setIsLayerConfigVisible(false)}>Close + + + + Discard + + + + + Update + + +
); diff --git a/src/plugins/maps_dashboards/public/components/layer_control_panel/MaplibreLayersPanelControl.tsx b/src/plugins/maps_dashboards/public/components/layer_control_panel/MaplibreLayersPanelControl.tsx deleted file mode 100644 index 6e276134..00000000 --- a/src/plugins/maps_dashboards/public/components/layer_control_panel/MaplibreLayersPanelControl.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { LayerControlPanel } from './layer_control_panel'; - -// Render layer panel on top of Maplibre -class MaplibreLayersPanelControl { - private _container: HTMLElement | undefined; - - onAdd(map: any) { - this._container = document.createElement('div'); - ReactDOM.render(, this._container); - return this._container; - } - - onRemove() { - if (this._container && this._container.parentNode) { - this._container.parentNode.removeChild(this._container); - } - } -} - -export { MaplibreLayersPanelControl }; diff --git a/src/plugins/maps_dashboards/public/components/layer_control_panel/index.ts b/src/plugins/maps_dashboards/public/components/layer_control_panel/index.ts index 00ed4cfb..30cd7379 100644 --- a/src/plugins/maps_dashboards/public/components/layer_control_panel/index.ts +++ b/src/plugins/maps_dashboards/public/components/layer_control_panel/index.ts @@ -4,4 +4,3 @@ */ export { LayerControlPanel } from './layer_control_panel'; -export { MaplibreLayersPanelControl } from './MaplibreLayersPanelControl'; diff --git a/src/plugins/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx b/src/plugins/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx index 27a2fba2..3f1c71d9 100644 --- a/src/plugins/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx +++ b/src/plugins/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiPanel, EuiTitle, @@ -15,40 +15,117 @@ import { EuiButton, } from '@elastic/eui'; import { I18nProvider } from '@osd/i18n/react'; +import { Map as Maplibre } from 'maplibre-gl'; import './layer_control_panel.scss'; import { AddLayerPanel } from '../add_layer_panel'; import { LayerConfigPanel } from '../layer_config'; -import { LAYER_TYPE } from '../../../common'; import { ILayerConfig } from '../../model/ILayerConfig'; +import { DASHBOARDS_MAPS_LAYER_TYPE, MAP_VECTOR_TILE_URL, LAYER_VISIBILITY } from '../../../common'; -const LayerControlPanel = () => { +interface MaplibreRef { + current: Maplibre | null; +} + +interface Props { + maplibreRef: MaplibreRef; +} + +const LayerControlPanel = ({ maplibreRef }: Props) => { const [isLayerConfigVisible, setIsLayerConfigVisible] = useState(false); const [isLayerControlVisible, setIsLayerControlVisible] = useState(true); + const [selectedLayerConfig, setSelectedLayerConfig] = useState({ iconType: '', - label: '', name: '', type: '', id: '', + zoomRange: [], + opacity: 1, + visibility: '', }); - // TODO: replace it once layers model ready - const demoLayers: ILayerConfig[] = [ + const [layers, setLayers] = useState([ { - label: 'Base Map', iconType: 'visMapRegion', id: 'example_id_1', - type: LAYER_TYPE.BASE_MAP, + type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP, name: 'Base Map Layer', + zoomRange: [0, 12], + opacity: 1, + visibility: LAYER_VISIBILITY.VISIBLE, + update() { + const maplibreInstance = maplibreRef.current; + if (maplibreInstance) { + const baseMapJson = maplibreInstance.getStyle().layers; + if (baseMapJson) { + baseMapJson.forEach((mbLayer) => { + maplibreInstance.setLayerZoomRange(mbLayer.id, this.zoomRange[0], this.zoomRange[1]); + // it will catch error when update opacity in symbol type layer, need figure out later + if (mbLayer.type === 'symbol') { + return; + } + maplibreInstance.setPaintProperty( + mbLayer.id, + `${mbLayer.type}-opacity`, + this.opacity + ); + maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', this.visibility); + }); + } else { + maplibreInstance.setStyle(MAP_VECTOR_TILE_URL); + } + } + }, + hide() { + const maplibreInstance = maplibreRef.current; + if (maplibreInstance) { + const baseMapJson = maplibreInstance.getStyle().layers; + if (baseMapJson) { + baseMapJson.forEach((mbLayer) => { + maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', this.visibility); + }); + } + } + }, + remove() { + const maplibreInstance = maplibreRef.current; + if (maplibreInstance) { + const baseMapJson = maplibreInstance.getStyle().layers; + if (baseMapJson) { + baseMapJson.forEach((mbLayer) => { + maplibreInstance.removeLayer(mbLayer.id); + }); + } + } + }, }, - { - label: 'Cluster Layer', - iconType: 'visMapRegion', - id: 'example_id_2', - type: LAYER_TYPE.CLUSTER_MAP, - name: 'Cluster Layer', - }, - ]; + ]); + + useEffect(() => { + layers.forEach((layer) => { + layer.update?.(); + }); + }, [layers]); + + const updateLayers = () => { + const layersClone = [...layers]; + const index = layersClone.findIndex((layer) => layer.id === selectedLayerConfig.id); + if (index > -1) { + layersClone[index] = { + ...layersClone[index], + ...selectedLayerConfig, + }; + } else { + layersClone.push(selectedLayerConfig); + } + setLayers(layersClone); + }; + + const removeLayer = (index: number) => { + const layersClone = [...layers]; + layersClone.splice(index, 1); + setLayers(layersClone); + }; if (isLayerControlVisible) { return ( @@ -77,66 +154,87 @@ const LayerControlPanel = () => { />
- - {demoLayers.map((layer) => ( -
- { - setSelectedLayerConfig(layer); - if (selectedLayerConfig.id === layer.id && isLayerConfigVisible === false) { - setIsLayerConfigVisible((visible) => !visible); - } - }} - > - - { + const isDisabled = + isLayerConfigVisible && selectedLayerConfig && selectedLayerConfig.id === layer.id; + return ( +
+ { + if (!isLayerConfigVisible) { + setSelectedLayerConfig(layer); } - /> - - - - window.alert('Hidden button clicked')} - aria-label="Hide or show layer" - color="text" - /> - - - window.alert('Delete button clicked')} - aria-label="Delete layer" - color="text" + }} + > + + { + if (selectedLayerConfig.id === layer.id && !isLayerConfigVisible) { + setIsLayerConfigVisible(true); + } + }} /> + + + { + if (layer.visibility === LAYER_VISIBILITY.VISIBLE) { + layer.visibility = LAYER_VISIBILITY.NONE; + } else { + layer.visibility = LAYER_VISIBILITY.VISIBLE; + } + layer.hide(index); + }} + aria-label="Hide or show layer" + color="text" + isDisabled={isDisabled} + /> + + + { + layer.remove(index); + removeLayer(index); + }} + aria-label="Delete layer" + color="text" + isDisabled={isDisabled} + /> + + - - -
- ))} + +
+ ); + })} {isLayerConfigVisible && ( )} - + diff --git a/src/plugins/maps_dashboards/public/components/map_container/map_container.scss b/src/plugins/maps_dashboards/public/components/map_container/map_container.scss new file mode 100644 index 00000000..e73cad5b --- /dev/null +++ b/src/plugins/maps_dashboards/public/components/map_container/map_container.scss @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +@import "maplibre-gl/dist/maplibre-gl.css"; + +/* stylelint-disable no-empty-source */ +.map-container { + width: 100%; + min-height: calc(100vh - 98px); +} + +.maplibregl-ctrl-top-left { + left: $euiSizeS; + top: $euiSizeS; +} + +.layerControlPanel-container { + z-index: 1; + position: absolute; + left: $euiSizeS; + top: $euiSizeS; +} diff --git a/src/plugins/maps_dashboards/public/components/map_container/map_container.tsx b/src/plugins/maps_dashboards/public/components/map_container/map_container.tsx index 42d7eb36..8d53d3e7 100644 --- a/src/plugins/maps_dashboards/public/components/map_container/map_container.tsx +++ b/src/plugins/maps_dashboards/public/components/map_container/map_container.tsx @@ -5,31 +5,36 @@ import React, { useEffect, useRef } from 'react'; import { Map as Maplibre, NavigationControl } from 'maplibre-gl'; -import { MaplibreLayersPanelControl } from '../../components/layer_control_panel'; -import '../../index.scss'; +import { LayerControlPanel } from '../layer_control_panel'; +import './map_container.scss'; import { MAP_VECTOR_TILE_URL, MAP_INITIAL_STATE } from '../../../common/index'; export const MapContainer = () => { + const maplibreRef = useRef(null); const mapContainer = useRef(null); useEffect(() => { const mapStyle = MAP_VECTOR_TILE_URL; - const initialState = MAP_INITIAL_STATE; - const map = new Maplibre({ + const maplibre = new Maplibre({ // @ts-ignore container: mapContainer.current, - style: `${mapStyle}`, center: [initialState.lng, initialState.lat], zoom: initialState.zoom, - logoPosition: 'bottom-left', }); - map.addControl(new MaplibreLayersPanelControl(), 'top-left'); - map.addControl(new NavigationControl({ showCompass: false }), 'top-right'); + maplibre.setStyle(mapStyle); + maplibreRef.current = maplibre; + maplibre.addControl(new NavigationControl({ showCompass: false }), 'top-right'); }, []); - // render the map DOM - return
; + return ( +
+
+ +
+
+
+ ); }; diff --git a/src/plugins/maps_dashboards/public/index.scss b/src/plugins/maps_dashboards/public/index.scss index 4288e690..a850c169 100644 --- a/src/plugins/maps_dashboards/public/index.scss +++ b/src/plugins/maps_dashboards/public/index.scss @@ -2,16 +2,3 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - -@import "maplibre-gl/dist/maplibre-gl.css"; - -/* stylelint-disable no-empty-source */ -.map-container { - width: 100%; - min-height: calc(100vh - 98px); -} - -.maplibregl-ctrl-top-left { - left: $euiSizeS; - top: $euiSizeS; -} diff --git a/src/plugins/maps_dashboards/public/model/ILayerConfig.ts b/src/plugins/maps_dashboards/public/model/ILayerConfig.ts index e7a80a4a..f9c54749 100644 --- a/src/plugins/maps_dashboards/public/model/ILayerConfig.ts +++ b/src/plugins/maps_dashboards/public/model/ILayerConfig.ts @@ -1,9 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ -//TODO: this is used for UI component development, remove or update it once layer model ready export interface ILayerConfig { name: string; id: string; type: string; - label: string; iconType: string; + zoomRange: number[]; + opacity: number; + visibility: string; + update: any; + remove: any; + hide: any; }