From 1d768bd09d7d20284663239903463287701131e2 Mon Sep 17 00:00:00 2001 From: Junqiu Lei Date: Wed, 9 Nov 2022 22:11:49 -0600 Subject: [PATCH] Integrate layerList to store and add breadcrumbs (#80) * Integrate layerList to store and add breadcrumbs Signed-off-by: Junqiu Lei --- maps_dashboards/common/index.ts | 7 +- maps_dashboards/public/components/app.tsx | 4 +- .../layer_control_panel.tsx | 65 ++++--- .../map_container/map_container.tsx | 45 +++-- .../public/components/map_page/map_page.tsx | 31 +++- .../map_top_nav/get_top_nav_config.tsx | 59 +++++-- .../components/map_top_nav/top_nav_menu.tsx | 40 ++++- .../public/components/maps_list/maps_list.tsx | 39 +++-- .../public/model/OSMLayerFunctions.ts | 165 ++++++++---------- .../public/model/layersFunctions.ts | 2 +- maps_dashboards/public/plugin.ts | 1 + maps_dashboards/public/types.ts | 1 + maps_dashboards/public/utils/breadcrumbs.ts | 38 ++++ 13 files changed, 318 insertions(+), 179 deletions(-) create mode 100644 maps_dashboards/public/utils/breadcrumbs.ts diff --git a/maps_dashboards/common/index.ts b/maps_dashboards/common/index.ts index a45056be..616e28c0 100644 --- a/maps_dashboards/common/index.ts +++ b/maps_dashboards/common/index.ts @@ -9,6 +9,9 @@ export const PLUGIN_NAME = 'Maps'; export const MAP_VECTOR_TILE_BASIC_STYLE = 'https://tiles.maps.opensearch.org/styles/basic.json'; export const MAP_GLYPHS = 'https://tiles.maps.opensearch.org/fonts/{fontstack}/{range}.pbf'; export const MAP_VECTOR_TILE_DATA_SOURCE = 'https://tiles.maps.opensearch.org/data/v1.json'; +export const MAP_DEFAULT_MIN_ZOOM = 0; +export const MAP_DEFAULT_MAX_ZOOM = 22; +export const MAP_DEFAULT_OPACITY = 1; // Starting position [lng, lat] and zoom export const MAP_INITIAL_STATE = { @@ -18,7 +21,9 @@ export const MAP_INITIAL_STATE = { }; export const APP_PATH = { - CREATE_MAP: '/create-map', + LANDING_PAGE_PATH: '/', + CREATE_MAP: '/create', + EDIT_MAP: '/:id', }; export enum DASHBOARDS_MAPS_LAYER_TYPE { diff --git a/maps_dashboards/public/components/app.tsx b/maps_dashboards/public/components/app.tsx index 4677f7b6..207a6906 100644 --- a/maps_dashboards/public/components/app.tsx +++ b/maps_dashboards/public/components/app.tsx @@ -22,8 +22,8 @@ export const MapsDashboardsApp = () => {
- } /> - } /> + } /> + } />
diff --git a/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx b/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx index f690c2b7..d67497b2 100644 --- a/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx +++ b/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect, useState } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import { EuiPanel, EuiTitle, @@ -25,6 +25,9 @@ import { DASHBOARDS_MAPS_LAYER_TYPE, LAYER_VISIBILITY, MAP_VECTOR_TILE_BASIC_STYLE, + MAP_DEFAULT_OPACITY, + MAP_DEFAULT_MAX_ZOOM, + MAP_DEFAULT_MIN_ZOOM, } from '../../../common'; import { layersFunctionMap } from '../../model/layersFunctions'; @@ -34,46 +37,49 @@ interface MaplibreRef { interface Props { maplibreRef: MaplibreRef; + mapIdFromUrl: string; + setLayers: (layers: ILayerConfig[]) => void; + layers: ILayerConfig[]; } -const LayerControlPanel = ({ maplibreRef }: Props) => { +const LayerControlPanel = memo(({ maplibreRef, mapIdFromUrl, setLayers, layers }: Props) => { const [isLayerConfigVisible, setIsLayerConfigVisible] = useState(false); const [isLayerControlVisible, setIsLayerControlVisible] = useState(true); + const [selectedLayerConfig, setSelectedLayerConfig] = useState(); - const [selectedLayerConfig, setSelectedLayerConfig] = useState({ - iconType: '', - name: '', - type: '', - id: '', - zoomRange: [], - opacity: 1, - visibility: '', - }); - - const initialLoadLayer: ILayerConfig = { + const initialDefaultLayer: ILayerConfig = { iconType: 'visMapRegion', id: uuidv4(), type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP, name: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP, - zoomRange: [0, 22], - opacity: 1, + zoomRange: [MAP_DEFAULT_MIN_ZOOM, MAP_DEFAULT_MAX_ZOOM], + opacity: MAP_DEFAULT_OPACITY, visibility: LAYER_VISIBILITY.VISIBLE, layerSpec: { OSMUrl: MAP_VECTOR_TILE_BASIC_STYLE, }, }; - const [layers, setLayers] = useState([initialLoadLayer]); - + // Initially load the layers from the saved object useEffect(() => { - maplibreRef.current?.on('load', function () { + if (layers && mapIdFromUrl) { layers.forEach((layer) => { - layersFunctionMap[layer.type]?.initial(maplibreRef, layer); + layersFunctionMap[layer.type]?.initialize(maplibreRef, layer); + }); + } else { + maplibreRef.current?.on('load', function () { + if (!mapIdFromUrl) { + layersFunctionMap[initialDefaultLayer.type]?.initialize(maplibreRef, initialDefaultLayer); + setLayers([initialDefaultLayer]); + } }); - }); - }, []); + } + }, [layers]); const updateLayer = () => { + if (!selectedLayerConfig) { + return; + } const layersClone = [...layers]; const index = layersClone.findIndex((layer) => layer.id === selectedLayerConfig.id); if (index <= -1) { @@ -86,9 +92,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => { }; } setLayers(layersClone); - setTimeout(function () { - layersFunctionMap[selectedLayerConfig.type]?.update(maplibreRef, selectedLayerConfig); - }, 50); + layersFunctionMap[selectedLayerConfig.type]?.update(maplibreRef, selectedLayerConfig); }; const removeLayer = (index: number) => { @@ -149,7 +153,12 @@ const LayerControlPanel = ({ maplibreRef }: Props) => { aria-label="layer in the map layers list" isDisabled={isDisabled} onClick={() => { - if (selectedLayerConfig.id === layer.id && !isLayerConfigVisible) { + setSelectedLayerConfig(layer); + if ( + selectedLayerConfig && + selectedLayerConfig.id === layer.id && + !isLayerConfigVisible + ) { setIsLayerConfigVisible(true); } }} @@ -158,7 +167,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => { { if (layer.visibility === LAYER_VISIBILITY.VISIBLE) { @@ -192,7 +201,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => { ); })} - {isLayerConfigVisible && ( + {isLayerConfigVisible && selectedLayerConfig && ( { ); -}; +}); export { LayerControlPanel }; diff --git a/maps_dashboards/public/components/map_container/map_container.tsx b/maps_dashboards/public/components/map_container/map_container.tsx index 8615a91d..96693920 100644 --- a/maps_dashboards/public/components/map_container/map_container.tsx +++ b/maps_dashboards/public/components/map_container/map_container.tsx @@ -9,14 +9,22 @@ import { Map as Maplibre, NavigationControl } from 'maplibre-gl'; import { LayerControlPanel } from '../layer_control_panel'; import './map_container.scss'; import { MAP_INITIAL_STATE, MAP_GLYPHS, MAP_VECTOR_TILE_DATA_SOURCE } from '../../../common'; +import { ILayerConfig } from '../../model/ILayerConfig'; -export const MapContainer = () => { +interface MapContainerProps { + mapIdFromUrl: string; + setLayers: (layers: ILayerConfig[]) => void; + layers: ILayerConfig[]; +} + +export const MapContainer = ({ mapIdFromUrl, setLayers, layers }: MapContainerProps) => { const maplibreRef = useRef(null); const mapContainer = useRef(null); const [mounted, setMounted] = useState(false); - const [zoom, setZoom] = useState(MAP_INITIAL_STATE.zoom); + const [zoom, setZoom] = useState(MAP_INITIAL_STATE.zoom); useEffect(() => { + if (!mapContainer.current) return; const mbStyle = { version: 8 as 8, sources: {}, @@ -25,33 +33,25 @@ export const MapContainer = () => { }; maplibreRef.current = new Maplibre({ - // @ts-ignore - container: mapContainer.current, + container: mapContainer.current!, center: [MAP_INITIAL_STATE.lng, MAP_INITIAL_STATE.lat], zoom, style: mbStyle, }); - maplibreRef.current.addControl(new NavigationControl({ showCompass: false }), 'top-right'); - maplibreRef.current.on('style.load', function () { - // @ts-ignore - maplibreRef.current.addSource('openmaptiles', { + const maplibreInstance = maplibreRef.current!; + maplibreInstance.addControl(new NavigationControl({ showCompass: false }), 'top-right'); + maplibreInstance.on('style.load', function () { + maplibreInstance.addSource('openmaptiles', { type: 'vector', url: MAP_VECTOR_TILE_DATA_SOURCE, }); setMounted(true); }); - }, []); - - useEffect(() => { - // @ts-ignore - maplibreRef.current.on('move', () => { - // @ts-ignore - return setZoom(maplibreRef.current.getZoom().toFixed(2)); + maplibreInstance.on('move', () => { + return setZoom(Number(maplibreInstance.getZoom().toFixed(2))); }); - }, [zoom]); - - useEffect(() => {}, []); + }, []); return (
@@ -59,7 +59,14 @@ export const MapContainer = () => {

Zoom: {zoom}

- {mounted && } + {mounted && ( + + )}
diff --git a/maps_dashboards/public/components/map_page/map_page.tsx b/maps_dashboards/public/components/map_page/map_page.tsx index 1a8fc4a7..98140c44 100644 --- a/maps_dashboards/public/components/map_page/map_page.tsx +++ b/maps_dashboards/public/components/map_page/map_page.tsx @@ -3,15 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { SimpleSavedObject } from 'opensearch-dashboards/public'; import { MapContainer } from '../map_container'; import { MapTopNavMenu } from '../map_top_nav'; +import { ILayerConfig } from '../../model/ILayerConfig'; +import { MapServices } from '../../types'; +import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public'; +import { MapSavedObjectAttributes } from '../../../common/map_saved_object_attributes'; export const MapPage = () => { + const [layers, setLayers] = useState([]); + const { id: mapIdFromUrl } = useParams<{ id: string }>(); + const [savedMapObject, setSavedMapObject] = useState | null>(); + const { services } = useOpenSearchDashboards(); + const { + savedObjects: { client: savedObjectsClient }, + } = services; + + useEffect(() => { + if (mapIdFromUrl) { + savedObjectsClient.get('map', mapIdFromUrl).then((res) => { + setSavedMapObject(res); + setLayers(JSON.parse(res.attributes.layerList as string)); + }); + } + }, [savedObjectsClient, mapIdFromUrl]); + return (
- - + +
); }; diff --git a/maps_dashboards/public/components/map_top_nav/get_top_nav_config.tsx b/maps_dashboards/public/components/map_top_nav/get_top_nav_config.tsx index 90133c09..07dcf72d 100644 --- a/maps_dashboards/public/components/map_top_nav/get_top_nav_config.tsx +++ b/maps_dashboards/public/components/map_top_nav/get_top_nav_config.tsx @@ -13,13 +13,24 @@ import { } from '../../../../../src/plugins/saved_objects/public'; import { MapServices } from '../../types'; -export const getTopNavConfig = (services: MapServices) => { - const { +interface GetTopNavConfigParams { + mapIdFromUrl: string; + layers: any; + title: string; + description: string; + setTitle: (title: string) => void; + setDescription: (description: string) => void; +} + +export const getTopNavConfig = ( + { notifications: { toasts }, i18n: { Context: I18nContext }, savedObjects: { client: savedObjectsClient }, - } = services; - + history, + }: MapServices, + { mapIdFromUrl, layers, title, description, setTitle, setDescription }: GetTopNavConfigParams +) => { const topNavConfig: TopNavMenuData[] = [ { iconType: 'save', @@ -29,19 +40,36 @@ export const getTopNavConfig = (services: MapServices) => { defaultMessage: `Save`, }), run: (_anchorElement) => { - const onModalSave = async (onSaveProps: OnSaveProps) => { - const savedMap = await savedObjectsClient.create('map', { - title: onSaveProps.newTitle, - description: onSaveProps.newDescription, - // TODO: Integrate other attributes to saved object - }); - const id = savedMap.id; + const onModalSave = async ({ newTitle, newDescription }: OnSaveProps) => { + let newlySavedMap; + if (mapIdFromUrl) { + // edit existing map + newlySavedMap = await savedObjectsClient.update('map', mapIdFromUrl, { + title: newTitle, + description: newDescription, + layerList: JSON.stringify(layers), + }); + } else { + // save new map + newlySavedMap = await savedObjectsClient.create('map', { + title: newTitle, + description: newDescription, + layerList: JSON.stringify(layers), + }); + } + const id = newlySavedMap.id; if (id) { + history.push({ + ...history.location, + pathname: `${id}`, + }); + setTitle(newTitle); + setDescription(newDescription); toasts.addSuccess({ title: i18n.translate('map.topNavMenu.saveMap.successNotificationText', { - defaultMessage: `Saved ${onSaveProps.newTitle}`, + defaultMessage: `Saved ${newTitle}`, values: { - visTitle: savedMap.attributes.title, + visTitle: newTitle, }, }), }); @@ -50,9 +78,10 @@ export const getTopNavConfig = (services: MapServices) => { }; const documentInfo = { - title: '', - description: '', + title, + description, }; + const saveModal = ( { +interface MapTopNavMenuProps { + mapIdFromUrl: string; + layers: any; + savedMapObject: SimpleSavedObject | null | undefined; +} + +export const MapTopNavMenu = ({ mapIdFromUrl, savedMapObject, layers }: MapTopNavMenuProps) => { const { services } = useOpenSearchDashboards(); const { setHeaderActionMenu, navigation: { ui: { TopNavMenu }, }, + chrome, + application: { navigateToApp }, } = services; + + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + + useEffect(() => { + if (savedMapObject) { + setTitle(savedMapObject.attributes.title); + setDescription(savedMapObject.attributes.description!); + chrome.setBreadcrumbs(getSavedMapBreadcrumbs(title, navigateToApp)); + chrome.docTitle.change(title); + } else { + chrome.setBreadcrumbs(getCreateBreadcrumbs(navigateToApp)); + chrome.docTitle.change('Create'); + } + }, [chrome, savedMapObject, mapIdFromUrl, navigateToApp, title]); + return ( ); diff --git a/maps_dashboards/public/components/maps_list/maps_list.tsx b/maps_dashboards/public/components/maps_list/maps_list.tsx index 11d0170c..f6b75099 100644 --- a/maps_dashboards/public/components/maps_list/maps_list.tsx +++ b/maps_dashboards/public/components/maps_list/maps_list.tsx @@ -4,25 +4,37 @@ */ import { i18n } from '@osd/i18n'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { I18nProvider } from '@osd/i18n/react'; -import { EuiPage, EuiPageBody, EuiPageContentBody } from '@elastic/eui'; +import { EuiPage, EuiPageBody, EuiPageContentBody, EuiLink } from '@elastic/eui'; import { TableListView, useOpenSearchDashboards, } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { MapSavedObjectAttributes } from '../../../common/map_saved_object_attributes'; import { MapServices } from '../../types'; +import { getMapsLandingBreadcrumbs } from '../../utils/breadcrumbs'; +import { PLUGIN_ID, APP_PATH } from '../../../common'; export const MapsList = () => { const { services: { notifications: { toasts }, - http: { basePath }, savedObjects: { client: savedObjectsClient }, - application: { navigateToUrl }, + application: { navigateToApp }, + chrome: { docTitle, setBreadcrumbs }, }, } = useOpenSearchDashboards(); + + useEffect(() => { + setBreadcrumbs(getMapsLandingBreadcrumbs(navigateToApp)); + docTitle.change(i18n.translate('maps.listing.pageTitle', { defaultMessage: 'Maps' })); + }, [docTitle, navigateToApp, setBreadcrumbs]); + + const navigateToSavedMapPage = (id: string) => { + navigateToApp(PLUGIN_ID, { path: `/${id}` }); + }; + const tableColumns = [ { field: 'attributes.title', @@ -30,6 +42,9 @@ export const MapsList = () => { defaultMessage: 'Title', }), sortable: true, + render: (title, record) => ( + navigateToSavedMapPage(record.id)}>{title} + ), }, { field: 'attributes.description', @@ -38,10 +53,17 @@ export const MapsList = () => { }), sortable: true, }, + { + field: 'updated_at', + name: i18n.translate('maps.listing.table.updatedTimeColumnName', { + defaultMessage: 'Last updated', + }), + sortable: true, + }, ]; - const createMap = () => { - navigateToUrl(basePath.prepend('/app/maps-dashboards/create-map')); + const navigateToCreateMapPage = () => { + navigateToApp(PLUGIN_ID, { path: APP_PATH.CREATE_MAP }); }; const fetchMaps = useCallback(async (): Promise<{ @@ -73,8 +95,6 @@ export const MapsList = () => { [savedObjectsClient, toasts] ); - const editItem = useCallback(() => {}, []); - // Render the map list DOM. return ( @@ -84,10 +104,9 @@ export const MapsList = () => { { + return fetch(MAP_VECTOR_TILE_BASIC_STYLE) + .then((res) => res.json()) + .then((json) => json.layers) + .catch((error) => { + // eslint-disable-next-line no-console + console.log('error', error); + }); +}; + +const getCurrentStyleLayers = (maplibreRef: MaplibreRef) => { + return maplibreRef.current?.getStyle().layers || []; +}; + +const handleStyleLayers = (layerConfig: ILayerConfig, maplibreRef: MaplibreRef) => { + const layers = getCurrentStyleLayers(maplibreRef); + layers.forEach((mbLayer) => { + if (mbLayer.id.includes(layerConfig.id)) { + maplibreRef.current?.setLayerZoomRange( + mbLayer.id, + layerConfig.zoomRange[0], + layerConfig.zoomRange[1] + ); + // TODO: figure out error reason + if (mbLayer.type === 'symbol') { + return; + } + maplibreRef.current?.setPaintProperty( + mbLayer.id, + `${mbLayer.type}-opacity`, + layerConfig.opacity + ); + } + }); +}; + // Functions for OpenSearch maps vector tile layer export const OSMLayerFunctions = { - initial: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { - const maplibreInstance = maplibreRef.current; - if (maplibreInstance) { - const renderStyleData = async () => { - try { - const response = await fetch(MAP_VECTOR_TILE_BASIC_STYLE); - const json = await response.json(); - const styleLayers: LayerSpecification[] = await json.layers; - styleLayers.forEach((styleLayer) => { - styleLayer.id = styleLayer.id + '_' + layerConfig.id; - // @ts-ignore - maplibreRef.current.addLayer(styleLayer); - }); - } catch (error) { - console.log('error', error); - } - }; - renderStyleData(); - - // @ts-ignore - setTimeout(function () { - const mbLayerJson: LayerSpecification[] = maplibreRef.current.getStyle().layers; - mbLayerJson.map((mbLayer) => { - if (mbLayer.id.includes(layerConfig.id)) { - maplibreInstance.setLayerZoomRange( - mbLayer.id, - layerConfig.zoomRange[0], - layerConfig.zoomRange[1] - ); - // TODO: figure out error reason - if (mbLayer.type === 'symbol') { - return; - } - maplibreInstance.setPaintProperty( - mbLayer.id, - `${mbLayer.type}-opacity`, - layerConfig.opacity - ); - } + initialize: async (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { + if (maplibreRef.current) { + fetchStyleLayers().then((styleLayers: LayerSpecification[]) => { + styleLayers.forEach((styleLayer) => { + styleLayer.id = styleLayer.id + '_' + layerConfig.id; + maplibreRef.current?.addLayer(styleLayer); + maplibreRef.current?.setLayoutProperty( + styleLayer.id, + 'visibility', + layerConfig.visibility + ); }); - }, 50); + handleStyleLayers(layerConfig, maplibreRef); + }); } }, update: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { - const maplibreInstance = maplibreRef.current; - if (maplibreInstance) { - const mbLayerJson = maplibreInstance.getStyle().layers; - mbLayerJson.forEach((mbLayer: { id: any; type: string }) => { - if (mbLayer.id.includes(layerConfig.id)) { - maplibreInstance.setLayerZoomRange( - mbLayer.id, - layerConfig.zoomRange[0], - layerConfig.zoomRange[1] - ); - if (mbLayer.type === 'symbol') { - return; - } - maplibreInstance.setPaintProperty( - mbLayer.id, - `${mbLayer.type}-opacity`, - layerConfig.opacity - ); - } - }); + if (maplibreRef.current) { + handleStyleLayers(layerConfig, maplibreRef); } }, addNewLayer: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { - const maplibreInstance = maplibreRef.current; - if (maplibreInstance) { - const renderStyleData = async () => { - try { - const response = await fetch(MAP_VECTOR_TILE_BASIC_STYLE); - const json = await response.json(); - const styleLayers: LayerSpecification[] = await json.layers; - styleLayers.forEach((styleLayer) => { - styleLayer.id = styleLayer.id + '_' + layerConfig.id; - // @ts-ignore - maplibreRef.current.addLayer(styleLayer); - }); - } catch (error) { - console.log('error', error); - } - }; - renderStyleData(); + if (maplibreRef.current) { + fetchStyleLayers().then((styleLayers: LayerSpecification[]) => { + styleLayers.forEach((styleLayer) => { + styleLayer.id = styleLayer.id + '_' + layerConfig.id; + maplibreRef.current?.addLayer(styleLayer); + }); + }); } }, remove: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { - const maplibreInstance = maplibreRef.current; - if (maplibreInstance) { - const mbLayerJson = maplibreInstance.getStyle().layers; - if (mbLayerJson) { - mbLayerJson.forEach((mbLayer: { id: any }) => { - if (mbLayer.id.includes(layerConfig.id)) { - maplibreInstance.removeLayer(mbLayer.id); - } - }); + const layers = getCurrentStyleLayers(maplibreRef); + layers.forEach((mbLayer: { id: any }) => { + if (mbLayer.id.includes(layerConfig.id)) { + maplibreRef.current?.removeLayer(mbLayer.id); } - } + }); }, hide: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => { - const maplibreInstance = maplibreRef.current; - if (maplibreInstance) { - const mbLayerJson = maplibreInstance.getStyle().layers; - if (mbLayerJson) { - mbLayerJson.forEach((mbLayer: { id: any }) => { - if (mbLayer.id.includes(layerConfig.id)) { - maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', layerConfig.visibility); - } - }); + const layers = getCurrentStyleLayers(maplibreRef); + layers.forEach((mbLayer: { id: any }) => { + if (mbLayer.id.includes(layerConfig.id)) { + maplibreRef.current?.setLayoutProperty(mbLayer.id, 'visibility', layerConfig.visibility); } - } + }); }, }; diff --git a/maps_dashboards/public/model/layersFunctions.ts b/maps_dashboards/public/model/layersFunctions.ts index eee50a51..6c85d526 100644 --- a/maps_dashboards/public/model/layersFunctions.ts +++ b/maps_dashboards/public/model/layersFunctions.ts @@ -6,6 +6,6 @@ import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../common'; import { OSMLayerFunctions } from './OSMLayerFunctions'; -export const layersFunctionMap = { +export const layersFunctionMap: { [key: string]: any } = { [DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP]: OSMLayerFunctions, }; diff --git a/maps_dashboards/public/plugin.ts b/maps_dashboards/public/plugin.ts index 334452d0..84e9f0f6 100644 --- a/maps_dashboards/public/plugin.ts +++ b/maps_dashboards/public/plugin.ts @@ -40,6 +40,7 @@ export class MapsDashboardsPlugin element: params.element, navigation, toastNotifications: coreStart.notifications.toasts, + history: params.history, }; // Render the application return renderApp(params, services); diff --git a/maps_dashboards/public/types.ts b/maps_dashboards/public/types.ts index df494667..c4bfe9da 100644 --- a/maps_dashboards/public/types.ts +++ b/maps_dashboards/public/types.ts @@ -28,4 +28,5 @@ export interface MapServices extends CoreStart { element: AppMountParameters['element']; navigation: NavigationPublicPluginStart; toastNotifications: ToastsStart; + history: AppMountParameters['history']; } diff --git a/maps_dashboards/public/utils/breadcrumbs.ts b/maps_dashboards/public/utils/breadcrumbs.ts new file mode 100644 index 00000000..3ac6bdb0 --- /dev/null +++ b/maps_dashboards/public/utils/breadcrumbs.ts @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { PLUGIN_ID } from '../../common'; + +export function getMapsLandingBreadcrumbs(navigateToApp) { + return [ + { + text: i18n.translate('maps.listing.breadcrumb', { + defaultMessage: 'Maps', + }), + onClick: () => navigateToApp(PLUGIN_ID), + }, + ]; +} + +export function getCreateBreadcrumbs(navigateToApp) { + return [ + ...getMapsLandingBreadcrumbs(navigateToApp), + { + text: i18n.translate('maps.create.breadcrumb', { + defaultMessage: 'Create', + }), + }, + ]; +} + +export function getSavedMapBreadcrumbs(text: string, navigateToApp) { + return [ + ...getMapsLandingBreadcrumbs(navigateToApp), + { + text, + }, + ]; +}