From 2c1f3727a703634cf62c63cfc4a3d4bb30fe2d9c Mon Sep 17 00:00:00 2001 From: Aijana Schumann Date: Fri, 7 May 2021 13:56:52 +0200 Subject: Add customization of networkmap Add customization capabilities to the networkmap Issue-ID: CCSDK-2938 Signed-off-by: Aijana Schumann Change-Id: Ibd3fe258d02939ea69b21de25569f20d9087f693 --- sdnr/wt/odlux/apps/networkMapApp/icons/README.md | 2 +- .../apps/networkMapApp/icons/apartment.png.d.ts | 18 + .../odlux/apps/networkMapApp/icons/customize.png | Bin 0 -> 399 bytes .../apps/networkMapApp/icons/customize.png.d.ts | 20 + .../apps/networkMapApp/icons/datacenter.png.d.ts | 18 + .../networkMapApp/icons/datacenterred.png.d.ts | 18 + .../apps/networkMapApp/icons/factory.png.d.ts | 18 + .../apps/networkMapApp/icons/factoryred.png.d.ts | 18 + .../odlux/apps/networkMapApp/icons/lamp.png.d.ts | 18 + .../apps/networkMapApp/icons/lampred.png.d.ts | 18 + sdnr/wt/odlux/apps/networkMapApp/src/App.tsx | 2 +- .../src/actions/connectivityAction.ts | 6 + .../networkMapApp/src/actions/settingsAction.ts | 85 +++ .../src/components/connectionInfo.tsx | 59 -- .../src/components/customize/customizationView.tsx | 291 +++++++++ .../src/components/customize/themeElement.tsx | 47 ++ .../networkMapApp/src/components/iconSwitch.tsx | 53 -- .../apps/networkMapApp/src/components/map.tsx | 593 ----------------- .../src/components/map/connectionInfo.tsx | 59 ++ .../src/components/map/iconSwitch.tsx | 53 ++ .../apps/networkMapApp/src/components/map/map.tsx | 677 ++++++++++++++++++++ .../networkMapApp/src/components/map/mapPopup.tsx | 94 +++ .../networkMapApp/src/components/map/searchBar.tsx | 160 +++++ .../src/components/map/statistics.tsx | 57 ++ .../apps/networkMapApp/src/components/mapPopup.tsx | 94 --- .../networkMapApp/src/components/searchBar.tsx | 160 ----- .../networkMapApp/src/components/statistics.tsx | 57 -- .../src/handlers/connectivityReducer.ts | 9 +- .../apps/networkMapApp/src/handlers/rootReducer.ts | 7 +- .../networkMapApp/src/handlers/settingsReducer.ts | 61 ++ .../odlux/apps/networkMapApp/src/model/settings.ts | 34 + .../apps/networkMapApp/src/pluginTransport.tsx | 44 +- .../networkMapApp/src/services/settingsService.ts | 43 ++ .../apps/networkMapApp/src/utils/mapLayers.ts | 712 +++++++-------------- sdnr/wt/odlux/apps/networkMapApp/webpack.config.js | 10 +- 35 files changed, 2108 insertions(+), 1507 deletions(-) create mode 100644 sdnr/wt/odlux/apps/networkMapApp/icons/customize.png create mode 100644 sdnr/wt/odlux/apps/networkMapApp/icons/customize.png.d.ts create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/actions/settingsAction.ts delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/iconSwitch.tsx delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/map.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/searchBar.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/map/statistics.tsx delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/searchBar.tsx delete mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/components/statistics.tsx create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/handlers/settingsReducer.ts create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/model/settings.ts create mode 100644 sdnr/wt/odlux/apps/networkMapApp/src/services/settingsService.ts (limited to 'sdnr/wt/odlux/apps') diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/README.md b/sdnr/wt/odlux/apps/networkMapApp/icons/README.md index acfbcf823..85c75c4e9 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/README.md +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/README.md @@ -19,7 +19,7 @@ Copyright of icons is as followes: */ --> -datacenter.png, lamp.png, factory.png, datacenterred.png, lampred.png, factoryred.png, +datacenter.png, lamp.png and customize.png with all variations (different colors) Taken from MS Word diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts index bf398f5a4..9f956f813 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const apartment: string; export default apartment; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png b/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png new file mode 100644 index 000000000..91dbf6824 Binary files /dev/null and b/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png differ diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png.d.ts new file mode 100644 index 000000000..16327f5af --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/customize.png.d.ts @@ -0,0 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +declare const customize: string; +export default customize; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts index a58a9f5af..dc7019766 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const datacenter: string; export default datacenter; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts index 33f3061e2..74836be8d 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const datacenterred: string; export default datacenterred; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/factory.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/factory.png.d.ts index b5c4f19d9..e341c5f95 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/factory.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/factory.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const factory: string; export default factory; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/factoryred.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/factoryred.png.d.ts index 1fac0a943..317ff7e85 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/factoryred.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/factoryred.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const factoryRed: string; export default factoryRed; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts index 9634b1275..eeedc7fcd 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const lamp: string; export default lamp; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/lampred.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/lampred.png.d.ts index 12a8f91cb..b053df0b8 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/icons/lampred.png.d.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/icons/lampred.png.d.ts @@ -1,2 +1,20 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + declare const lampred: string; export default lampred; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx index 6caab5147..5840d1842 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx +++ b/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx @@ -17,7 +17,7 @@ */ import * as React from 'react'; -import Map from './components/map' +import Map from './components/map/map' import Details from './components/details/details' function MainView() { diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts index 8fcdc4c3b..63f52c8f9 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts @@ -33,6 +33,12 @@ export class IsTileServerReachableAction extends Action{ } } +export class IsBusycheckingConnectivityAction extends Action{ + constructor(public isBusy: boolean){ + super(); + } +} + export const verifyResponse = (response: Response) =>{ if(response.ok){ diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/settingsAction.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/settingsAction.ts new file mode 100644 index 000000000..5b8982368 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/settingsAction.ts @@ -0,0 +1,85 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { NetworkMapSettings, NetworkMapThemes, NetworkSettings } from '../model/settings'; +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { settingsService } from '../services/settingsService'; + +export class SetMapSettingsAction extends Action{ + + constructor(public settings:NetworkMapSettings) { + super(); + } +} + +export class SetThemeSettingsAction extends Action{ + + constructor(public settings:NetworkMapThemes) { + super(); + } +} + +export class SetSettingsAction extends Action{ + + constructor(public settings:NetworkSettings) { + super(); + } +} + +export class SetBusyLoadingAction extends Action{ + + constructor(public busy: boolean) { + super(); + + } +} + + +export const getSettings = () => async (dispatcher: Dispatch) => { + dispatcher(new SetBusyLoadingAction(true)); + console.log("getting settings in action..") + settingsService.getMapSettings().then(result =>{ + if(result){ + if(result.networkMap && result.networkMapThemes){ + const mapSettings : NetworkSettings = { networkMap: result.networkMap, networkMapThemes: result.networkMapThemes} + dispatcher(new SetSettingsAction(mapSettings)); + }else if(result.networkMap){ + dispatcher(new SetMapSettingsAction(result)); + }else if(result.networkMapThemes){ + dispatcher(new SetThemeSettingsAction(result)); + } + } + else{ + console.warn("settings couldn't be loaded."); + } + dispatcher(new SetBusyLoadingAction(false)); + }); +} + +export const updateSettings = (mapSettings: NetworkMapSettings) => async (dispatcher: Dispatch) =>{ + + const result = await settingsService.updateMapSettings(mapSettings); + console.log("update settings"); + dispatcher(new SetMapSettingsAction(mapSettings)); + + console.log(result); + if(result){ + } + +} diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx deleted file mode 100644 index d1e2d978f..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react' - -import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore"; -import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect"; -import { Paper, Typography } from "@material-ui/core"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; - - -type props = Connect; - -const ConnectionInfo: React.FunctionComponent = (props) => { - - return ((props.isTopoServerReachable === false || props.isTileServerReachable === false )? -
-
Connection Error
- {props.isTileServerReachable === false && Tile data can't be loaded.} - {props.isTopoServerReachable === false && Network data can't be loaded.} -
-
: null -) - -} - -const mapStateToProps = (state: IApplicationStoreState) => ({ - isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, - isTileServerReachable: state.network.connectivity.isTileServerAvailable - -}); - - - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ - - //zoomToSearchResult: (lat: number, lon: number) => dispatcher.dispatch(new ZoomToSearchResultAction(lat, lon)) - -});; - - -export default connect(mapStateToProps,mapDispatchToProps)(ConnectionInfo) - diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx new file mode 100644 index 000000000..82e7b795b --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx @@ -0,0 +1,291 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { Button, Grid, InputLabel, makeStyles, MenuItem, Select, Slider, TextField, Typography } from '@material-ui/core'; +import { NetworkMapSettings, ThemeElement } from '../../model/settings'; +import * as React from 'react' +import connect, { Connect, IDispatcher } from '../../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import { updateSettings } from '../../actions/settingsAction'; +import ThemeEntry from './themeElement' +import * as mapboxgl from 'mapbox-gl'; +import { OSM_STYLE } from '../../config'; +import mapLayerService from '../../utils/mapLayers'; +import { requestRest } from '../../../../../framework/src/services/restService'; +import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions'; + +type props = Connect; +let map: mapboxgl.Map; +let myMapRef = React.createRef(); +const default_boundingbox = "12.882544785787754,52.21421979821472,13.775455214211949,52.80406241672602"; + + +const mapProps = (state: IApplicationStoreState) => ({ + settings: state.network.settings, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + updateSettings: (mapSettings: NetworkMapSettings) => dispatcher.dispatch(updateSettings(mapSettings)), + navigateToApplication: (applicationName: string) => dispatcher.dispatch(new NavigateToApplication(applicationName)), + + +}); + +const styles = makeStyles({ + sectionMargin: { + marginTop: "30px", + marginBottom: "15px" + }, + elementMargin: { + + marginLeft: "10px" + } +}); + +const CustomizationView: React.FunctionComponent = (props) => { + + const [opacity, setOpacity] = React.useState(Number(props.settings.mapSettings?.networkMap.tileOpacity) || 100); + const [theme, setTheme] = React.useState(props.settings.mapSettings?.networkMap.styling.theme || ''); + const [latitude, setLatitude] = React.useState(Number(props.settings.mapSettings?.networkMap.startupPosition.latitude)|| 52.5); + const [longitude, setLongitude] = React.useState(Number(props.settings.mapSettings?.networkMap.startupPosition.longitude)|| 13.35); + const [zoom, setZoom] = React.useState(Number(props.settings.mapSettings?.networkMap.startupPosition.zoom) || 10); + + + //used to make opacity available within the map event-listeners + //(hook state values are snapshotted at initalization and not updated afterwards, thus use a ref here) + const myOpacityRef = React.useRef(opacity); + const setOpacityState = (data:any) => { + myOpacityRef.current = data; + setOpacity(data); + }; + + const classes = styles(); + const currentTheme = props.settings.themes.networkMapThemes.themes.find(el => el.key === theme); + + + React.useEffect(() => { + mapLayerService.settings = props.settings.themes; + + map = new mapboxgl.Map({ + container: myMapRef.current!, + style: OSM_STYLE as any, + center: [longitude, latitude], + zoom: zoom, + accessToken: '' + }); + + map.on('load', (ev) => { + + mapLayerService.addBaseSources(map, null, null); + if(props.settings.mapSettings?.networkMap.styling.theme !== theme){ + mapLayerService.addBaseLayers(map, currentTheme); + + }else{ + mapLayerService.addBaseLayers(map); + } + + mapLayerService.changeMapOpacity(map, myOpacityRef.current); + + getData(); + }); + + map.on('moveend', () => { + const center = map.getCenter(); + setZoom(Number(map.getZoom().toFixed(4))); + setLatitude(Number(center.lat.toFixed(4))); + setLongitude(Number(center.lng.toFixed(4))); + }); + + }, []); + + React.useEffect(() => { + recenterMap(); + }, [latitude, longitude, zoom]); + + const setState = () => { + if (props.settings.mapSettings?.networkMap.styling) { + setTheme(props.settings.mapSettings.networkMap.styling.theme); + mapLayerService.changeTheme(map, props.settings.mapSettings.networkMap.styling.theme); + } + + const propOpacity = props.settings.mapSettings?.networkMap.tileOpacity; + if (propOpacity) { + setOpacityState(propOpacity); + } + } + + React.useEffect(() => { + setState(); + }, [props.settings.mapSettings]); + + const onOpacityChange = (event: React.ChangeEvent, newValue: number) => { + setOpacity(newValue); + mapLayerService.changeMapOpacity(map, newValue); + + }; + + const onChangeTheme = (e: any) => { + + const newTheme = e.target.value; + setTheme(newTheme); + mapLayerService.changeTheme(map, newTheme); + } + + const onCancel = (e: React.MouseEvent) => { + e.preventDefault(); + props.navigateToApplication("network"); + } + + const onSaveSettings = async (e: React.MouseEvent) => { + e.preventDefault(); + + const updatedSettings: NetworkMapSettings = { + networkMap: { + tileOpacity: opacity.toString(), + styling: { theme: theme }, + startupPosition: { + latitude: latitude.toString(), + longitude: longitude.toString(), + zoom: zoom.toString() + } + } + }; + + console.log(updatedSettings); + + await props.updateSettings(updatedSettings) + props.navigateToApplication("network"); + + } + + const recenterMap = () => { + + if (!isNaN(latitude) && !isNaN(longitude) && !isNaN(zoom)) + + map.flyTo({ + center: [ + longitude, + latitude + ], zoom: zoom, + essential: false + }); + } + + + + const getData = () => { + + //get data of boundingbox from networkmap + + const links = requestRest("/topology/network/links/geojson/" + default_boundingbox); + const sites = requestRest("/topology/network/sites/geojson/" + default_boundingbox); + + Promise.all([links, sites]).then(results => { + if (map.getSource('lines')) { + (map.getSource('lines') as mapboxgl.GeoJSONSource).setData(results[0]); + } + + if (map.getSource('points')) { + (map.getSource('points') as mapboxgl.GeoJSONSource).setData(results[1]); + } + + if (map.getSource('selectedPoints')) { + (map.getSource('selectedPoints') as mapboxgl.GeoJSONSource).setData(results[1].features[0]); + } + }); + } + + /** + * Style property names to readable text + * @param text propretyName + * @returns readable text + */ + const styleText = (text: string) => { + const textParts = text.split(/(?=[A-Z])/); //split on uppercase character + const newText = textParts.join(" "); + return newText.charAt(0).toUpperCase() + newText.slice(1); + } + + + return (<> +

