Add customization of networkmap 81/121181/1
authorAijana Schumann <aijana.schumann@highstreet-technologies.com>
Fri, 7 May 2021 11:56:52 +0000 (13:56 +0200)
committerAijana Schumann <aijana.schumann@highstreet-technologies.com>
Fri, 7 May 2021 11:56:52 +0000 (13:56 +0200)
Add customization capabilities to the networkmap

Issue-ID: CCSDK-2938
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: Ibd3fe258d02939ea69b21de25569f20d9087f693

30 files changed:
sdnr/wt/odlux/apps/networkMapApp/icons/README.md
sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/customize.png [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/icons/customize.png.d.ts [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/factory.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/factoryred.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/icons/lampred.png.d.ts
sdnr/wt/odlux/apps/networkMapApp/src/App.tsx
sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts
sdnr/wt/odlux/apps/networkMapApp/src/actions/settingsAction.ts [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx with 93% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/iconSwitch.tsx with 89% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/map.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx with 81% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx with 87% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/searchBar.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/searchBar.tsx with 90% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/components/map/statistics.tsx [moved from sdnr/wt/odlux/apps/networkMapApp/src/components/statistics.tsx with 93% similarity]
sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts
sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts
sdnr/wt/odlux/apps/networkMapApp/src/handlers/settingsReducer.ts [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/model/settings.ts [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx
sdnr/wt/odlux/apps/networkMapApp/src/services/settingsService.ts [new file with mode: 0644]
sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts
sdnr/wt/odlux/apps/networkMapApp/webpack.config.js
sdnr/wt/odlux/odlux.properties

index acfbcf8..85c75c4 100644 (file)
@@ -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
 
index bf398f5..9f956f8 100644 (file)
@@ -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 (file)
index 0000000..91dbf68
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 (file)
index 0000000..16327f5
--- /dev/null
@@ -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
index a58a9f5..dc70197 100644 (file)
@@ -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
index 33f3061..74836be 100644 (file)
@@ -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
index b5c4f19..e341c5f 100644 (file)
@@ -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
index 1fac0a9..317ff7e 100644 (file)
@@ -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
index 9634b12..eeedc7f 100644 (file)
@@ -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
index 12a8f91..b053df0 100644 (file)
@@ -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
index 6caab51..5840d18 100644 (file)
@@ -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() {
index 8fcdc4c..63f52c8 100644 (file)
@@ -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 (file)
index 0000000..5b89823
--- /dev/null
@@ -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/customize/customizationView.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/customize/customizationView.tsx
new file mode 100644 (file)
index 0000000..82e7b79
--- /dev/null
@@ -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<typeof mapProps, typeof mapDispatch>;
+let map: mapboxgl.Map;
+let myMapRef = React.createRef<HTMLDivElement>();
+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> = (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>(Number(props.settings.mapSettings?.networkMap.startupPosition.latitude)|| 52.5);
+  const [longitude, setLongitude] = React.useState<number>(Number(props.settings.mapSettings?.networkMap.startupPosition.longitude)|| 13.35);
+  const [zoom, setZoom] = React.useState<number>(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<HTMLInputElement | HTMLTextAreaElement>, 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<HTMLButtonElement, MouseEvent>) => {
+    e.preventDefault();
+    props.navigateToApplication("network");
+  }
+
+  const onSaveSettings = async (e: React.MouseEvent<HTMLButtonElement, 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<any>("/topology/network/links/geojson/" + default_boundingbox);
+    const sites = requestRest<any>("/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 (<>
+    <h3>Settings</h3>
+    <div style={{ display: 'flex', flexDirection: 'row', flexGrow: 1, height: "100%", position: 'relative' }}>
+      <div style={{ width: "60%", flexDirection: 'column', position:'relative' }}>
+        <Typography variant="body1" style={{ fontWeight: "bold" }} gutterBottom>Startup Position</Typography>
+        <div style={{ display: 'flex', flexDirection: 'row' }}>
+          <TextField type="number" value={latitude} onChange={(e) => setLatitude(e.target.value as any)} style={{ marginLeft: 10 }} label="Latitude" />
+          <TextField type="number" value={longitude} onChange={(e) => setLongitude(e.target.value as any)} style={{ marginLeft: 5 }} label="Longitude" />
+          <TextField type="number" value={zoom} onChange={(e) => setZoom(e.target.value as any)} style={{ marginLeft: 5 }} label="Zoom" />
+        </div>
+
+        <Typography className={classes.sectionMargin} variant="body1" style={{ fontWeight: "bold" }} gutterBottom>
+          Tile Opacity
+        </Typography>
+        <Grid className={classes.elementMargin} container spacing={2} style={{ width: '50%' }}>
+          <Grid item>0</Grid>
+          <Grid item xs>
+            <Slider color="secondary" min={0} max={100} value={opacity} onChange={onOpacityChange} aria-labelledby="continuous-slider" />
+          </Grid>
+          <Grid item>100</Grid>
+        </Grid>
+
+        <Typography className={classes.sectionMargin} variant="body1" style={{ fontWeight: "bold" }} gutterBottom>
+          Style of properties
+      </Typography>
+        <InputLabel id="theme-select-label">Theme</InputLabel>
+        <Select
+          className={classes.elementMargin}
+          value={theme}
+          onChange={onChangeTheme}
+          labelId="theme-select-label"
+          style={{ marginLeft: 10 }}>
+          {
+            props.settings.themes.networkMapThemes.themes.map(el => <MenuItem value={el.key}>{el.key}</MenuItem>)
+          }
+
+        </Select>
+
+        {
+          currentTheme && <div style={{ marginLeft: 60 }}>
+            { //skip the 'key' (theme name) entry
+              Object.keys(currentTheme).slice(1).map(el => <ThemeEntry text={styleText(el)} color={(currentTheme as any)[el]} />)
+            }
+          </div>
+        }
+
+
+        <div className={classes.sectionMargin} style={{ position: 'absolute', right: 0, top: '60%' }}>
+          <Button className={classes.elementMargin} variant="contained"
+            color="primary" onClick={onCancel}>Cancel</Button>
+
+          <Button className={classes.elementMargin} variant="contained"
+            color="secondary" onClick={onSaveSettings}>Save</Button>
+        </div>
+      </div>
+      <div id="map" ref={myMapRef} style={{ width: "35%", height: "50%" }}>
+
+      </div>
+    </div>
+
+  </>)
+
+}
+
+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 (file)
index 0000000..c991aaf
--- /dev/null
@@ -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 <div style={{display: 'flex', flexDirection:'row'}}>
+            <div style={circleStyle} />
+            <Typography variant="body1" style={{marginTop:24}}>{props.text}</Typography>
+            </div>
+
+}
+
+export default ThemeEntry;
\ No newline at end of file
@@ -18,8 +18,8 @@
 
 import * as React from 'react'
 
-import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
-import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect";
+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';
@@ -18,9 +18,9 @@
 
 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';
+import connect, { Connect, IDispatcher } from '../../../../../framework/src/flux/connect';
+import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore';
+import { SetIconSwitchAction } from '../../actions/mapActions';
 
 type props = Connect<typeof mapStateToProps, typeof mapDispatchToProps> & {visible: boolean}
 
@@ -21,25 +21,28 @@ 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 { 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 { 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 { verifyResponse, IsTileServerReachableAction, handleConnectionError, setTileServerReachableAction, IsBusycheckingConnectivityAction } from '../../actions/connectivityAction';
 import ConnectionInfo from './connectionInfo'
-import { showIconLayers, addBaseLayers, addBaseSources, addIconLayers } from '../utils/mapLayers';
+import  mapLayerService  from '../../utils/mapLayers';
 import Statistics from './statistics';
 import IconSwitch from './iconSwitch';
-import { addImages } from '../services/mapImagesService';
-import { PopupElement } from '../model/popupElements';
+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 }
 
@@ -61,31 +64,52 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
 
     }
 
-    componentDidMount() {
+    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);
 
-        // try if connection to tile + topologyserver is available
+        //pass themes to mapLayerService
+        this.updateTheme();
 
-        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 => {
+        // 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);
-                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));
+        } 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 = () => {
@@ -94,6 +118,21 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
         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)) {
@@ -116,15 +155,17 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
             const bbox = map.getBounds();
             this.props.updateMapPosition(bbox.getCenter().lat, bbox.getCenter().lng, map.getZoom())
 
-            addBaseSources(map, this.props.selectedSite, this.props.selectedLink);
-                
+            mapLayerService.addBaseSources(map, this.props.selectedSite, this.props.selectedLink);
+            
             addImages(map, (result: boolean)=>{
                 if(map.getZoom()>11)
                 {
-                    addIconLayers(map, this.props.selectedSite?.properties.id)
+                    mapLayerService.addIconLayers(map, this.props.selectedSite?.properties.id)
                 }else{
-                    addBaseLayers(map, this.props.selectedSite, this.props.selectedLink);
+                    mapLayerService.addBaseLayers(map);
                 }
+            this.updateOpacity();
+
             });
 
             const boundingBox = increaseBoundingBox(map);
@@ -229,7 +270,7 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
            
             
             //switch icon layers if applicable
-            showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
+            mapLayerService.showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
 
             //update statistics
             const boundingBox = map.getBounds();
@@ -277,6 +318,29 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
 
     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) {
 
@@ -361,7 +425,7 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
 
             if (prevProps.showIcons !== this.props.showIcons) {
                 if (map && map.getZoom() > 11) {
-                    showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
+                    mapLayerService.showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
                 }
             }
 
@@ -384,6 +448,9 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
     componentWillUnmount(){
         window.removeEventListener("menu-resized", this.handleResize);
         lastBoundingBox=null;
+
+        // will be checked again on next load
+        this.props.setConnectivityCheck(true); 
     }
 
     handleResize = () => {
@@ -543,6 +610,9 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
 
         return <>
 
+{
+    !this.props.settings.isLoadingData ?
+
             <div id="map" style={{ width: "70%", position: 'relative' }} ref={myRef} >
                 {
                     this.state.isPopupOpen &&
@@ -552,7 +622,16 @@ class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
                 <Statistics />
                 <IconSwitch visible={this.props.zoom>11} />
                 <ConnectionInfo />
+                <Button 
+                disabled={!this.props.isTopoServerReachable}
+                style={{'position': 'absolute', 'right':5, top:5, backgroundColor:'white'}}
+                onClick={e => this.props.navigateToApplication("network", "customize")} >
+                    <img src={customize} />
+                </Button>
             </div>
+            :<div style={{ width: "70%", position: 'relative' }} />
+
+    }
         </>
     }
 
@@ -572,7 +651,9 @@ const mapStateToProps = (state: IApplicationStoreState) => ({
     siteCount: state.network.map.statistics.sites,
     isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable,
     isTileServerReachable: state.network.connectivity.isTileServerAvailable,
-    showIcons: state.network.map.allowIconSwitch
+    isConnectivityCheckBusy: state.network.connectivity.isBusy,
+    showIcons: state.network.map.allowIconSwitch,
+    settings: state.network.settings,
 });
 
 const mapDispatchToProps = (dispatcher: IDispatcher) => ({
@@ -587,7 +668,10 @@ const mapDispatchToProps = (dispatcher: IDispatcher) => ({
     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))
+    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
 
 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';
+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';
 
 
 
@@ -20,16 +20,16 @@ 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';
+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';
 
 
 
@@ -20,8 +20,8 @@ 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';
+import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore';
+import connect, { IDispatcher, Connect } from '../../../../../framework/src/flux/connect';
 
 type props = Connect<typeof mapStateToProps, typeof mapDispatchToProps>;
 
index 7214705..8ab82f2 100644 (file)
  */
 
 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<connectivityState> =(state=initialState, action)=> {
 
@@ -32,6 +32,9 @@ export const ConnectivityReducer: IActionHandler<connectivityState> =(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;
index c9c4754..697dbd7 100644 (file)
@@ -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<INetworkAppStoreState>(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 (file)
index 0000000..977a379
--- /dev/null
@@ -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<SettingsState> = (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 (file)
index 0000000..521f47c
--- /dev/null
@@ -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
index 3ce435f..e933685 100644 (file)
@@ -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 <MainView />
-};
+const mapProps = (state: IApplicationStoreState) => ({
+});
+
+const mapDisp = (dispatcher: IDispatcher) => ({
+  getSettings: () => dispatcher.dispatch(getSettings())
+
+});
+
+
+const NetworkRouterApp = withRouter(connect(mapProps, mapDisp)((props: RouteComponentProps & Connect<typeof mapProps, typeof mapDisp>) => {
+
+  React.useLayoutEffect(() => {
+    (async function waitFor() {
+      await  props.getSettings();
+    })();
+   
+  }, []);
+
+  //props.history.action = "POP";
+  return (
+    <Switch>
+      <Route path={`${props.match.path}/customize`} component={CustomizationView} />
+      <Route path={`${props.match.path}`} component={MainView} />
+      <Redirect to={`${props.match.path}`} />
+    </Switch>
+  )
+}));
 
 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<ObjectNotification & IFormatedMessage>(["ObjectCreationNotification",
 }));
 */
 
+/*
 subscribe<FaultAlarmNotification & IFormatedMessage>("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 (file)
index 0000000..2a2f094
--- /dev/null
@@ -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<any>("/userdata", {method: "GET"});
+        return result;
+    }
+
+    public getMapThemes = async () =>{
+        const result = await requestRest<NetworkMapThemes>("/userdata/networkMapThemes", {method: "GET"});
+        return result;
+    }
+
+    public updateMapSettings = async (newElement: NetworkMapSettings) =>{
+
+        const result = await requestRest<NetworkMapSettings>("/userdata/networkMap", {method: "PUT", body: JSON.stringify(newElement.networkMap)});
+        return result;
+    }
+
+
+ }
+
+ export const settingsService = new SettingsService();
\ No newline at end of file
index 1d4aa89..6dfd798 100644 (file)
 
 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
index e0f16d0..3e80514 100644 (file)
@@ -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
index e2cc41d..36de5d4 100644 (file)
@@ -8,5 +8,5 @@ odlux.apps.inventoryApp.buildno=96.078ad12(21/03/25)
 odlux.apps.linkCalculationApp.buildno=96.078ad12(21/03/25)
 odlux.apps.maintenanceApp.buildno=96.078ad12(21/03/25)
 odlux.apps.mediatorApp.buildno=96.078ad12(21/03/25)
-odlux.apps.networkMapApp.buildno=96.078ad12(21/03/25)
+odlux.apps.networkMapApp.buildno=102.acd1c0b(21/05/07)
 odlux.apps.permanceHistoryApp.buildno=81.1c38886(20/12/04)