Settings

+
+
+ Startup Position +
+ setLatitude(e.target.value as any)} style={{ marginLeft: 10 }} label="Latitude" /> + setLongitude(e.target.value as any)} style={{ marginLeft: 5 }} label="Longitude" /> + setZoom(e.target.value as any)} style={{ marginLeft: 5 }} label="Zoom" /> +
+ + + Tile Opacity + + + 0 + + + + 100 + + + + Style of properties + + Theme + + + { + currentTheme &&
+ { //skip the 'key' (theme name) entry + Object.keys(currentTheme).slice(1).map(el => ) + } +
+ } + + +
+ + + +
+
+
+ +
+
+ + ) + +} + +export default connect(mapProps, mapDispatch)(CustomizationView); + + diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx new file mode 100644 index 000000000..c991aaf63 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { Typography } from '@material-ui/core'; +import * as React from 'react' + + +type props={ + color: string, + text: string +}; + +const ThemeEntry = (props: props) =>{ + + var circleStyle = { + padding:10, + margin:20, + backgroundColor: props.color, + borderRadius: "50%", + width:10, + height:10, + left:0, + top:0}; + + return
+
+ {props.text} +
+ +} + +export default ThemeEntry; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/iconSwitch.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/iconSwitch.tsx deleted file mode 100644 index 8df1385d0..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/iconSwitch.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react'; -import { FormControlLabel, Switch, Paper } from "@material-ui/core"; -import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; -import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; -import { SetIconSwitchAction } from '../actions/mapActions'; - -type props = Connect & {visible: boolean} - -const IconSwitch: React.FunctionComponent = (props) =>{ - - const toggleChecked = () => { - props.toogle(!props.areIconsEnabled) - }; - - return ( - props.visible ? - } - label="Show icons" - labelPlacement="end" - />: null) -} - -const mapStateToProps = (state: IApplicationStoreState) => ({ - areIconsEnabled: state.network.map.allowIconSwitch -}); - - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ - toogle : (enable:boolean) => dispatcher.dispatch(new SetIconSwitchAction(enable)) - -});; - -export default (connect(mapStateToProps,mapDispatchToProps)(IconSwitch)) diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx deleted file mode 100644 index 855e5cedf..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx +++ /dev/null @@ -1,593 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react' -import * as mapboxgl from 'mapbox-gl'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; - - -import { Site } from '../model/site'; -import { SelectSiteAction, ClearHistoryAction, SelectLinkAction } from '../actions/detailsAction'; -import { OSM_STYLE, URL_API, URL_BASEPATH, URL_TILE_API } from '../config'; -import { link } from '../model/link'; -import MapPopup from './mapPopup'; -import { SetPopupPositionAction, SelectMultipleLinksAction, SelectMultipleSitesAction } from '../actions/popupActions'; -import { Feature } from '../model/Feature'; -import { HighlightLinkAction, HighlightSiteAction, SetCoordinatesAction, SetStatistics } from '../actions/mapActions'; -import { addDistance, getUniqueFeatures, increaseBoundingBox } from '../utils/mapUtils'; -import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; -import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect'; -import SearchBar from './searchBar'; -import { verifyResponse, IsTileServerReachableAction, handleConnectionError, setTileServerReachableAction } from '../actions/connectivityAction'; -import ConnectionInfo from './connectionInfo' -import { showIconLayers, addBaseLayers, addBaseSources, addIconLayers } from '../utils/mapLayers'; -import Statistics from './statistics'; -import IconSwitch from './iconSwitch'; -import { addImages } from '../services/mapImagesService'; -import { PopupElement } from '../model/popupElements'; - -type coordinates = { lat: number, lon: number, zoom: number } - -let alarmElements: Feature[] = []; -let map: mapboxgl.Map; -let isLoadingInProgress = false; -let notLoadedBoundingBoxes: mapboxgl.LngLatBounds[] = []; - -let lastBoundingBox: mapboxgl.LngLatBounds | null = null; -let myRef = React.createRef(); - - -class Map extends React.Component { - - constructor(props: mapProps) { - super(props); - //any state stuff - this.state = { isPopupOpen: false } - - } - - componentDidMount() { - - // resize the map, if menu gets collapsed - window.addEventListener("menu-resized", this.handleResize); - - // try if connection to tile + topologyserver is available - - fetch(URL_TILE_API + '/10/0/0.png') - .then(res => { - if (res.ok) { - this.setupMap(); - this.props.setTileServerLoaded(true); - } else { - this.props.setTileServerLoaded(false); - console.error("tileserver " + URL_TILE_API + " can't be reached."); - } - }) - .catch(err => { - this.props.setTileServerLoaded(false); - console.error("tileserver " + URL_TILE_API + " can't be reached."); - }); - - fetch(URL_API + "/info/count/all") - .then(result => verifyResponse(result)) - .catch(error => this.props.handleConnectionError(error)); - } - - setupMap = () => { - - let lat = this.props.lat; - let lon = this.props.lon; - let zoom = this.props.zoom; - - const coordinates = this.extractCoordinatesFromUrl(); - // override lat/lon/zoom with coordinates from url, if available - if (this.areCoordinatesValid(coordinates)) { - lat = coordinates.lat; - lon = coordinates.lon; - zoom = !Number.isNaN(coordinates.zoom) ? coordinates.zoom : zoom; - } - - map = new mapboxgl.Map({ - container: myRef.current!, - style: OSM_STYLE as any, - center: [lon, lat], - zoom: zoom, - accessToken: '' - }); - - map.on('load', (ev) => { - - map.setMaxZoom(18); - const bbox = map.getBounds(); - this.props.updateMapPosition(bbox.getCenter().lat, bbox.getCenter().lng, map.getZoom()) - - addBaseSources(map, this.props.selectedSite, this.props.selectedLink); - - addImages(map, (result: boolean)=>{ - if(map.getZoom()>11) - { - addIconLayers(map, this.props.selectedSite?.properties.id) - }else{ - addBaseLayers(map, this.props.selectedSite, this.props.selectedLink); - } - }); - - const boundingBox = increaseBoundingBox(map); - - fetch(`${URL_API}/links/geojson/${boundingBox.west},${boundingBox.south},${boundingBox.east},${boundingBox.north}`) - .then(result => verifyResponse(result)) - .then(result => result.json()) - .then(features => { - if (map.getSource('lines')) { - (map.getSource('lines') as mapboxgl.GeoJSONSource).setData(features); - } - }) - .catch(error => this.props.handleConnectionError(error)); - - - fetch(`${URL_API}/sites/geojson/${boundingBox.west},${boundingBox.south},${boundingBox.east},${boundingBox.north}`) - .then(result => verifyResponse(result)) - .then(result => result.json()) - .then(features => { - if (map.getSource('points')) { - (map.getSource('points') as mapboxgl.GeoJSONSource).setData(features); - } - }) - .catch(error => this.props.handleConnectionError(error)); - }); - - map.on('click', (e: any) => { - - if (map.getLayer('points')) { // data is shown as points - - var clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], - [e.point.x + 5, e.point.y + 5]], { - layers: ['microwave-lines', 'fibre-lines'] - }), "id"); - - const clickedPoints = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['points'] }), "id"); - const alarmedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['alarmedPoints'] }), "id"); - - if (clickedPoints.length != 0) { - - - if (alarmedSites.length > 0) { - alarmedSites.forEach(alarm => { - const index = clickedPoints.findIndex(item => item.properties!.id === alarm.properties!.id); - - if (index !== -1) { - clickedPoints[index].properties!.alarmed = true; - clickedPoints[index].properties!.type = "alarmed"; - } - }); - } - - this.showSitePopup(clickedPoints, e.point.x, e.point.y); - } else if (clickedLines.length != 0) { - this.showLinkPopup(clickedLines, e.point.x, e.point.y); - } - - - } else { // data is shown as icons - - const clickedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-lamps', 'point-building', 'point-data-center', 'point-factory', 'point-remaining'] }), "id"); - const clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], - [e.point.x + 5, e.point.y + 5]], { - layers: ['microwave-lines', 'fibre-lines'] - }), "id"); - - if (clickedSites.length > 0) - this.showSitePopup(clickedSites, e.point.x, e.point.y); - else if (clickedLines.length != 0) { - this.showLinkPopup(clickedLines, e.point.x, e.point.y); - } - } - - }); - - map.on('moveend', () => { - - const mapZoom = Number(map.getZoom().toFixed(2)); - const lat = Number(map.getCenter().lat.toFixed(4)); - const lon = Number(map.getCenter().lng.toFixed(4)); - - - if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== mapZoom) { - this.props.updateMapPosition(lat, lon, mapZoom) - } - - // update the url to current lat,lon,zoom values - - const currentUrl = window.location.href; - const parts = currentUrl.split(URL_BASEPATH); - if(parts.length>0){ - - const detailsPath = parts[1].split("/details/"); - - if (detailsPath[1] !== undefined && detailsPath[1].length > 0) { - this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}/details/${detailsPath[1]}`) - } - else { - this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}`) - } - } - - - //switch icon layers if applicable - showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id); - - //update statistics - const boundingBox = map.getBounds(); - - fetch(`${URL_API}/info/count/${boundingBox.getWest()},${boundingBox.getSouth()},${boundingBox.getEast()},${boundingBox.getNorth()}`) - .then(result => verifyResponse(result)) - .then(res => res.json()) - .then(result => { - if (result.links !== this.props.linkCount || result.sites !== this.props.siteCount) { - this.props.setStatistics(result.links, result.sites); - } - }) - .catch(error => this.props.handleConnectionError(error));; - }) - - map.on('move', () => { - const mapZoom = map.getZoom(); - - const boundingBox = map.getBounds(); - - this.loadNetworkData(boundingBox); - if (mapZoom > 9) { - - if (map.getLayer('points')) { - map.setLayoutProperty('selectedPoints', 'visibility', 'visible'); - map.setPaintProperty('points', 'circle-radius', 7); - } - } else { - - // reduce size of points / lines if zoomed out - map.setPaintProperty('points', 'circle-radius', 2); - map.setLayoutProperty('selectedPoints', 'visibility', 'none'); - - if (mapZoom <= 4) { - map.setPaintProperty('fibre-lines', 'line-width', 1); - map.setPaintProperty('microwave-lines', 'line-width', 1); - - } else { - map.setPaintProperty('fibre-lines', 'line-width', 2); - map.setPaintProperty('microwave-lines', 'line-width', 2); - } - } - }); - } - - componentDidUpdate(prevProps: mapProps, prevState: {}) { - - if (map !== undefined) { - if (prevProps.selectedSite?.properties.id !== this.props.selectedSite?.properties.id) { - - if (this.props.selectedSite != null) { - if (map.getSource("selectedLine") !== undefined) { - (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); - (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedSite] }); - } - - - if (map.getLayer('point-lamps') !== undefined) { - - map.setFilter('point-lamps', ['==', 'type', 'street lamp']); - map.setFilter('point-data-center', ['==', 'type', 'data center']); - map.setFilter('point-building', ['==', 'type', 'high rise building']); - map.setFilter('point-factory', ['==', 'type', 'factory']); - - if (this.props.selectedSite?.properties.type !== undefined) { - switch (this.props.selectedSite?.properties.type) { - case 'street lamp': - map.setFilter('point-lamps', ["all", ['==', 'type', 'street lamp'], ['!=', 'id', this.props.selectedSite.properties.id]]); - break; - case 'data center': - map.setFilter('point-data-center', ["all", ['==', 'type', 'data center'], ['!=', 'id', this.props.selectedSite.properties.id]]); - break; - case 'high rise building': - map.setFilter('point-building', ["all", ['==', 'type', 'high rise building'], ['!=', 'id', this.props.selectedSite.properties.id]]) - break; - case 'factory': - map.setFilter('point-factory', ["all", ['==', 'type', 'factory'], ['!=', 'id', this.props.selectedSite.properties.id]]); - break; - } - } - } - - - } - else - { - if (map.getSource("selectedPoints") !== undefined) - (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); - - } - } - - if (prevProps.selectedLink !== this.props.selectedLink) { - if (this.props.selectedLink != null) { - - if (map.getLayer('point-lamps') !== undefined) { - map.setFilter('point-lamps', ['==', 'type', 'street lamp']); - map.setFilter('point-data-center', ['==', 'type', 'data center']); - map.setFilter('point-building', ['==', 'type', 'high rise building']); - map.setFilter('point-factory', ['==', 'type', 'factory']); - } - - if (map.getSource("selectedLine") !== undefined) { - (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); - (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedLink] }); - } - } - else - { - if (map.getSource("selectedLine") !== undefined) - (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); - } - } - - if (prevProps.location.pathname !== this.props.location.pathname) { - if (map) { - const coordinates = this.extractCoordinatesFromUrl(); - this.moveMapToCoordinates(coordinates); - } - } - - if (prevProps.alarmlement !== this.props.alarmlement) { - if (this.props.alarmlement !== null && !alarmElements.includes(this.props.alarmlement)) { - if (map.getSource("alarmedPoints")) - (map.getSource("alarmedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: alarmElements }); - alarmElements.push(this.props.alarmlement) - } - } - - if (prevProps.showIcons !== this.props.showIcons) { - if (map && map.getZoom() > 11) { - showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id); - } - } - - if (prevProps.zoomToElement !== this.props.zoomToElement) { - if (this.props.zoomToElement !== null) { - const currentZoom = map?.getZoom(); - - map.flyTo({ - center: [ - this.props.zoomToElement.lon, - this.props.zoomToElement.lat - ], zoom: currentZoom < 10 ? 10 : currentZoom, - essential: true - }); - } - } - } - } - - componentWillUnmount(){ - window.removeEventListener("menu-resized", this.handleResize); - lastBoundingBox=null; - } - - handleResize = () => { - if (map) { - // wait a moment until resizing actually happened - window.setTimeout(() => map.resize(), 500); - } - } - - extractCoordinatesFromUrl = (): coordinates => { - const currentUrl = window.location.href; - const mainPathParts = currentUrl.split(URL_BASEPATH); - const coordinatePathPart = mainPathParts[1].split("/details/"); // split by details if present - const allCoordinates = coordinatePathPart[0].replace("/", ""); - const coordinates = allCoordinates.split(","); - return { lat: Number(coordinates[0]), lon: Number(coordinates[1]), zoom: Number(coordinates[2]) } - } - - areCoordinatesValid = (coordinates: coordinates) => { - - if ((!Number.isNaN(coordinates.lat)) && (!Number.isNaN(coordinates.lon))) { - return true; - } else { - return false; - } - } - - moveMapToCoordinates = (coordinates: coordinates) => { - - if (this.areCoordinatesValid(coordinates)) { - let zoom = -1; - - if (!Number.isNaN(coordinates.zoom)) { - zoom = coordinates.zoom; - } - - map.flyTo({ - center: [ - coordinates.lon, - coordinates.lat - ], zoom: zoom !== -1 ? zoom : this.props.zoom, - essential: true - }) - } - } - - loadNetworkData = async (bbox: mapboxgl.LngLatBounds) => { - if (!isLoadingInProgress) { // only load data if loading not in progress - isLoadingInProgress = true; - - if (lastBoundingBox == null) { - lastBoundingBox = bbox; - await this.draw('lines', `${URL_API}/links/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); - await this.draw('points', `${URL_API}/sites/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); - } else { - - // new bbox is bigger than old one - if (bbox.contains(lastBoundingBox.getNorthEast()) && bbox.contains(lastBoundingBox.getSouthWest()) && lastBoundingBox !== bbox) { //if new bb is bigger than old one - - lastBoundingBox = bbox; - - //calculate new boundingBox - const increasedBoundingBox = increaseBoundingBox(map); - - await this.draw('lines', `${URL_API}/links/geojson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`); - await this.draw('points', `${URL_API}/sites/geojson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`); - - } else if (lastBoundingBox.contains(bbox.getNorthEast()) && lastBoundingBox.contains(bbox.getSouthWest())) { // last one contains new one - // bbox is contained in last one, do nothing - isLoadingInProgress = false; - - } else { // bbox is not fully contained in old one, extend - - lastBoundingBox.extend(bbox); - - await this.draw('lines', `${URL_API}/links/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); - await this.draw('points', `${URL_API}/sites/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); - } - - } - - - if (notLoadedBoundingBoxes.length > 0) { // load last not loaded boundingbox - this.loadNetworkData(notLoadedBoundingBoxes.pop()!) - notLoadedBoundingBoxes = []; - } - - } else { - notLoadedBoundingBoxes.push(bbox); - } - } - - showSitePopup = (sites: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => { - if (sites.length > 1) { - const elements: PopupElement[] = sites.map(feature => {return {name: feature.properties!.name, id: feature.properties!.id}}); - - this.props.setPopupPosition(top, left); - this.props.selectMultipleSites(elements); //name, id object container - this.setState({ isPopupOpen: true }); - - } else { - const id = sites[0].properties!.id; - - fetch(`${URL_API}/sites/${id}`) - .then(result => verifyResponse(result)) - .then(res => res.json() as Promise) - .then(result => { - this.props.selectSite(result); - this.props.highlightSite(result); - this.props.clearDetailsHistory(); - }) - .catch(error => this.props.handleConnectionError(error));; - } - } - - showLinkPopup = (links: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => { - - if (links.length > 1) { - - const elements: PopupElement[] = links.map(feature => {return {name: feature.properties!.name, id: feature.properties!.id}}); - - this.props.setPopupPosition(top, left); - this.props.selectMultipleLinks(elements); - this.setState({ isPopupOpen: true }); - - } else { - var id = links[0].properties!.id; - - fetch(`${URL_API}/links/${id}`) - .then(result => verifyResponse(result)) - .then(res => res.json() as Promise) - .then(result => { - this.props.selectLink(result); - this.props.highlightLink(result); - - this.props.clearDetailsHistory(); - }) - .catch(error => this.props.handleConnectionError(error));; - } - } - - draw = async (layer: string, url: string) => { - - fetch(url) - .then(result => verifyResponse(result)) - .then(res => res.json()) - .then(result => { - isLoadingInProgress = false; - if (map.getSource(layer)) { - (map.getSource(layer) as mapboxgl.GeoJSONSource).setData(result); - } - }) - .catch(error => this.props.handleConnectionError(error));; - } - - render() { - - return <> - -
- { - this.state.isPopupOpen && - { this.setState({ isPopupOpen: false }); }} /> - } - - - 11} /> - -
- - } - -} - -type mapProps = RouteComponentProps & Connect; - -const mapStateToProps = (state: IApplicationStoreState) => ({ - selectedLink: state.network.map.selectedLink, - selectedSite: state.network.map.selectedSite, - zoomToElement: state.network.map.zoomToElement, - alarmlement: state.network.map.alarmlement, - lat: state.network.map.lat, - lon: state.network.map.lon, - zoom: state.network.map.zoom, - linkCount: state.network.map.statistics.links, - siteCount: state.network.map.statistics.sites, - isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, - isTileServerReachable: state.network.connectivity.isTileServerAvailable, - showIcons: state.network.map.allowIconSwitch -}); - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ - selectSite: (site: Site) => dispatcher.dispatch(new SelectSiteAction(site)), - selectLink: (link: link) => dispatcher.dispatch(new SelectLinkAction(link)), - clearDetailsHistory: () => dispatcher.dispatch(new ClearHistoryAction()), - selectMultipleLinks: (ids: PopupElement[]) => dispatcher.dispatch(new SelectMultipleLinksAction(ids)), - selectMultipleSites: (ids: PopupElement[]) => dispatcher.dispatch(new SelectMultipleSitesAction(ids)), - setPopupPosition: (x: number, y: number) => dispatcher.dispatch(new SetPopupPositionAction(x, y)), - highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)), - highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), - updateMapPosition: (lat: number, lon: number, zoom: number) => dispatcher.dispatch(new SetCoordinatesAction(lat, lon, zoom)), - setStatistics: (linkCount: string, siteCount: string) => dispatcher.dispatch(new SetStatistics(siteCount, linkCount)), - setTileServerLoaded: (reachable: boolean) => dispatcher.dispatch(setTileServerReachableAction(reachable)), - handleConnectionError: (error: Error) => dispatcher.dispatch(handleConnectionError(error)) -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Map)); \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx new file mode 100644 index 000000000..c1fdccfb8 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react' + +import { IApplicationStoreState } from "../../../../../framework/src/store/applicationStore"; +import connect, { IDispatcher, Connect } from "../../../../../framework/src/flux/connect"; +import { Paper, Typography } from "@material-ui/core"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + + +type props = Connect; + +const ConnectionInfo: React.FunctionComponent = (props) => { + + return ((props.isTopoServerReachable === false || props.isTileServerReachable === false )? +
+
Connection Error
+ {props.isTileServerReachable === false && Tile data can't be loaded.} + {props.isTopoServerReachable === false && Network data can't be loaded.} +
+
: null +) + +} + +const mapStateToProps = (state: IApplicationStoreState) => ({ + isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, + isTileServerReachable: state.network.connectivity.isTileServerAvailable + +}); + + + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + + //zoomToSearchResult: (lat: number, lon: number) => dispatcher.dispatch(new ZoomToSearchResultAction(lat, lon)) + +});; + + +export default connect(mapStateToProps,mapDispatchToProps)(ConnectionInfo) + diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx new file mode 100644 index 000000000..bb98cb467 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; +import { FormControlLabel, Switch, Paper } from "@material-ui/core"; +import connect, { Connect, IDispatcher } from '../../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import { SetIconSwitchAction } from '../../actions/mapActions'; + +type props = Connect & {visible: boolean} + +const IconSwitch: React.FunctionComponent = (props) =>{ + + const toggleChecked = () => { + props.toogle(!props.areIconsEnabled) + }; + + return ( + props.visible ? + } + label="Show icons" + labelPlacement="end" + />: null) +} + +const mapStateToProps = (state: IApplicationStoreState) => ({ + areIconsEnabled: state.network.map.allowIconSwitch +}); + + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + toogle : (enable:boolean) => dispatcher.dispatch(new SetIconSwitchAction(enable)) + +});; + +export default (connect(mapStateToProps,mapDispatchToProps)(IconSwitch)) diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/map.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/map.tsx new file mode 100644 index 000000000..9637d745e --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/map.tsx @@ -0,0 +1,677 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react' +import * as mapboxgl from 'mapbox-gl'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + + +import { Site } from '../../model/site'; +import { SelectSiteAction, ClearHistoryAction, SelectLinkAction } from '../../actions/detailsAction'; +import { OSM_STYLE, URL_API, URL_BASEPATH, URL_TILE_API } from '../../config'; +import { link } from '../../model/link'; +import MapPopup from './mapPopup'; +import { SetPopupPositionAction, SelectMultipleLinksAction, SelectMultipleSitesAction } from '../../actions/popupActions'; +import { Feature } from '../../model/Feature'; +import { HighlightLinkAction, HighlightSiteAction, SetCoordinatesAction, SetStatistics } from '../../actions/mapActions'; +import { addDistance, getUniqueFeatures, increaseBoundingBox } from '../../utils/mapUtils'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import connect, { IDispatcher, Connect } from '../../../../../framework/src/flux/connect'; +import SearchBar from './searchBar'; +import { verifyResponse, IsTileServerReachableAction, handleConnectionError, setTileServerReachableAction, IsBusycheckingConnectivityAction } from '../../actions/connectivityAction'; +import ConnectionInfo from './connectionInfo' +import mapLayerService from '../../utils/mapLayers'; +import Statistics from './statistics'; +import IconSwitch from './iconSwitch'; +import { addImages } from '../../services/mapImagesService'; +import { PopupElement } from '../../model/popupElements'; +import { Button } from '@material-ui/core'; +import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions'; +import customize from '../../../icons/customize.png'; + +type coordinates = { lat: number, lon: number, zoom: number } + +let alarmElements: Feature[] = []; +let map: mapboxgl.Map; +let isLoadingInProgress = false; +let notLoadedBoundingBoxes: mapboxgl.LngLatBounds[] = []; + +let lastBoundingBox: mapboxgl.LngLatBounds | null = null; +let myRef = React.createRef(); + + +class Map extends React.Component { + + constructor(props: mapProps) { + super(props); + //any state stuff + this.state = { isPopupOpen: false } + + } + + updateTheme(){ + mapLayerService.settings=this.props.settings.themes; + if(this.props.settings.mapSettings?.networkMap.styling.theme){ + mapLayerService.selectedTheme = this.props.settings.mapSettings?.networkMap.styling.theme; + } + } + + updateOpacity(){ + if(this.props.settings.mapSettings && this.props.settings.mapSettings.networkMap.tileOpacity){ + mapLayerService.changeMapOpacity(map, Number(this.props.settings.mapSettings.networkMap.tileOpacity)); + } + } + + async componentDidMount() { + + // resize the map, if menu gets collapsed + window.addEventListener("menu-resized", this.handleResize); + + //pass themes to mapLayerService + this.updateTheme(); + + // try if connection to tile + topologyserver are available + + try { + const tiles = await fetch(URL_TILE_API + '/10/0/0.png'); + if (tiles.ok) { + this.props.setTileServerLoaded(true); + }else{ + this.props.setTileServerLoaded(false); + } + + } catch (error) { + this.props.setTileServerLoaded(false); + console.error("tileserver " + URL_TILE_API + " can't be reached."); + } + + try { + const topology = await fetch(URL_API + "/info/count/all"); + verifyResponse(topology); + } catch (error) { + this.props.handleConnectionError(error) + } + + //both done + this.props.setConnectivityCheck(false); + //map loaded in componentDidUpdate + } + + setupMap = () => { + + let lat = this.props.lat; + let lon = this.props.lon; + let zoom = this.props.zoom; + + if(this.props.settings.mapSettings){ + if(this.props.settings.mapSettings.networkMap.startupPosition.latitude){ + lat = Number(this.props.settings.mapSettings.networkMap.startupPosition.latitude) + } + + if(this.props.settings.mapSettings.networkMap.startupPosition.longitude){ + lon = Number(this.props.settings.mapSettings.networkMap.startupPosition.longitude) + } + + if(this.props.settings.mapSettings.networkMap.startupPosition.zoom){ + zoom = Number(this.props.settings.mapSettings.networkMap.startupPosition.zoom) + } + + } + + const coordinates = this.extractCoordinatesFromUrl(); + // override lat/lon/zoom with coordinates from url, if available + if (this.areCoordinatesValid(coordinates)) { + lat = coordinates.lat; + lon = coordinates.lon; + zoom = !Number.isNaN(coordinates.zoom) ? coordinates.zoom : zoom; + } + + map = new mapboxgl.Map({ + container: myRef.current!, + style: OSM_STYLE as any, + center: [lon, lat], + zoom: zoom, + accessToken: '' + }); + + map.on('load', (ev) => { + + map.setMaxZoom(18); + const bbox = map.getBounds(); + this.props.updateMapPosition(bbox.getCenter().lat, bbox.getCenter().lng, map.getZoom()) + + mapLayerService.addBaseSources(map, this.props.selectedSite, this.props.selectedLink); + + addImages(map, (result: boolean)=>{ + if(map.getZoom()>11) + { + mapLayerService.addIconLayers(map, this.props.selectedSite?.properties.id) + }else{ + mapLayerService.addBaseLayers(map); + } + this.updateOpacity(); + + }); + + const boundingBox = increaseBoundingBox(map); + + fetch(`${URL_API}/links/geojson/${boundingBox.west},${boundingBox.south},${boundingBox.east},${boundingBox.north}`) + .then(result => verifyResponse(result)) + .then(result => result.json()) + .then(features => { + if (map.getSource('lines')) { + (map.getSource('lines') as mapboxgl.GeoJSONSource).setData(features); + } + }) + .catch(error => this.props.handleConnectionError(error)); + + + fetch(`${URL_API}/sites/geojson/${boundingBox.west},${boundingBox.south},${boundingBox.east},${boundingBox.north}`) + .then(result => verifyResponse(result)) + .then(result => result.json()) + .then(features => { + if (map.getSource('points')) { + (map.getSource('points') as mapboxgl.GeoJSONSource).setData(features); + } + }) + .catch(error => this.props.handleConnectionError(error)); + }); + + map.on('click', (e: any) => { + + if (map.getLayer('points')) { // data is shown as points + + var clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], + [e.point.x + 5, e.point.y + 5]], { + layers: ['microwave-lines', 'fibre-lines'] + }), "id"); + + const clickedPoints = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['points'] }), "id"); + const alarmedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['alarmedPoints'] }), "id"); + + if (clickedPoints.length != 0) { + + + if (alarmedSites.length > 0) { + alarmedSites.forEach(alarm => { + const index = clickedPoints.findIndex(item => item.properties!.id === alarm.properties!.id); + + if (index !== -1) { + clickedPoints[index].properties!.alarmed = true; + clickedPoints[index].properties!.type = "alarmed"; + } + }); + } + + this.showSitePopup(clickedPoints, e.point.x, e.point.y); + } else if (clickedLines.length != 0) { + this.showLinkPopup(clickedLines, e.point.x, e.point.y); + } + + + } else { // data is shown as icons + + const clickedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-lamps', 'point-building', 'point-data-center', 'point-factory', 'point-remaining'] }), "id"); + const clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], + [e.point.x + 5, e.point.y + 5]], { + layers: ['microwave-lines', 'fibre-lines'] + }), "id"); + + if (clickedSites.length > 0) + this.showSitePopup(clickedSites, e.point.x, e.point.y); + else if (clickedLines.length != 0) { + this.showLinkPopup(clickedLines, e.point.x, e.point.y); + } + } + + }); + + map.on('moveend', () => { + + const mapZoom = Number(map.getZoom().toFixed(2)); + const lat = Number(map.getCenter().lat.toFixed(4)); + const lon = Number(map.getCenter().lng.toFixed(4)); + + + if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== mapZoom) { + this.props.updateMapPosition(lat, lon, mapZoom) + } + + // update the url to current lat,lon,zoom values + + const currentUrl = window.location.href; + const parts = currentUrl.split(URL_BASEPATH); + if(parts.length>0){ + + const detailsPath = parts[1].split("/details/"); + + if (detailsPath[1] !== undefined && detailsPath[1].length > 0) { + this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}/details/${detailsPath[1]}`) + } + else { + this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}`) + } + } + + + //switch icon layers if applicable + mapLayerService.showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id); + + //update statistics + const boundingBox = map.getBounds(); + + fetch(`${URL_API}/info/count/${boundingBox.getWest()},${boundingBox.getSouth()},${boundingBox.getEast()},${boundingBox.getNorth()}`) + .then(result => verifyResponse(result)) + .then(res => res.json()) + .then(result => { + if (result.links !== this.props.linkCount || result.sites !== this.props.siteCount) { + this.props.setStatistics(result.links, result.sites); + } + }) + .catch(error => this.props.handleConnectionError(error));; + }) + + map.on('move', () => { + const mapZoom = map.getZoom(); + + const boundingBox = map.getBounds(); + + this.loadNetworkData(boundingBox); + if (mapZoom > 9) { + + if (map.getLayer('points')) { + map.setLayoutProperty('selectedPoints', 'visibility', 'visible'); + map.setPaintProperty('points', 'circle-radius', 7); + } + } else { + + // reduce size of points / lines if zoomed out + map.setPaintProperty('points', 'circle-radius', 2); + map.setLayoutProperty('selectedPoints', 'visibility', 'none'); + + if (mapZoom <= 4) { + map.setPaintProperty('fibre-lines', 'line-width', 1); + map.setPaintProperty('microwave-lines', 'line-width', 1); + + } else { + map.setPaintProperty('fibre-lines', 'line-width', 2); + map.setPaintProperty('microwave-lines', 'line-width', 2); + } + } + }); + } + + componentDidUpdate(prevProps: mapProps, prevState: {}) { + + //(load map) + //triggered if either settings were done loading or tile/topology server connectivity checked + if(prevProps.settings !== this.props.settings || this.props.isConnectivityCheckBusy !== prevProps.isConnectivityCheckBusy){ + + //update theme if settings changed + if(prevProps.settings !== this.props.settings){ + this.updateTheme(); + } + + //if everything done loading/reachable, load map + if(!this.props.isConnectivityCheckBusy && this.props.isTileServerReachable && !this.props.settings.isLoadingData && (prevProps.settings.isLoadingData !==this.props.settings.isLoadingData || prevProps.isConnectivityCheckBusy !== this.props.isConnectivityCheckBusy)){ + if(map == undefined){ + this.setupMap(); + } + else + if(map.getContainer() !== myRef.current){ + // reload map, because the current container (fresh div) doesn't hold the map and changing containers isn't supported + map.remove(); + this.setupMap(); + } + } + } + + if (map !== undefined) { + if (prevProps.selectedSite?.properties.id !== this.props.selectedSite?.properties.id) { + + if (this.props.selectedSite != null) { + if (map.getSource("selectedLine") !== undefined) { + (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); + (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedSite] }); + } + + + if (map.getLayer('point-lamps') !== undefined) { + + map.setFilter('point-lamps', ['==', 'type', 'street lamp']); + map.setFilter('point-data-center', ['==', 'type', 'data center']); + map.setFilter('point-building', ['==', 'type', 'high rise building']); + map.setFilter('point-factory', ['==', 'type', 'factory']); + + if (this.props.selectedSite?.properties.type !== undefined) { + switch (this.props.selectedSite?.properties.type) { + case 'street lamp': + map.setFilter('point-lamps', ["all", ['==', 'type', 'street lamp'], ['!=', 'id', this.props.selectedSite.properties.id]]); + break; + case 'data center': + map.setFilter('point-data-center', ["all", ['==', 'type', 'data center'], ['!=', 'id', this.props.selectedSite.properties.id]]); + break; + case 'high rise building': + map.setFilter('point-building', ["all", ['==', 'type', 'high rise building'], ['!=', 'id', this.props.selectedSite.properties.id]]) + break; + case 'factory': + map.setFilter('point-factory', ["all", ['==', 'type', 'factory'], ['!=', 'id', this.props.selectedSite.properties.id]]); + break; + } + } + } + + + } + else + { + if (map.getSource("selectedPoints") !== undefined) + (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); + + } + } + + if (prevProps.selectedLink !== this.props.selectedLink) { + if (this.props.selectedLink != null) { + + if (map.getLayer('point-lamps') !== undefined) { + map.setFilter('point-lamps', ['==', 'type', 'street lamp']); + map.setFilter('point-data-center', ['==', 'type', 'data center']); + map.setFilter('point-building', ['==', 'type', 'high rise building']); + map.setFilter('point-factory', ['==', 'type', 'factory']); + } + + if (map.getSource("selectedLine") !== undefined) { + (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); + (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedLink] }); + } + } + else + { + if (map.getSource("selectedLine") !== undefined) + (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] }); + } + } + + if (prevProps.location.pathname !== this.props.location.pathname) { + if (map) { + const coordinates = this.extractCoordinatesFromUrl(); + this.moveMapToCoordinates(coordinates); + } + } + + if (prevProps.alarmlement !== this.props.alarmlement) { + if (this.props.alarmlement !== null && !alarmElements.includes(this.props.alarmlement)) { + if (map.getSource("alarmedPoints")) + (map.getSource("alarmedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: alarmElements }); + alarmElements.push(this.props.alarmlement) + } + } + + if (prevProps.showIcons !== this.props.showIcons) { + if (map && map.getZoom() > 11) { + mapLayerService.showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id); + } + } + + if (prevProps.zoomToElement !== this.props.zoomToElement) { + if (this.props.zoomToElement !== null) { + const currentZoom = map?.getZoom(); + + map.flyTo({ + center: [ + this.props.zoomToElement.lon, + this.props.zoomToElement.lat + ], zoom: currentZoom < 10 ? 10 : currentZoom, + essential: true + }); + } + } + } + } + + componentWillUnmount(){ + window.removeEventListener("menu-resized", this.handleResize); + lastBoundingBox=null; + + // will be checked again on next load + this.props.setConnectivityCheck(true); + } + + handleResize = () => { + if (map) { + // wait a moment until resizing actually happened + window.setTimeout(() => map.resize(), 500); + } + } + + extractCoordinatesFromUrl = (): coordinates => { + const currentUrl = window.location.href; + const mainPathParts = currentUrl.split(URL_BASEPATH); + const coordinatePathPart = mainPathParts[1].split("/details/"); // split by details if present + const allCoordinates = coordinatePathPart[0].replace("/", ""); + const coordinates = allCoordinates.split(","); + return { lat: Number(coordinates[0]), lon: Number(coordinates[1]), zoom: Number(coordinates[2]) } + } + + areCoordinatesValid = (coordinates: coordinates) => { + + if ((!Number.isNaN(coordinates.lat)) && (!Number.isNaN(coordinates.lon))) { + return true; + } else { + return false; + } + } + + moveMapToCoordinates = (coordinates: coordinates) => { + + if (this.areCoordinatesValid(coordinates)) { + let zoom = -1; + + if (!Number.isNaN(coordinates.zoom)) { + zoom = coordinates.zoom; + } + + map.flyTo({ + center: [ + coordinates.lon, + coordinates.lat + ], zoom: zoom !== -1 ? zoom : this.props.zoom, + essential: true + }) + } + } + + loadNetworkData = async (bbox: mapboxgl.LngLatBounds) => { + if (!isLoadingInProgress) { // only load data if loading not in progress + isLoadingInProgress = true; + + if (lastBoundingBox == null) { + lastBoundingBox = bbox; + await this.draw('lines', `${URL_API}/links/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); + await this.draw('points', `${URL_API}/sites/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); + } else { + + // new bbox is bigger than old one + if (bbox.contains(lastBoundingBox.getNorthEast()) && bbox.contains(lastBoundingBox.getSouthWest()) && lastBoundingBox !== bbox) { //if new bb is bigger than old one + + lastBoundingBox = bbox; + + //calculate new boundingBox + const increasedBoundingBox = increaseBoundingBox(map); + + await this.draw('lines', `${URL_API}/links/geojson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`); + await this.draw('points', `${URL_API}/sites/geojson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`); + + } else if (lastBoundingBox.contains(bbox.getNorthEast()) && lastBoundingBox.contains(bbox.getSouthWest())) { // last one contains new one + // bbox is contained in last one, do nothing + isLoadingInProgress = false; + + } else { // bbox is not fully contained in old one, extend + + lastBoundingBox.extend(bbox); + + await this.draw('lines', `${URL_API}/links/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); + await this.draw('points', `${URL_API}/sites/geojson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`); + } + + } + + + if (notLoadedBoundingBoxes.length > 0) { // load last not loaded boundingbox + this.loadNetworkData(notLoadedBoundingBoxes.pop()!) + notLoadedBoundingBoxes = []; + } + + } else { + notLoadedBoundingBoxes.push(bbox); + } + } + + showSitePopup = (sites: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => { + if (sites.length > 1) { + const elements: PopupElement[] = sites.map(feature => {return {name: feature.properties!.name, id: feature.properties!.id}}); + + this.props.setPopupPosition(top, left); + this.props.selectMultipleSites(elements); //name, id object container + this.setState({ isPopupOpen: true }); + + } else { + const id = sites[0].properties!.id; + + fetch(`${URL_API}/sites/${id}`) + .then(result => verifyResponse(result)) + .then(res => res.json() as Promise) + .then(result => { + this.props.selectSite(result); + this.props.highlightSite(result); + this.props.clearDetailsHistory(); + }) + .catch(error => this.props.handleConnectionError(error));; + } + } + + showLinkPopup = (links: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => { + + if (links.length > 1) { + + const elements: PopupElement[] = links.map(feature => {return {name: feature.properties!.name, id: feature.properties!.id}}); + + this.props.setPopupPosition(top, left); + this.props.selectMultipleLinks(elements); + this.setState({ isPopupOpen: true }); + + } else { + var id = links[0].properties!.id; + + fetch(`${URL_API}/links/${id}`) + .then(result => verifyResponse(result)) + .then(res => res.json() as Promise) + .then(result => { + this.props.selectLink(result); + this.props.highlightLink(result); + + this.props.clearDetailsHistory(); + }) + .catch(error => this.props.handleConnectionError(error));; + } + } + + draw = async (layer: string, url: string) => { + + fetch(url) + .then(result => verifyResponse(result)) + .then(res => res.json()) + .then(result => { + isLoadingInProgress = false; + if (map.getSource(layer)) { + (map.getSource(layer) as mapboxgl.GeoJSONSource).setData(result); + } + }) + .catch(error => this.props.handleConnectionError(error));; + } + + render() { + + return <> + +{ + !this.props.settings.isLoadingData ? + +
+ { + this.state.isPopupOpen && + { this.setState({ isPopupOpen: false }); }} /> + } + + + 11} /> + + +
+ :
+ + } + + } + +} + +type mapProps = RouteComponentProps & Connect; + +const mapStateToProps = (state: IApplicationStoreState) => ({ + selectedLink: state.network.map.selectedLink, + selectedSite: state.network.map.selectedSite, + zoomToElement: state.network.map.zoomToElement, + alarmlement: state.network.map.alarmlement, + lat: state.network.map.lat, + lon: state.network.map.lon, + zoom: state.network.map.zoom, + linkCount: state.network.map.statistics.links, + siteCount: state.network.map.statistics.sites, + isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, + isTileServerReachable: state.network.connectivity.isTileServerAvailable, + isConnectivityCheckBusy: state.network.connectivity.isBusy, + showIcons: state.network.map.allowIconSwitch, + settings: state.network.settings, +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + selectSite: (site: Site) => dispatcher.dispatch(new SelectSiteAction(site)), + selectLink: (link: link) => dispatcher.dispatch(new SelectLinkAction(link)), + clearDetailsHistory: () => dispatcher.dispatch(new ClearHistoryAction()), + selectMultipleLinks: (ids: PopupElement[]) => dispatcher.dispatch(new SelectMultipleLinksAction(ids)), + selectMultipleSites: (ids: PopupElement[]) => dispatcher.dispatch(new SelectMultipleSitesAction(ids)), + setPopupPosition: (x: number, y: number) => dispatcher.dispatch(new SetPopupPositionAction(x, y)), + highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)), + highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), + updateMapPosition: (lat: number, lon: number, zoom: number) => dispatcher.dispatch(new SetCoordinatesAction(lat, lon, zoom)), + setStatistics: (linkCount: string, siteCount: string) => dispatcher.dispatch(new SetStatistics(siteCount, linkCount)), + setTileServerLoaded: (reachable: boolean) => dispatcher.dispatch(setTileServerReachableAction(reachable)), + handleConnectionError: (error: Error) => dispatcher.dispatch(handleConnectionError(error)), + navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path, "test3")), + setConnectivityCheck: (done: boolean) => dispatcher.dispatch(new IsBusycheckingConnectivityAction(done)), + +}) + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Map)); \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx new file mode 100644 index 000000000..7a64f5a58 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx @@ -0,0 +1,94 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; +import { Typography, Select, MenuItem, ClickAwayListener, Popper, Paper, FormGroup, Portal, Popover } from '@material-ui/core'; +import { SelectSiteAction, ClearHistoryAction, ClearDetailsAction } from '../../actions/detailsAction'; +import { Site } from '../../model/site'; +import { link } from '../../model/link'; +import { URL_API } from '../../config'; +import { HighlightLinkAction, HighlightSiteAction } from '../../actions/mapActions'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import connect, { IDispatcher, Connect } from '../../../../../framework/src/flux/connect'; +import { verifyResponse, handleConnectionError } from '../../actions/connectivityAction'; + + + + +const MapPopup: React.FunctionComponent = (props) => { + + const [value, setValue] = React.useState(""); + + const handleChange = (event: any) => { + setValue(event.target.value); + + const id = event.target.value; + + + fetch(`${URL_API}/${props.type.toLocaleLowerCase()}s/${id}`) + .then(result => verifyResponse(result)) + .then(res => res.json()) + .then(result => { + props.clearDetailsHistory(); + props.selectElement(result); + props.type === "link" ? props.highlightLink(result) : props.highlightSite(result) + props.onClose(); + }) + .catch(error => { + props.handleConnectionError(error); + props.onClose(); + // props.clearDetails(); + }); + }; + + return <> + + + {`Multiple ${props.type.toLowerCase()}s were selected`} + Please select one. + + + + +} + +type props = Connect& { onClose(): void } + +const mapStateToProps = (state: IApplicationStoreState) => ({ + elements: state.network.popup.selectionPendingForElements, + type: state.network.popup.pendingDataType, + position: state.network.popup.position + +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + selectElement: (site: Site) => dispatcher.dispatch(new SelectSiteAction(site)), + clearDetailsHistory:()=> dispatcher.dispatch(new ClearHistoryAction()), + highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)), + highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), + handleConnectionError: (error:Error) => dispatcher.dispatch(handleConnectionError(error)), + clearDetails: () => dispatcher.dispatch(new ClearDetailsAction()), + +}); + +export default (connect(mapStateToProps, mapDispatchToProps))(MapPopup); \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/searchBar.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/searchBar.tsx new file mode 100644 index 000000000..45bc6092d --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/searchBar.tsx @@ -0,0 +1,160 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; +import { makeStyles, Paper, InputBase, IconButton, Divider, Popover, Typography } from '@material-ui/core'; +import SearchIcon from '@material-ui/icons/Search'; + +import { URL_API } from '../../config'; +import { isSite } from '../../utils/utils'; +import { Site } from '../../model/site'; +import { link } from '../../model/link'; +import { SelectSiteAction, SelectLinkAction } from '../../actions/detailsAction'; +import { HighlightLinkAction, HighlightSiteAction, ZoomToSearchResultAction } from '../../actions/mapActions'; +import { calculateMidPoint } from '../../utils/mapUtils'; +import { SetSearchValueAction } from '../../actions/searchAction'; +import connect,{ Connect, IDispatcher } from '../../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; + + + + + + +const styles = makeStyles({ + root: { + //{ padding:5, position: 'absolute', display:'flex', flexDirection:"column",top: 150, width: 200} + padding: '2px 4px', + position: 'absolute', + display:'flex', + alignItems: 'center', + top: 15, + marginLeft: 5, + width: 400, + }, + input: { + flex: 1, + marginLeft: 5 + }, + iconButton: { + padding: 10, + }, + divider: { + height: 28, + margin: 4, + }, + }); + + +const SearchBar: React.FunctionComponent = (props) =>{ + + const classes = styles(); + const [anchorEl, setAnchorEl] = React.useState(null); + const [errorMessage, setErrorMessage] = React.useState(""); + + const divRef = React.useRef(); + + const handleClick = (e: any) =>{ + + setAnchorEl(null); + if(props.searchterm.length>0){ + + const siteResult = fetch(`${URL_API}/sites/name/${props.searchterm}`) + + const linkResult = fetch(`${URL_API}/links/${props.searchterm}`); + + Promise.all([ siteResult, linkResult]).then((result)=>{ + const suceededResults = result.filter(el=> el.ok); + + if(suceededResults.length==0){ + setAnchorEl(divRef.current); + setErrorMessage("No element found.") + // hide message after 3 sec + window.setTimeout(()=>{setAnchorEl(null)}, 3000); + + }else{ + suceededResults[0].json().then(result =>{ + if(isSite(result)){ + props.selectSite(result); + props.highlightSite(result); + props.zoomToSearchResult(result.location.lat, result.location.lon); + }else{ + props.selectLink(result); + props.highlightLink(result); + const midPoint = calculateMidPoint(result.locationA.lat, result.locationA.lon, result.locationB.lat, result.locationB.lon); + props.zoomToSearchResult(midPoint[1], midPoint[0]) + } + }); + } + }); + } + e.preventDefault(); +} + + const open = Boolean(anchorEl); + + const reachabe = props.isTopoServerReachable && props.isTileServerReachable; + + return ( + <> + + props.setSearchTerm(e.currentTarget.value)} + /> + + + + + + setAnchorEl(null)} anchorEl={anchorEl} anchorOrigin={{ + vertical: "bottom", + horizontal: "left" + }}> + + {errorMessage} + + + + ); +} + +const mapStateToProps = (state: IApplicationStoreState) => ({ + searchterm: state.network.search.value, + isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, + isTileServerReachable: state.network.connectivity.isTileServerAvailable + +}); + +type searchBarProps = Connect; + + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + selectSite:(site: Site)=> dispatcher.dispatch(new SelectSiteAction(site)), + selectLink:(link: link) => dispatcher.dispatch(new SelectLinkAction(link)), + highlightLink:(link: link)=> dispatcher.dispatch(new HighlightLinkAction(link)), + highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), + setSearchTerm: (value: string) => dispatcher.dispatch(new SetSearchValueAction(value)), + zoomToSearchResult: (lat: number, lon: number) => dispatcher.dispatch(new ZoomToSearchResultAction(lat, lon)), +});; + +export default (connect(mapStateToProps,mapDispatchToProps)(SearchBar)) \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map/statistics.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/statistics.tsx new file mode 100644 index 000000000..116b789d7 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map/statistics.tsx @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; +import { Paper, Typography, Tooltip } from '@material-ui/core'; +import InfoIcon from '@material-ui/icons/Info'; + +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import connect, { IDispatcher, Connect } from '../../../../../framework/src/flux/connect'; + +type props = Connect; + +const mapStateToProps = (state: IApplicationStoreState) => ({ + linkCount: state.network.map.statistics.links, + siteCount: state.network.map.statistics.sites, + isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, + isTileServerReachable: state.network.connectivity.isTileServerAvailable, + +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ +}); + +const Statistics: React.FunctionComponent = (props: props) =>{ + + const reachabe = props.isTopoServerReachable && props.isTileServerReachable; + + + return ( +
+ Statistics + + + +
+ + Sites: {props.siteCount} + Links: {props.linkCount} +
) +} + +export default connect(mapStateToProps, mapDispatchToProps)(Statistics); diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx deleted file mode 100644 index 7435a0a3f..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react'; -import { Typography, Select, MenuItem, ClickAwayListener, Popper, Paper, FormGroup, Portal, Popover } from '@material-ui/core'; -import { SelectSiteAction, ClearHistoryAction, ClearDetailsAction } from '../actions/detailsAction'; -import { Site } from '../model/site'; -import { link } from '../model/link'; -import { URL_API } from '../config'; -import { HighlightLinkAction, HighlightSiteAction } from '../actions/mapActions'; -import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; -import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect'; -import { verifyResponse, handleConnectionError } from '../actions/connectivityAction'; - - - - -const MapPopup: React.FunctionComponent = (props) => { - - const [value, setValue] = React.useState(""); - - const handleChange = (event: any) => { - setValue(event.target.value); - - const id = event.target.value; - - - fetch(`${URL_API}/${props.type.toLocaleLowerCase()}s/${id}`) - .then(result => verifyResponse(result)) - .then(res => res.json()) - .then(result => { - props.clearDetailsHistory(); - props.selectElement(result); - props.type === "link" ? props.highlightLink(result) : props.highlightSite(result) - props.onClose(); - }) - .catch(error => { - props.handleConnectionError(error); - props.onClose(); - // props.clearDetails(); - }); - }; - - return <> - - - {`Multiple ${props.type.toLowerCase()}s were selected`} - Please select one. - - - - -} - -type props = Connect& { onClose(): void } - -const mapStateToProps = (state: IApplicationStoreState) => ({ - elements: state.network.popup.selectionPendingForElements, - type: state.network.popup.pendingDataType, - position: state.network.popup.position - -}); - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ - selectElement: (site: Site) => dispatcher.dispatch(new SelectSiteAction(site)), - clearDetailsHistory:()=> dispatcher.dispatch(new ClearHistoryAction()), - highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)), - highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), - handleConnectionError: (error:Error) => dispatcher.dispatch(handleConnectionError(error)), - clearDetails: () => dispatcher.dispatch(new ClearDetailsAction()), - -}); - -export default (connect(mapStateToProps, mapDispatchToProps))(MapPopup); \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/searchBar.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/searchBar.tsx deleted file mode 100644 index 2e698158d..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/searchBar.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react'; -import { makeStyles, Paper, InputBase, IconButton, Divider, Popover, Typography } from '@material-ui/core'; -import SearchIcon from '@material-ui/icons/Search'; - -import { URL_API } from '../config'; -import { isSite } from '../utils/utils'; -import { Site } from '../model/site'; -import { link } from '../model/link'; -import { SelectSiteAction, SelectLinkAction } from '../actions/detailsAction'; -import { HighlightLinkAction, HighlightSiteAction, ZoomToSearchResultAction } from '../actions/mapActions'; -import { calculateMidPoint } from '../utils/mapUtils'; -import { SetSearchValueAction } from '../actions/searchAction'; -import connect,{ Connect, IDispatcher } from '../../../../framework/src/flux/connect'; -import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; - - - - - - -const styles = makeStyles({ - root: { - //{ padding:5, position: 'absolute', display:'flex', flexDirection:"column",top: 150, width: 200} - padding: '2px 4px', - position: 'absolute', - display:'flex', - alignItems: 'center', - top: 15, - marginLeft: 5, - width: 400, - }, - input: { - flex: 1, - marginLeft: 5 - }, - iconButton: { - padding: 10, - }, - divider: { - height: 28, - margin: 4, - }, - }); - - -const SearchBar: React.FunctionComponent = (props) =>{ - - const classes = styles(); - const [anchorEl, setAnchorEl] = React.useState(null); - const [errorMessage, setErrorMessage] = React.useState(""); - - const divRef = React.useRef(); - - const handleClick = (e: any) =>{ - - setAnchorEl(null); - if(props.searchterm.length>0){ - - const siteResult = fetch(`${URL_API}/sites/name/${props.searchterm}`) - - const linkResult = fetch(`${URL_API}/links/${props.searchterm}`); - - Promise.all([ siteResult, linkResult]).then((result)=>{ - const suceededResults = result.filter(el=> el.ok); - - if(suceededResults.length==0){ - setAnchorEl(divRef.current); - setErrorMessage("No element found.") - // hide message after 3 sec - window.setTimeout(()=>{setAnchorEl(null)}, 3000); - - }else{ - suceededResults[0].json().then(result =>{ - if(isSite(result)){ - props.selectSite(result); - props.highlightSite(result); - props.zoomToSearchResult(result.location.lat, result.location.lon); - }else{ - props.selectLink(result); - props.highlightLink(result); - const midPoint = calculateMidPoint(result.locationA.lat, result.locationA.lon, result.locationB.lat, result.locationB.lon); - props.zoomToSearchResult(midPoint[1], midPoint[0]) - } - }); - } - }); - } - e.preventDefault(); -} - - const open = Boolean(anchorEl); - - const reachabe = props.isTopoServerReachable && props.isTileServerReachable; - - return ( - <> - - props.setSearchTerm(e.currentTarget.value)} - /> - - - - - - setAnchorEl(null)} anchorEl={anchorEl} anchorOrigin={{ - vertical: "bottom", - horizontal: "left" - }}> - - {errorMessage} - - - - ); -} - -const mapStateToProps = (state: IApplicationStoreState) => ({ - searchterm: state.network.search.value, - isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, - isTileServerReachable: state.network.connectivity.isTileServerAvailable - -}); - -type searchBarProps = Connect; - - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ - selectSite:(site: Site)=> dispatcher.dispatch(new SelectSiteAction(site)), - selectLink:(link: link) => dispatcher.dispatch(new SelectLinkAction(link)), - highlightLink:(link: link)=> dispatcher.dispatch(new HighlightLinkAction(link)), - highlightSite: (site: Site) => dispatcher.dispatch(new HighlightSiteAction(site)), - setSearchTerm: (value: string) => dispatcher.dispatch(new SetSearchValueAction(value)), - zoomToSearchResult: (lat: number, lon: number) => dispatcher.dispatch(new ZoomToSearchResultAction(lat, lon)), -});; - -export default (connect(mapStateToProps,mapDispatchToProps)(SearchBar)) \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/statistics.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/statistics.tsx deleted file mode 100644 index 4103d64f1..000000000 --- a/sdnr/wt/odlux/apps/networkMapApp/src/components/statistics.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ - -import * as React from 'react'; -import { Paper, Typography, Tooltip } from '@material-ui/core'; -import InfoIcon from '@material-ui/icons/Info'; - -import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; -import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect'; - -type props = Connect; - -const mapStateToProps = (state: IApplicationStoreState) => ({ - linkCount: state.network.map.statistics.links, - siteCount: state.network.map.statistics.sites, - isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable, - isTileServerReachable: state.network.connectivity.isTileServerAvailable, - -}); - -const mapDispatchToProps = (dispatcher: IDispatcher) => ({ -}); - -const Statistics: React.FunctionComponent = (props: props) =>{ - - const reachabe = props.isTopoServerReachable && props.isTileServerReachable; - - - return ( -
- Statistics - - - -
- - Sites: {props.siteCount} - Links: {props.linkCount} -
) -} - -export default connect(mapStateToProps, mapDispatchToProps)(Statistics); diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts index 7214705e1..8ab82f2e9 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts @@ -17,12 +17,12 @@ */ import { IActionHandler } from "../../../../framework/src/flux/action"; -import { IsTopologyServerReachableAction, IsTileServerReachableAction } from "../actions/connectivityAction"; +import { IsTopologyServerReachableAction, IsTileServerReachableAction, IsBusycheckingConnectivityAction } from "../actions/connectivityAction"; -export type connectivityState = {isToplogyServerAvailable: boolean, isTileServerAvailable: boolean }; +export type connectivityState = {isToplogyServerAvailable: boolean, isTileServerAvailable: boolean, isBusy: boolean }; -const initialState: connectivityState = {isToplogyServerAvailable: true, isTileServerAvailable: true}; +const initialState: connectivityState = {isToplogyServerAvailable: true, isTileServerAvailable: true, isBusy: true}; export const ConnectivityReducer: IActionHandler =(state=initialState, action)=> { @@ -32,6 +32,9 @@ export const ConnectivityReducer: IActionHandler =(state=init else if (action instanceof IsTileServerReachableAction){ state = Object.assign({}, state, { isTileServerAvailable: action.reachable }); + }else if(action instanceof IsBusycheckingConnectivityAction){ + state = {...state, isBusy: action.isBusy} + } return state; diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts index c9c475411..697dbd7a0 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts @@ -23,13 +23,15 @@ import { PopupsReducer, popupStoreState } from "./popupReducer"; import { MapReducer, mapState } from "./mapReducer"; import { SearchReducer, searchState } from "./searchReducer"; import { connectivityState, ConnectivityReducer } from './connectivityReducer'; +import { SettingsReducer, SettingsState } from './settingsReducer'; export interface INetworkAppStoreState{ details: DetailsStoreState, popup: popupStoreState, map: mapState, search: searchState, - connectivity: connectivityState + connectivity: connectivityState, + settings: SettingsState } declare module '../../../../framework/src/store/applicationStore' { @@ -43,7 +45,8 @@ const appHandler = { popup: PopupsReducer, map: MapReducer, search: SearchReducer, - connectivity: ConnectivityReducer}; + connectivity: ConnectivityReducer, + settings: SettingsReducer}; export const networkmapRootHandler = combineActionHandler(appHandler) diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/settingsReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/settingsReducer.ts new file mode 100644 index 000000000..977a379a0 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/settingsReducer.ts @@ -0,0 +1,61 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { NetworkMapSettings, NetworkMapThemes } from "../model/settings"; +import { IActionHandler } from "../../../../framework/src/flux/action"; +import { SetBusyLoadingAction, SetMapSettingsAction, SetSettingsAction, SetThemeSettingsAction } from "../actions/settingsAction"; + +export type SettingsState = { + mapSettings: NetworkMapSettings|null, + themes: NetworkMapThemes, + isLoadingData: boolean +}; + + +const defaultThemes:NetworkMapThemes = {networkMapThemes:{themes: [ + + {key: "light", site: "#11b4da", selectedSite: "#116bda", fiberLink: "#1154d9", microwaveLink: "#039903"}, + {key: "dark", site: "#000000", selectedSite: "#6e6e6e", fiberLink: "#0a2a6b", microwaveLink: "#005200"}, +]}} + +const initialState: SettingsState = { + mapSettings: null, + themes: defaultThemes, + isLoadingData: true + +}; + +export const SettingsReducer: IActionHandler = (state = initialState, action) => { + + if(action instanceof SetSettingsAction){ + state = { + isLoadingData: false, + mapSettings: {networkMap: action.settings.networkMap}, + themes:{networkMapThemes: {themes: action.settings.networkMapThemes.themes}} + }; + }else if(action instanceof SetMapSettingsAction){ + state={...state, mapSettings: action.settings}; + }else if(action instanceof SetThemeSettingsAction){ + state={...state, themes:{networkMapThemes: {themes: action.settings.networkMapThemes.themes}}}; + }else if(action instanceof SetBusyLoadingAction){ + state={...state, isLoadingData: action.busy}; + } + + return state; + +} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/settings.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/settings.ts new file mode 100644 index 000000000..521f47ccc --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/settings.ts @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +export type NetworkMapSettings = { + networkMap: { + startupPosition: { latitude?: string, longitude?: string, zoom?: string }, + tileOpacity: string, + styling: { theme: string } } }; + +export type ThemeElement = { + key: string, + site: string, + selectedSite: string, + microwaveLink: string, + fiberLink: string}; + +export type NetworkMapThemes = {networkMapThemes: {themes: ThemeElement[]} }; + +export type NetworkSettings = NetworkMapSettings & NetworkMapThemes; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx index 3ce435f9b..e93368524 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx +++ b/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx @@ -30,17 +30,46 @@ import applicationApi from "../../../framework/src/services/applicationApi"; import { UpdateDetailsView } from "./actions/detailsAction"; import { findSiteToAlarm } from "./actions/mapActions"; import { URL_BASEPATH } from "./config"; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from "react-router-dom"; +import CustomizationView from "./components/customize/customizationView"; +import { getSettings } from "./actions/settingsAction"; +import connect, { Connect, IDispatcher } from "../../../framework/src/flux/connect"; +import { IApplicationStoreState } from "../../../framework/src/store/applicationStore"; -const App : React.SFC = (props) => { - return -}; +const mapProps = (state: IApplicationStoreState) => ({ +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + getSettings: () => dispatcher.dispatch(getSettings()) + +}); + + +const NetworkRouterApp = withRouter(connect(mapProps, mapDisp)((props: RouteComponentProps & Connect) => { + + React.useLayoutEffect(() => { + (async function waitFor() { + await props.getSettings(); + })(); + + }, []); + + //props.history.action = "POP"; + return ( + + + + + + ) +})); export function register() { applicationManager.registerApplication({ name: URL_BASEPATH, // used as name of state as well icon: faMapMarked, rootActionHandler: networkmapRootHandler, - rootComponent: App, + rootComponent: NetworkRouterApp, menuEntry: "Network Map" }); } @@ -73,12 +102,13 @@ subscribe(["ObjectCreationNotification", })); */ +/* subscribe("ProblemNotification", (fault => { const store = applicationApi && applicationApi.applicationStore; if (fault && store) { - store.dispatch(findSiteToAlarm(fault.nodeName)); + store.dispatch(findSiteToAlarm(fault.nodeName)); + - } -})); +}));*/ diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/services/settingsService.ts b/sdnr/wt/odlux/apps/networkMapApp/src/services/settingsService.ts new file mode 100644 index 000000000..2a2f09466 --- /dev/null +++ b/sdnr/wt/odlux/apps/networkMapApp/src/services/settingsService.ts @@ -0,0 +1,43 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { NetworkMapSettings, NetworkMapThemes, NetworkSettings } from "../model/settings"; +import { requestRest } from "../../../../framework/src/services/restService"; + + class SettingsService{ + + public getMapSettings = async () =>{ + const result = await requestRest("/userdata", {method: "GET"}); + return result; + } + + public getMapThemes = async () =>{ + const result = await requestRest("/userdata/networkMapThemes", {method: "GET"}); + return result; + } + + public updateMapSettings = async (newElement: NetworkMapSettings) =>{ + + const result = await requestRest("/userdata/networkMap", {method: "PUT", body: JSON.stringify(newElement.networkMap)}); + return result; + } + + + } + + export const settingsService = new SettingsService(); \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts index 1d4aa89ad..6dfd7983a 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts +++ b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts @@ -18,553 +18,313 @@ import * as mapboxgl from 'mapbox-gl'; import { Feature } from 'model/Feature'; +import { NetworkMapThemes, ThemeElement } from 'model/settings'; const fibreLinkColor = "#1154d9"; -const microwaveLinkColor="#039903"; +const microwaveLinkColor = "#039903"; -export const addBaseSources = (map: mapboxgl.Map, selectedPoint: Feature|null, selectedLine: Feature|null) =>{ - +class MapLayerService { - // make sure the sources don't already exist - // (if the networkmap app gets opened quickly within short time periods, the prior sources might not be fully removed) + checkedLayers = false; + settings: NetworkMapThemes; + selectedTheme: string | null = null; - if(!map.getSource("lines")){ - - map.addSource('lines', { - type: 'geojson', - data: { type: "FeatureCollection", features: [] } - }); - } - - if(!map.getSource("selectedLine")) - { - const features = selectedLine !== null ? [selectedLine] : []; - map.addSource('selectedLine', { - type: 'geojson', - data: { type: "FeatureCollection", features: features } - }); - } + public addBaseSources = (map: mapboxgl.Map, selectedPoint: Feature | null, selectedLine: Feature | null) => { - if(!map.getSource("points")) - { - map.addSource('points', { - type: 'geojson', - data: { type: "FeatureCollection", features: [] } - }); - } - if(!map.getSource("selectedPoints")) - { - const selectedPointFeature = selectedPoint !== null ? [selectedPoint] : []; - map.addSource('selectedPoints', { - type: 'geojson', - data: { type: "FeatureCollection", features: selectedPointFeature } - - }); - } + // make sure the sources don't already exist + // (if the networkmap app gets opened quickly within short time periods, the prior sources might not be fully removed) - if(!map.getSource("alarmedPoints")) - { - map.addSource("alarmedPoints", { - type: 'geojson', - data: {type:"FeatureCollection", features:[]} - }); - } -} - -export const addBaseLayers = (map: mapboxgl.Map, selectedPoint: Feature|null, selectedLine: Feature|null) => { - - addCommonLayers(map); + if (!map.getSource("lines")) { - map.addLayer({ - id: 'points', - source: 'points', - type: 'circle', - paint: { - 'circle-color': '#11b4da', - 'circle-radius': 7, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' + map.addSource('lines', { + type: 'geojson', + data: { type: "FeatureCollection", features: [] } + }); } - }); - - map.addLayer({ - id: 'selectedPoints', - source: 'selectedPoints', - type: 'circle', - paint: { - 'circle-color': '#116bda', - 'circle-radius': 9, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' + + if (!map.getSource("selectedLine")) { + const features = selectedLine !== null ? [selectedLine] : []; + map.addSource('selectedLine', { + type: 'geojson', + data: { type: "FeatureCollection", features: features } + }); } - }); - - map.addLayer({ - id: 'alarmedPoints', - source: 'alarmedPoints', - type: 'circle', - paint: { - 'circle-color': '#CC0000', - 'circle-radius': 9, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' + + if (!map.getSource("points")) { + map.addSource('points', { + type: 'geojson', + data: { type: "FeatureCollection", features: [] } + }); } - }); -} -export const addIconLayers = (map: mapboxgl.Map, selectedSiteId?: string) =>{ + if (!map.getSource("selectedPoints")) { + const selectedPointFeature = selectedPoint !== null ? [selectedPoint] : []; + map.addSource('selectedPoints', { + type: 'geojson', + data: { type: "FeatureCollection", features: selectedPointFeature } - addCommonLayers(map); - createIconLayers(map, selectedSiteId); -} + }); + } -const createIconLayers =(map: mapboxgl.Map, selectedSiteId?: string) =>{ - map.addLayer({ - 'id': 'point-lamps', - 'type': 'symbol', - 'source': 'points', - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'lamp', - 'icon-size': 0.1 - - }, - 'filter': createFilter("street lamp", selectedSiteId), - }); - - map.addLayer({ - 'id': 'point-building', - 'type': 'symbol', - 'source': 'points', - 'filter': createFilter("high rise building", selectedSiteId), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'house', - 'icon-size': 0.1 + if (!map.getSource("alarmedPoints")) { + map.addSource("alarmedPoints", { + type: 'geojson', + data: { type: "FeatureCollection", features: [] } + }); } - }); - - map.addLayer({ - 'id': 'point-data-center', - 'type': 'symbol', - 'source': 'points', - 'filter': createFilter("data center", selectedSiteId), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'data-center', - 'icon-size': 0.1 - } }); + } + + private addCircleLayer = (map: mapboxgl.Map, id: string, source: string, circleColor: string, radius: number, strokeWidth: number, outerColor: string) => { map.addLayer({ - 'id': 'point-factory', - 'type': 'symbol', - 'source': 'points', - 'filter': createFilter("factory", selectedSiteId), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'factory', - 'icon-size': 0.2 + id: id, + source: source, + type: 'circle', + paint: { + 'circle-color': circleColor, + 'circle-radius': radius, + 'circle-stroke-width': strokeWidth, + 'circle-stroke-color': outerColor } }); + } - //select layers + private addLineLayer = (map: mapboxgl.Map, id: string, source: string, color: string, width: number, filter: string[]) => { map.addLayer({ - 'id': 'select-point-lamps', - 'type': 'symbol', - 'source': 'selectedPoints', + 'id': id, + 'type': 'line', + 'source': source, 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'lamp', - 'icon-size': 0.15 - + 'line-join': 'round', + 'line-cap': 'round' }, - 'filter': ['==', 'type', 'street lamp'], - }); - - map.addLayer({ - 'id': 'select-point-buildings', - 'type': 'symbol', - 'source': 'selectedPoints', - 'filter': ['==', 'type', 'high rise building'], - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'house', - 'icon-size': 0.15 - } - }); - - map.addLayer({ - 'id': 'select-point-data-center', - 'type': 'symbol', - 'source': 'selectedPoints', - 'filter': ['==', 'type', 'data center'], - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'data-center', - 'icon-size': 0.15 - } - }); - - map.addLayer({ - 'id': 'select-point-factory', - 'type': 'symbol', - 'source': 'selectedPoints', - 'filter': ['==', 'type', 'factory'], - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'factory', - 'icon-size': 0.3 - } + 'paint': { + 'line-color': color, + 'line-width': width + }, + 'filter': filter }); + } - //alarm layers - + private addIconLayer = (map: mapboxgl.Map, id: string, source: string, iconName: string, iconSize: number, filter: (string | string[])[]) => { map.addLayer({ - 'id': 'point-lamps-alarm', + 'id': id, 'type': 'symbol', - 'source': 'alarmedPoints', + 'source': source, 'layout': { 'icon-allow-overlap': true, - 'icon-image': 'lamp-red', - 'icon-size': 0.15 + 'icon-image': iconName, + 'icon-size': iconSize }, - 'filter': createFilter("street lamp"), - }); - - map.addLayer({ - 'id': 'point-building-alarm', - 'type': 'symbol', - 'source': 'alarmedPoints', - 'filter': createFilter("high rise building"), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'house-red', - 'icon-size': 0.15 - } + 'filter': filter, }); + } - map.addLayer({ - 'id': 'point-data-center-alarm', - 'type': 'symbol', - 'source': 'alarmedPoints', - 'filter': createFilter("data center"), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'data-center-red', - 'icon-size': 0.15 - } }); - - map.addLayer({ - 'id': 'point-factory-alarm', - 'type': 'symbol', - 'source': 'alarmedPoints', - 'filter': createFilter("factory"), - 'layout': { - 'icon-allow-overlap': true, - 'icon-image': 'factory-red', - 'icon-size': 0.3 - } - }); + /** + * Pick the correct theme based on user selection + */ + private pickTheme = () => { + if (this.selectedTheme !== null) { + const result = this.settings.networkMapThemes.themes.find(el => el.key === this.selectedTheme); + if (result) + return result; - map.addLayer({ - id: 'point-remaining', - source: 'points', - type: 'circle', - 'filter': ['none', ['==', 'type', "high rise building"], ['==', 'type', "data center"], ['==', 'type', "factory"], ['==', 'type', "street lamp"] ], - paint: { - 'circle-color': '#11b4da', - 'circle-radius': 7, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' } - }); -} - const addCommonLayers = (map: mapboxgl.Map) =>{ - - map.addLayer({ - 'id': 'microwave-lines', - 'type': 'line', - 'source': 'lines', - 'layout': { - 'line-join': 'round', - 'line-cap': 'round' - }, - 'paint': { - 'line-color': microwaveLinkColor, - 'line-width': 2 - }, - 'filter': ['==', 'type', 'microwave'] - }); - - map.addLayer({ - 'id': 'fibre-lines', - 'type': 'line', - 'source': 'lines', - 'layout': { - 'line-join': 'round', - 'line-cap': 'round' - }, - 'paint': { - 'line-color': fibreLinkColor, - 'line-width': 2 - }, - 'filter': ['==', 'type', 'fibre'] - }); - - map.addLayer({ - 'id': 'selectedLineMicrowave', - 'type': 'line', - 'source': 'selectedLine', - 'layout': { - 'line-join': 'round', - 'line-cap': 'round' - }, - 'paint': { - 'line-color': microwaveLinkColor, - 'line-width': 4 - }, - 'filter': ['==', 'type', 'microwave'] - }); - - map.addLayer({ - 'id': 'selectedLineFibre', - 'type': 'line', - 'source': 'selectedLine', - 'layout': { - 'line-join': 'round', - 'line-cap': 'round' - }, - 'paint': { - 'line-color': fibreLinkColor, - 'line-width': 4 - }, - 'filter': ['==', 'type', 'fibre'] - }); -} + return this.settings.networkMapThemes.themes[0]; -export const removeBaseLayers = (map: mapboxgl.Map) => { - - map.removeLayer("points"); - map.removeLayer("lines"); - map.removeLayer('selectedPoints'); - map.removeLayer('selectedLine'); -} - -const removeIconLayers = (map: mapboxgl.Map) =>{ - - map.removeLayer('point-building'); - map.removeLayer('point-lamps'); - map.removeLayer('point-data-center'); - map.removeLayer('point-factory'); - map.removeLayer('point-remaining'); - map.removeLayer('select-point-data-center'); - map.removeLayer('select-point-buildings'); - map.removeLayer('select-point-lamps'); - map.removeLayer('select-point-factory'); - map.removeLayer('point-building-alarm'); - map.removeLayer('point-lamps-alarm'); - map.removeLayer('point-data-center-alarm'); - map.removeLayer('point-factory-alarm'); -} - -let checkedLayers = false; + } -const createFilter = (type:'street lamp'|'high rise building'|'data center'|'factory', selectedSiteId?:string) =>{ + public addBaseLayers = (map: mapboxgl.Map, themesettings?: ThemeElement) => { - return selectedSiteId === undefined ? ['==', 'type', type] : ["all", ['==', 'type', type], ['!=', 'id', selectedSiteId]] -} + const theme = !themesettings ? this.pickTheme() : themesettings; -export const showIconLayers = (map: mapboxgl.Map, show: boolean, selectedSiteId?: string) => { + this.addCommonLayers(map); - const zoom = map.getZoom(); - - if(show){ - - if (zoom > 11) { + this.addCircleLayer(map, 'points', 'points', theme.site, 7, 1, '#fff'); + this.addCircleLayer(map, 'selectedPoints', 'selectedPoints', theme.selectedSite, 9, 1, '#fff'); + this.addCircleLayer(map, 'alarmedPoints', 'alarmedPoints', '#CC0000', 9, 1, '#fff'); + } - const bounds = map.getBounds(); + public addIconLayers = (map: mapboxgl.Map, selectedSiteId?: string) => { - if(map.getLayer('points')!== undefined && map.getLayer('point-lamps')===undefined && !checkedLayers){ + this.addCommonLayers(map); + this.createIconLayers(map, selectedSiteId); + } - // if sites don't have a type don't change layers to icons - const elements = map.queryRenderedFeatures( undefined,{ - layers: ['points'], filter:['has', 'type'] - }); - checkedLayers=true; + private createIconLayers = (map: mapboxgl.Map, selectedSiteId?: string) => { - if(elements.length>0 && elements.length<1000){ + this.addIconLayer(map, 'point-lamps', 'points', 'lamp', 0.1, this.createFilter("street lamp", selectedSiteId)); + this.addIconLayer(map, 'point-building', 'points', 'house', 0.1, this.createFilter("high rise building", selectedSiteId)); + this.addIconLayer(map, 'point-data-center', 'points', 'data-center', 0.1, this.createFilter("data center", selectedSiteId)); + this.addIconLayer(map, 'point-factory', 'points', 'factory', 0.2, this.createFilter("factory", selectedSiteId)); - if (map.getLayer('point-lamps') === undefined) { - map.removeLayer('points'); - map.setLayoutProperty('alarmedPoints', 'visibility', 'none'); - map.setLayoutProperty('selectedPoints', 'visibility', 'none'); - createIconLayers(map,selectedSiteId); - //map.moveLayer('point-remaining','selectedPoints'); - } - } - } - - } else { - swapLayersBack(map); - } -}else{ - swapLayersBack(map); -} -} + //select layers + this.addIconLayer(map, 'select-point-lamps', 'selectedPoints', 'lamp', 0.15, ['==', 'type', 'street lamp']); + this.addIconLayer(map, 'select-point-buildings', 'selectedPoints', 'house', 0.15, ['==', 'type', 'high rise building']); + this.addIconLayer(map, 'select-point-data-center', 'selectedPoints', 'data-center', 0.15, ['==', 'type', 'data center']); + this.addIconLayer(map, 'select-point-factory', 'selectedPoints', 'factory', 0.3, ['==', 'type', 'factory']); -export const swapLayersBack = (map: mapboxgl.Map) =>{ - checkedLayers=false; + //alarm layers + this.addIconLayer(map, 'point-lamps-alarm', 'alarmedPoints', 'lamp-red', 0.3, this.createFilter("street lamp")); + this.addIconLayer(map, 'point-building-alarm', 'alarmedPoints', 'house-red', 0.3, this.createFilter("high rise building")); + this.addIconLayer(map, 'point-data-center-alarm', 'alarmedPoints', 'data-center-red', 0.3, this.createFilter("data center")); + this.addIconLayer(map, 'point-factory-alarm', 'alarmedPoints', 'factory-red', 0.45, this.createFilter("factory")); - if(map.getLayer('selectedPoints') === undefined){ map.addLayer({ - id: 'selectedPoints', - source: 'selectedPoints', + id: 'point-remaining', + source: 'points', type: 'circle', + 'filter': ['none', ['==', 'type', "high rise building"], ['==', 'type', "data center"], ['==', 'type', "factory"], ['==', 'type', "street lamp"]], paint: { - 'circle-color': '#116bda', - 'circle-radius': 9, + 'circle-color': '#11b4da', + 'circle-radius': 7, 'circle-stroke-width': 1, 'circle-stroke-color': '#fff' } }); } - if(map.getLayer('alarmedPoints') === undefined){ - map.addLayer({ - id: 'alarmedPoints', - source: 'alarmedPoints', - type: 'circle', - paint: { - 'circle-color': '#CC0000', - 'circle-radius': 9, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' - } - }); + private addCommonLayers = (map: mapboxgl.Map, themesettings?: ThemeElement) => { + + const theme = !themesettings ? this.pickTheme() : themesettings; + + this.addLineLayer(map, 'microwave-lines', 'lines', theme.microwaveLink, 2, ['==', 'type', 'microwave']); + this.addLineLayer(map, 'fibre-lines', 'lines', theme.fiberLink, 2, ['==', 'type', 'fibre']); + this.addLineLayer(map, 'selectedLineMicrowave', 'selectedLine', theme.microwaveLink, 4, ['==', 'type', 'microwave']); + this.addLineLayer(map, 'selectedLineFibre', 'selectedLine', theme.fiberLink, 4, ['==', 'type', 'fibre']); } + public removeBaseLayers = (map: mapboxgl.Map) => { - if (map.getLayer('points') === undefined) { + map.removeLayer("points"); + map.removeLayer("lines"); + map.removeLayer('selectedPoints'); + map.removeLayer('selectedLine'); + } - map.setLayoutProperty('selectedPoints', 'visibility', 'visible'); - map.setLayoutProperty('alarmedPoints', 'visibility', 'visible'); - removeIconLayers(map); + private removeIconLayers = (map: mapboxgl.Map) => { + + map.removeLayer('point-building'); + map.removeLayer('point-lamps'); + map.removeLayer('point-data-center'); + map.removeLayer('point-factory'); + map.removeLayer('point-remaining'); + map.removeLayer('select-point-data-center'); + map.removeLayer('select-point-buildings'); + map.removeLayer('select-point-lamps'); + map.removeLayer('select-point-factory'); + map.removeLayer('point-building-alarm'); + map.removeLayer('point-lamps-alarm'); + map.removeLayer('point-data-center-alarm'); + map.removeLayer('point-factory-alarm'); + } - map.addLayer({ - id: 'points', - source: 'points', - type: 'circle', - paint: { - 'circle-color': '#11b4da', - 'circle-radius': 7, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' - } - }); - map.moveLayer('points', map.getLayer('selectedPoints').id); + private createFilter = (type: 'street lamp' | 'high rise building' | 'data center' | 'factory', selectedSiteId?: string) => { + + return selectedSiteId === undefined ? ['==', 'type', type] : ["all", ['==', 'type', type], ['!=', 'id', selectedSiteId]] } -} -const addClusterLayers = (map: mapboxgl.Map, data: any) => { - map.addSource('clusters', { - type: 'geojson', - data: data - }); - - map.addSource('selectedLine', { - type: 'geojson', - data: { type: "FeatureCollection", features: [] } - }); - - map.addSource('selectedPoints', { - type: 'geojson', - data: { type: "FeatureCollection", features: [] } - - }); - - map.addLayer({ - id: 'clusters', - type: 'circle', - source: 'clusters', - filter: ['has', 'count'], - paint: { - 'circle-color': [ - 'step', - ['get', 'count'], - '#51bbd6', - 100, - '#f1f075', - 750, - '#f28cb1' - ], - 'circle-radius': [ - 'step', - ['get', 'count'], - 20, - 100, - 30, - 750, - 40 - ] + public showIconLayers = (map: mapboxgl.Map, show: boolean, selectedSiteId?: string) => { + + const zoom = map.getZoom(); + + if (show) { + + if (zoom > 11) { + + const bounds = map.getBounds(); + + if (map.getLayer('points') !== undefined && map.getLayer('point-lamps') === undefined && !this.checkedLayers) { + + // if sites don't have a type don't change layers to icons + const elements = map.queryRenderedFeatures(undefined, { + layers: ['points'], filter: ['has', 'type'] + }); + this.checkedLayers = true; + + if (elements.length > 0 && elements.length < 1000) { + + if (map.getLayer('point-lamps') === undefined) { + map.removeLayer('points'); + map.setLayoutProperty('alarmedPoints', 'visibility', 'none'); + map.setLayoutProperty('selectedPoints', 'visibility', 'none'); + this.createIconLayers(map, selectedSiteId); + //map.moveLayer('point-remaining','selectedPoints'); + + } + } + } + + } else { + this.swapLayersBack(map); + } + } else { + this.swapLayersBack(map); } - }); - - - map.addLayer({ - id: 'cluster-count', - type: 'symbol', - source: 'clusters', - filter: ['has', 'count'], - layout: { - 'text-field': '{count}', - 'text-font': ['Roboto Bold'], - 'text-size': 12 + } + + public swapLayersBack = (map: mapboxgl.Map) => { + this.checkedLayers = false; + const theme = this.pickTheme(); + + if (map.getLayer('selectedPoints') === undefined) { + this.addCircleLayer(map, 'selectedPoints', 'selectedPoints', theme.selectedSite, 9, 1, '#fff'); + } - }); - - map.addLayer({ - 'id': 'selectedLine', - 'type': 'line', - 'source': 'selectedLine', - 'layout': { - 'line-join': 'round', - 'line-cap': 'round' - }, - 'paint': { - 'line-color': '#888', - 'line-width': 4 + + if (map.getLayer('alarmedPoints') === undefined) { + this.addCircleLayer(map, 'alarmedPoints', 'alarmedPoints', '#CC0000', 9, 1, '#fff'); + + } + + + if (map.getLayer('points') === undefined) { + + map.setLayoutProperty('selectedPoints', 'visibility', 'visible'); + map.setLayoutProperty('alarmedPoints', 'visibility', 'visible'); + this.removeIconLayers(map); + + this.addCircleLayer(map, 'points', 'points', theme.site, 7, 1, '#fff'); + + + map.moveLayer('points', map.getLayer('selectedPoints').id); } - }); - - map.addLayer({ - id: 'unclustered-points', - source: 'clusters', - filter: ['!', ['has', 'count'],], - type: 'circle', - paint: { - 'circle-color': '#11b4da', - 'circle-radius': 7, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' + } + + public changeMapOpacity = (map: mapboxgl.Map, newValue: number) => { + const newOpacity = newValue / 100; + if (map) { + const tiles = map.getStyle().layers?.filter(el => el.id.includes("tiles")) + tiles?.forEach(layer => { + if (layer.type === 'symbol') { + map.setPaintProperty(layer.id, `icon-opacity`, newOpacity); + map.setPaintProperty(layer.id, `text-opacity`, newOpacity); + } else { + map.setPaintProperty(layer.id, `${layer.type}-opacity`, newOpacity); + } + }) } - }); - - map.addLayer({ - id: 'selectedPoints', - source: 'selectedPoints', - type: 'circle', - paint: { - 'circle-color': '#116bda', - 'circle-radius': 9, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#fff' + + } + + public changeTheme = (map: mapboxgl.Map, themeName: string) => { + this.selectedTheme = themeName; + const theme = this.pickTheme(); + if (theme && map.loaded()) { + map.setPaintProperty('points', 'circle-color', theme.site); + map.setPaintProperty('selectedPoints', 'circle-color', theme.selectedSite); + map.setPaintProperty('microwave-lines', 'line-color', theme.microwaveLink); + map.setPaintProperty('fibre-lines', 'line-color', theme.fiberLink); } - }); + } +} + +const mapLayerService = new MapLayerService(); +export default mapLayerService; -} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js b/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js index e0f16d0e7..3e80514f5 100644 --- a/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js +++ b/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js @@ -139,7 +139,15 @@ module.exports = (env) => { "/yang-schema/": { target: "http://sdnr:8181", secure: false - }, + }, + "/userdata": { + target: "http://sdnr:8181", + secure: false + }, + "/userdata/": { + target: "http://sdnr:8181", + secure: false + }, "/oauth2/": { target: "http://sdnr:8181", secure: false -- cgit 1.2.3-korg