@eeacms/volto-arcgis-block 0.1.341 → 0.1.343

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.1.343](https://github.com/eea/volto-arcgis-block/compare/0.1.342...0.1.343) - 20 March 2025
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - Merge pull request #911 from eea/CLMS-DEMO-BUG-LAYERS-NOT-LOADING-AFTER-TOGGLING-CHECK [Unai Bolivar - [`0ab2532`](https://github.com/eea/volto-arcgis-block/commit/0ab25327fa4eea7b62035cd3ce6e5bee32eb6d01)]
12
+ - CLMS-DEMO (bug): Fixed lyers that didn't load on map view after toggleing their checks on [Unai Bolivar - [`a7eb001`](https://github.com/eea/volto-arcgis-block/commit/a7eb001979871dfce976dab786ed2d109bf41dfe)]
13
+ ### [0.1.342](https://github.com/eea/volto-arcgis-block/compare/0.1.341...0.1.342) - 20 March 2025
14
+
15
+ #### :hammer_and_wrench: Others
16
+
17
+ - Merge pull request #909 from eea/CLMS-280000-FIX-DELETE-USER-SERVICE [Unai Bolivar - [`90458d4`](https://github.com/eea/volto-arcgis-block/commit/90458d4ea49c35960bf578e1053aa140874eb0e8)]
18
+ - CLMS-28000 (bug): styles fixed and errors management in place [Unai Bolivar - [`9bfdcc8`](https://github.com/eea/volto-arcgis-block/commit/9bfdcc879043a27fd394277dbc7864637f4b7596)]
19
+ - CLMS-28000 (bug): full extent for layer is working) [Unai Bolivar - [`88cb01f`](https://github.com/eea/volto-arcgis-block/commit/88cb01fd252cff11425871f2d3f712b9c0e4a9d7)]
20
+ - CLMS-285179 (bug): At this commit I have recovered functionality doe loding and unloading the users wms services without losing control of the app. [Unai Bolivar - [`005846e`](https://github.com/eea/volto-arcgis-block/commit/005846efff3e6045dd15d711beb393b3fd213855)]
21
+ - CLMS-285179 (bug): work in progress but going to load stash so this is a checkpoint save [Unai Bolivar - [`157ee6a`](https://github.com/eea/volto-arcgis-block/commit/157ee6a000810f069c12ce20141732f2e0b58546)]
22
+ - CLMS-280000 (feat): user wms service layers appear in active layers tab. need to match the options to figma and set the correct actions for them. [Unai Bolivar - [`8a0f04c`](https://github.com/eea/volto-arcgis-block/commit/8a0f04c416b0067e27f1e34c1e5cf05f7f818b48)]
23
+ - CLMS-280000 (feat): Services update mapviewer state to re upload the same url after deleting it previously now [Unai Bolivar - [`4c69979`](https://github.com/eea/volto-arcgis-block/commit/4c69979fa636601c1b2ad83e1a2d8df65bd5ed12)]
24
+ - CLMS-284630 (bug): Default active remove bug solved [Urkorue - [`5ab8937`](https://github.com/eea/volto-arcgis-block/commit/5ab8937d6907e81766c53004d916898bf265464d)]
25
+ - CLMS-280000 (feat): services load and delete from the map and menu. Need to update mapviewer state to re upload the same url after deleting it [Unai Bolivar - [`cec867a`](https://github.com/eea/volto-arcgis-block/commit/cec867a1550e841706e7ee5a1a0c282a7e3109ee)]
26
+ - CLMS-280000 (feat): Component style is correct [Unai Bolivar - [`7b2eff5`](https://github.com/eea/volto-arcgis-block/commit/7b2eff5ff2254d14689ea648a4a7cc2f74692986)]
7
27
  ### [0.1.341](https://github.com/eea/volto-arcgis-block/compare/0.1.340...0.1.341) - 6 March 2025
8
28
 
9
29
  ### [0.1.340](https://github.com/eea/volto-arcgis-block/compare/0.1.339...0.1.340) - 5 March 2025
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.341",
3
+ "version": "0.1.343",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -60,6 +60,7 @@ class MapViewer extends React.Component {
60
60
  layerLoading: false,
61
61
  layers: {},
62
62
  uploadedFile: true,
63
+ wmsServiceUrl: '',
63
64
  };
64
65
  this.activeLayersHandler = this.activeLayersHandler.bind(this);
65
66
  this.activeLayersArray = {};
@@ -72,6 +73,8 @@ class MapViewer extends React.Component {
72
73
  this.bookmarkHandler = this.bookmarkHandler.bind(this);
73
74
  this.prepackageHandler = this.prepackageHandler.bind(this);
74
75
  this.uploadFileHandler = this.uploadFileHandler.bind(this);
76
+ this.uploadFileErrorHandler = this.uploadFileErrorHandler.bind(this);
77
+ this.uploadUrlServiceHandler = this.uploadUrlServiceHandler.bind(this);
75
78
  //this.getTaxonomy = this.props.getTaxonomy.bind(this);
76
79
  }
77
80
 
@@ -97,20 +100,57 @@ class MapViewer extends React.Component {
97
100
  this.setState({ bookmarkData: newBookmarkData });
98
101
  }
99
102
 
103
+ // Function to remove circular references
104
+ removeCircularReferences(obj) {
105
+ const seen = new WeakSet();
106
+ return JSON.parse(
107
+ JSON.stringify(obj, (key, value) => {
108
+ if (typeof value === 'object' && value !== null) {
109
+ if (seen.has(value)) {
110
+ return;
111
+ }
112
+ seen.add(value);
113
+ }
114
+ return value;
115
+ }),
116
+ );
117
+ }
118
+
100
119
  activeLayersHandler(newActiveLayers) {
101
- this.activeLayers = newActiveLayers;
102
- mapStatus.activeLayers = newActiveLayers;
103
- sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
120
+ try {
121
+ const layersWithoutCircularReferences = this.removeCircularReferences(
122
+ newActiveLayers,
123
+ );
124
+ this.activeLayers = layersWithoutCircularReferences;
125
+ mapStatus.activeLayers = layersWithoutCircularReferences;
126
+ sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
127
+ } catch (error) {
128
+ //setup some sort of error message
129
+ }
104
130
  }
105
131
 
106
132
  setCenterState(centerStatus) {
107
133
  mapStatus.center = centerStatus;
108
- sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
134
+ try {
135
+ sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
136
+ } catch (e) {
137
+ if (e.name === 'QuotaExceededError') {
138
+ sessionStorage.clear();
139
+ sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
140
+ }
141
+ }
109
142
  }
110
143
 
111
144
  setZoomState(zoomStatus) {
112
145
  mapStatus.zoom = zoomStatus;
113
- sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
146
+ try {
147
+ sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
148
+ } catch (e) {
149
+ if (e.name === 'QuotaExceededError') {
150
+ sessionStorage.clear();
151
+ sessionStorage.setItem('mapStatus', JSON.stringify(mapStatus));
152
+ }
153
+ }
114
154
  }
115
155
 
116
156
  recoverState() {
@@ -129,6 +169,33 @@ class MapViewer extends React.Component {
129
169
  this.setState({ uploadedFile: message });
130
170
  }
131
171
 
172
+ uploadFileErrorHandler = (error) => {
173
+ this.setState({
174
+ showInfoPopup: true,
175
+ infoPopupType: 'uploadError',
176
+ });
177
+ setTimeout(() => {
178
+ this.setState({
179
+ showInfoPopup: false,
180
+ infoPopupType: '',
181
+ });
182
+ }, 3000);
183
+ };
184
+
185
+ uploadUrlServiceHandler = (newUrl) => {
186
+ if (newUrl && typeof newUrl === 'string') {
187
+ this.setState({ wmsServiceUrl: newUrl });
188
+ } else {
189
+ //set popup error messsage
190
+ this.setState({ wmsServiceUrl: '' });
191
+ }
192
+ };
193
+
194
+ serviceAddedHandler = () => {
195
+ // Reset wmsServiceUrl without causing a new update of the children
196
+ this.setState({ wmsServiceUrl: '' });
197
+ };
198
+
132
199
  loader() {
133
200
  return loadModules([
134
201
  'esri/WebMap',
@@ -275,6 +342,13 @@ class MapViewer extends React.Component {
275
342
  sessionStorage.clear();
276
343
  sessionStorage.setItem('toc_panel_scrolls', toc_panel_scrolls);
277
344
  }
345
+ // if (
346
+ // prevState.wmsServiceUrl !== this.state.wmsServiceUrl &&
347
+ // this.state.wmsServiceUrl === ''
348
+ // ) {
349
+ // // Reset wmsServiceUrl without causing a new update of the children
350
+ // this.setState({ wmsServiceUrl: '' });
351
+ // }
278
352
  }
279
353
 
280
354
  componentWillUnmount() {
@@ -432,6 +506,10 @@ class MapViewer extends React.Component {
432
506
  prepackageHandler={this.prepackageHandler}
433
507
  uploadedFile={this.state.uploadedFile}
434
508
  uploadFileHandler={this.uploadFileHandler}
509
+ uploadUrlServiceHandler={this.uploadUrlServiceHandler}
510
+ wmsServiceUrl={this.state.wmsServiceUrl}
511
+ onServiceAdded={this.serviceAddedHandler}
512
+ uploadFileErrorHandler={this.uploadFileErrorHandler}
435
513
  //getTaxonomy={this.getTaxonomy}
436
514
  />
437
515
  ); //call conf
@@ -459,6 +537,9 @@ class MapViewer extends React.Component {
459
537
  view={this.view}
460
538
  map={this.map}
461
539
  mapViewer={this}
540
+ wmsServiceUrl={this.state.wmsServiceUrl}
541
+ uploadUrlServiceHandler={this.uploadUrlServiceHandler}
542
+ uploadfileErrorHandler={this.uploadFileErrorHandler}
462
543
  />
463
544
  );
464
545
  }
@@ -412,6 +412,8 @@ class MenuWidget extends React.Component {
412
412
  draggedElements: [],
413
413
  popup: false,
414
414
  filterArrow: 'chevron-down',
415
+ wmsUserServiceLayers: [],
416
+ //wmsServiceUrl: this.props.wmsServiceUrl,
415
417
  };
416
418
  this.menuClass =
417
419
  'esri-icon-drag-horizontal esri-widget--button esri-widget esri-interactive';
@@ -429,6 +431,7 @@ class MenuWidget extends React.Component {
429
431
  this.getLimitScale = this.getLimitScale.bind(this);
430
432
  this.handleOpenPopup = this.handleOpenPopup.bind(this);
431
433
  this.filtersApplied = false;
434
+ this.filtersApplied = false;
432
435
  // add zoomend listener to map to show/hide zoom in message
433
436
  this.view.watch('stationary', (isStationary) => {
434
437
  let snowAndIceInSessionStorage = sessionStorage.getItem('snowAndIce');
@@ -549,6 +552,15 @@ class MenuWidget extends React.Component {
549
552
  );
550
553
  }
551
554
 
555
+ static getDerivedStateFromProps(nextProps, prevState) {
556
+ if (nextProps.wmsServiceUrl !== prevState.wmsServiceUrl) {
557
+ return {
558
+ wmsServiceUrl: nextProps.wmsServiceUrl,
559
+ };
560
+ }
561
+ return null;
562
+ }
563
+
552
564
  stringMatch(str1, str2) {
553
565
  if (!str1 || !str2) {
554
566
  return '';
@@ -982,6 +994,12 @@ class MenuWidget extends React.Component {
982
994
  this.props.bookmarkHandler(bookmarkData);
983
995
  });
984
996
  });
997
+
998
+ // Add "My Services" component to the UI
999
+ const myServicesComponent = this.createMyServicesComponent();
1000
+ const myServicesContainer = document.createElement('div');
1001
+ ReactDOM.render(myServicesComponent, myServicesContainer);
1002
+ this.container.current.appendChild(myServicesContainer.firstChild);
985
1003
  }
986
1004
 
987
1005
  setSliderTag(val) {
@@ -1135,6 +1153,10 @@ class MenuWidget extends React.Component {
1135
1153
  }
1136
1154
  }
1137
1155
  }
1156
+
1157
+ // Add "My Services" component
1158
+ components.push(this.createMyServicesComponent());
1159
+
1138
1160
  return components;
1139
1161
  }
1140
1162
 
@@ -1935,6 +1957,252 @@ class MenuWidget extends React.Component {
1935
1957
  }
1936
1958
  }
1937
1959
 
1960
+ async handleNewMapServiceLayer(viewService) {
1961
+ // Check if the URL is already in state to prevent duplicates
1962
+ if (
1963
+ this.state.wmsUserServiceLayers.some((layer) => layer.url === viewService)
1964
+ ) {
1965
+ return;
1966
+ }
1967
+
1968
+ // CREATE A TEMPORARY LAYER OBJECT TO EXTRACT DATA
1969
+ let resourceLayer;
1970
+ try {
1971
+ resourceLayer = new WMSLayer({
1972
+ url: viewService,
1973
+ });
1974
+ } catch (error) {
1975
+ // Set a popup error message in here
1976
+ this.props.uploadFileErrorHandler();
1977
+ return;
1978
+ }
1979
+
1980
+ const legendRequest =
1981
+ 'request=GetLegendGraphic&version=1.0.0&format=image/png&layer=';
1982
+ let layerId, layerObj;
1983
+
1984
+ await resourceLayer.load().then(() => {
1985
+ // EXTRACT DATA FOR NEW LAYER REQUEST
1986
+ let { featureInfoUrl, title } = resourceLayer;
1987
+ layerId = title.toUpperCase().replace(/ /g, '_');
1988
+ const constructedSublayers = resourceLayer.sublayers?.items?.map(
1989
+ (sublayer) => {
1990
+ const { index, name, title, legendUrl, featureInfoUrl } = sublayer;
1991
+ return {
1992
+ index,
1993
+ name,
1994
+ title,
1995
+ popupEnabled: true,
1996
+ queryable: true,
1997
+ visible: true,
1998
+ legendEnabled: true,
1999
+ legendUrl: legendUrl
2000
+ ? legendUrl
2001
+ : viewService + legendRequest + name,
2002
+ featureInfoUrl: featureInfoUrl ? featureInfoUrl : viewService,
2003
+ };
2004
+ },
2005
+ );
2006
+
2007
+ layerObj = {
2008
+ url: viewService,
2009
+ featureInfoFormat: 'text/html',
2010
+ featureInfoUrl: featureInfoUrl ? featureInfoUrl : viewService,
2011
+ title,
2012
+ legendEnabled: true,
2013
+ sublayers: constructedSublayers,
2014
+ ViewService: viewService,
2015
+ LayerId: layerId,
2016
+ };
2017
+ return layerObj;
2018
+ });
2019
+
2020
+ // DESTROY THE TEMPORARY LAYER OBJECT
2021
+ resourceLayer.destroy();
2022
+ resourceLayer = null; // Important: clear the reference to the old layer
2023
+
2024
+ const { LayerId } = layerObj;
2025
+
2026
+ // Check if the layer already exists in this.layers before adding
2027
+ if (!this.layers[LayerId]) {
2028
+ try {
2029
+ // Create and add the new layer
2030
+ this.layers[LayerId] = new WMSLayer(layerObj);
2031
+
2032
+ // Update state to include the new layer, which will trigger componentDidUpdate
2033
+ this.setState((prevState) => {
2034
+ return {
2035
+ wmsUserServiceLayers: [
2036
+ ...prevState.wmsUserServiceLayers,
2037
+ this.layers[LayerId],
2038
+ ],
2039
+ };
2040
+ });
2041
+
2042
+ this.props.onServiceAdded();
2043
+
2044
+ // Add the layer to the map
2045
+ const node = document.getElementById(LayerId);
2046
+ if (node) {
2047
+ node.checked = true;
2048
+ this.toggleLayer(node);
2049
+ }
2050
+ } catch (error) {
2051
+ // Set a popup error message in here
2052
+ this.props.uploadFileErrorHandler();
2053
+ return;
2054
+ }
2055
+ }
2056
+ }
2057
+
2058
+ createMyServicesComponent() {
2059
+ let dropdowns = document.querySelectorAll('.map-menu-dropdown');
2060
+ let i = dropdowns.length === 0 ? 0 : dropdowns.length - 1;
2061
+ let componentId = `component_${i}`;
2062
+ let dropdownId = `dropdown_${i}`;
2063
+
2064
+ // Create "My Services" component from the start and set its display to none
2065
+ let myServicesStyle =
2066
+ this.state.wmsUserServiceLayers.length > 0 ? {} : { display: 'none' };
2067
+ return (
2068
+ <div
2069
+ className="map-menu-dropdown"
2070
+ id={componentId}
2071
+ key="a5"
2072
+ style={myServicesStyle}
2073
+ >
2074
+ <div
2075
+ id={dropdownId}
2076
+ className="ccl-expandable__button"
2077
+ aria-expanded="false"
2078
+ onClick={this.toggleDropdownContent.bind(this)}
2079
+ onKeyDown={this.toggleDropdownContent.bind(this)}
2080
+ tabIndex="0"
2081
+ role="button"
2082
+ >
2083
+ <div className="dropdown-icon">
2084
+ <FontAwesomeIcon icon={['fas', 'caret-right']} />
2085
+ </div>
2086
+ {<span>{'My Service'}</span>}
2087
+ </div>
2088
+ <div className="map-menu-components-container" id="map-menu-services" />
2089
+ </div>
2090
+ );
2091
+ }
2092
+
2093
+ createUserServices(serviceLayers) {
2094
+ const fieldset = document.getElementById('map-menu-services');
2095
+ if (!fieldset) return;
2096
+
2097
+ // Create an array of all layer elements
2098
+ const layerElements = serviceLayers.map((layer, index) => {
2099
+ const { LayerId, title, description } = layer;
2100
+ const parentIndex = this.layers[layer.id];
2101
+ const checkboxId = LayerId;
2102
+
2103
+ return (
2104
+ <div className="map-menu-dataset-dropdown">
2105
+ <fieldset className="ccl-fieldset">
2106
+ <div className="ccl-expandable__button" aria-expanded="false">
2107
+ <div className="dropdown-icon">
2108
+ <div className="ccl-form map-dataset-checkbox">
2109
+ <div
2110
+ className="ccl-form-group map-menu-service"
2111
+ key={`service_layer_${LayerId}`}
2112
+ >
2113
+ <input
2114
+ type="checkbox"
2115
+ id={checkboxId}
2116
+ parentid={parentIndex}
2117
+ layerid={LayerId}
2118
+ name="layerCheckbox"
2119
+ value="name"
2120
+ className="ccl-checkbox ccl-required ccl-form-check-input"
2121
+ title={layer.title}
2122
+ onChange={(e) => {
2123
+ this.toggleLayer(e.target);
2124
+ }}
2125
+ />
2126
+ <label
2127
+ className="ccl-form-check-label"
2128
+ htmlFor={checkboxId}
2129
+ >
2130
+ <legend className="ccl-form-legend">
2131
+ {description ? (
2132
+ <Popup
2133
+ trigger={<span>{title}</span>}
2134
+ content={description}
2135
+ basic
2136
+ className="custom"
2137
+ style={{ transform: 'translateX(-4rem)' }}
2138
+ />
2139
+ ) : (
2140
+ <span>{title || `Layer ${index + 1}`}</span>
2141
+ )}
2142
+ </legend>
2143
+ </label>
2144
+ <span
2145
+ className="map-menu-icon map-menu-service-icon"
2146
+ onClick={() => this.deleteServiceLayer(LayerId)}
2147
+ onKeyDown={() => this.deleteServiceLayer(LayerId)}
2148
+ tabIndex="0"
2149
+ role="button"
2150
+ >
2151
+ <FontAwesomeIcon icon={['fas', 'trash']} />
2152
+ </span>
2153
+ </div>
2154
+ </div>
2155
+ </div>
2156
+ </div>
2157
+ </fieldset>
2158
+ </div>
2159
+ );
2160
+ });
2161
+
2162
+ // Render all layers at once to avoid overwriting previous layers
2163
+ ReactDOM.render(layerElements, fieldset);
2164
+ }
2165
+
2166
+ deleteServiceLayer(elemId) {
2167
+ // Remove the layer from the map
2168
+ const node = document.getElementById(elemId);
2169
+ if (node) {
2170
+ node.checked = false;
2171
+ this.toggleLayer(node);
2172
+ }
2173
+
2174
+ // Delete from layers object
2175
+ if (this.layers[elemId]) delete this.layers[elemId];
2176
+
2177
+ // Remove from ArcGIS map
2178
+ let removeLayer = this.props.map.findLayerById(elemId) || null;
2179
+ if (removeLayer) {
2180
+ removeLayer.clear();
2181
+ removeLayer.destroy();
2182
+ this.props.map.remove(removeLayer);
2183
+ removeLayer = null;
2184
+ }
2185
+
2186
+ // Update state to trigger componentDidUpdate
2187
+ this.setState((prevState) => {
2188
+ const layerExists = prevState.wmsUserServiceLayers.some(
2189
+ (layer) => layer.LayerId === elemId,
2190
+ );
2191
+
2192
+ if (layerExists) {
2193
+ const newWmsUserServiceLayers = prevState.wmsUserServiceLayers.filter(
2194
+ (layer) => layer.LayerId !== elemId,
2195
+ );
2196
+ return {
2197
+ wmsUserServiceLayers: newWmsUserServiceLayers,
2198
+ };
2199
+ }
2200
+
2201
+ // If layer doesn't exist, return unchanged state to avoid issues
2202
+ return null;
2203
+ });
2204
+ }
2205
+
1938
2206
  /**
1939
2207
  * Method to show/hide a layer. Update checkboxes from dataset and products
1940
2208
  * @param {*} elem Is the checkbox
@@ -2140,17 +2408,23 @@ class MenuWidget extends React.Component {
2140
2408
  }
2141
2409
 
2142
2410
  async toggleLayer(elem) {
2143
- if (elem.checked) {
2411
+ const userService =
2412
+ this.state.wmsUserServiceLayers.find(
2413
+ (layer) => layer.LayerId === elem.id,
2414
+ ) || null;
2415
+ if (elem.checked && !userService) {
2144
2416
  this.findCheckedDatasetNoServiceToVisualize(elem);
2145
2417
  }
2146
2418
  if (this.layers[elem.id] === undefined) return;
2147
2419
  if (!this.visibleLayers) this.visibleLayers = {};
2148
2420
  if (!this.timeLayers) this.timeLayers = {};
2149
- let parentId = elem.getAttribute('parentid');
2150
- let productContainerId = document
2151
- .getElementById(parentId)
2152
- .closest('.map-menu-product-dropdown')
2153
- .getAttribute('productid');
2421
+ let parentId = !userService ? elem.getAttribute('parentid') : null;
2422
+ let productContainerId = !userService
2423
+ ? document
2424
+ .getElementById(parentId)
2425
+ .closest('.map-menu-product-dropdown')
2426
+ .getAttribute('productid')
2427
+ : null;
2154
2428
 
2155
2429
  let group = this.getGroup(elem);
2156
2430
  if (elem.checked) {
@@ -2158,7 +2432,7 @@ class MenuWidget extends React.Component {
2158
2432
  if (
2159
2433
  this.props.download ||
2160
2434
  this.location.search.includes('product=') ||
2161
- this.location.search.includes('dataset=')
2435
+ (this.location.search.includes('dataset=') && !userService)
2162
2436
  ) {
2163
2437
  if (
2164
2438
  this.extentInitiated === false &&
@@ -2172,7 +2446,8 @@ class MenuWidget extends React.Component {
2172
2446
  }
2173
2447
  if (
2174
2448
  (elem.id.includes('all_lcc') || elem.id.includes('all_present')) &&
2175
- (this.layers['lc_filter'] || this.layers['lcc_filter'])
2449
+ (this.layers['lc_filter'] || this.layers['lcc_filter']) &&
2450
+ !userService
2176
2451
  ) {
2177
2452
  let bookmarkHotspotFilter = localStorage.getItem(
2178
2453
  'bookmarkHotspotFilter',
@@ -2249,14 +2524,14 @@ class MenuWidget extends React.Component {
2249
2524
  if (nuts) {
2250
2525
  this.map.reorder(nuts, this.map.layers.items.length + 1);
2251
2526
  }
2252
- this.checkForHotspots(elem, productContainerId);
2527
+ if (!userService) this.checkForHotspots(elem, productContainerId);
2253
2528
  } else {
2254
2529
  sessionStorage.removeItem('downloadButtonClicked');
2255
2530
  sessionStorage.removeItem('timeSliderTag');
2256
2531
  this.deleteCheckedLayer(elem.id);
2257
2532
  this.layers[elem.id].opacity = 1;
2258
2533
  this.layers[elem.id].visible = false;
2259
- this.deleteFilteredLayer(elem.id);
2534
+ if (!userService) this.deleteFilteredLayer(elem.id);
2260
2535
  let mapLayer = this.map.findLayerById(elem.id);
2261
2536
  if (mapLayer) {
2262
2537
  if (mapLayer.type && mapLayer.type !== 'base-tile') mapLayer.clear();
@@ -2267,7 +2542,7 @@ class MenuWidget extends React.Component {
2267
2542
  delete this.visibleLayers[elem.id];
2268
2543
  delete this.timeLayers[elem.id];
2269
2544
  }
2270
- this.updateCheckDataset(parentId);
2545
+ if (!userService) this.updateCheckDataset(parentId);
2271
2546
  this.layersReorder();
2272
2547
  this.checkInfoWidget();
2273
2548
  // toggle custom legend for WMTS and TMS
@@ -2482,7 +2757,6 @@ class MenuWidget extends React.Component {
2482
2757
  for (var i in this.activeLayersJSON) {
2483
2758
  activeLayersArray.push(this.activeLayersJSON[i]);
2484
2759
  }
2485
-
2486
2760
  if (!activeLayersArray.length) {
2487
2761
  messageLayers && (messageLayers.style.display = 'block');
2488
2762
  } else messageLayers && (messageLayers.style.display = 'none');
@@ -2895,11 +3169,19 @@ class MenuWidget extends React.Component {
2895
3169
  }
2896
3170
 
2897
3171
  async FullExtentDataset(elem) {
3172
+ const serviceLayer = this.state.wmsUserServiceLayers.find(
3173
+ (layer) => layer.LayerId === elem.id,
3174
+ );
3175
+
3176
+ if (!serviceLayer) {
3177
+ this.findCheckedDataset(elem);
3178
+ } else {
3179
+ this.url = serviceLayer.ViewService;
3180
+ }
2898
3181
  let BBoxes = {};
2899
- this.findCheckedDataset(elem);
2900
3182
  if (this.url?.toLowerCase().endsWith('mapserver')) {
2901
3183
  BBoxes = await this.parseBBOXMAPSERVER(this.layers[elem.id]);
2902
- } else if (this.url?.toLowerCase().includes('wms')) {
3184
+ } else if (this.url?.toLowerCase().includes('wms') || serviceLayer) {
2903
3185
  await this.getCapabilities(this.url, 'wms');
2904
3186
  BBoxes = this.parseBBOXWMS(this.xml);
2905
3187
  } else if (this.url?.toLowerCase().includes('wmts')) {
@@ -2928,7 +3210,16 @@ class MenuWidget extends React.Component {
2928
3210
  }
2929
3211
 
2930
3212
  async fullExtent(elem) {
2931
- this.findCheckedDataset(elem);
3213
+ const serviceLayer = this.state.wmsUserServiceLayers.find(
3214
+ (layer) => layer.LayerId === elem.id,
3215
+ );
3216
+
3217
+ if (!serviceLayer) {
3218
+ this.findCheckedDataset(elem);
3219
+ } else {
3220
+ this.productId = null;
3221
+ this.url = serviceLayer.ViewService;
3222
+ }
2932
3223
  let BBoxes = {};
2933
3224
  let firstLayer;
2934
3225
  let landCoverAndLandUseMapping = document.querySelector('#component_0');
@@ -2945,14 +3236,14 @@ class MenuWidget extends React.Component {
2945
3236
  });
2946
3237
  }
2947
3238
 
2948
- if (this.productId.includes('333e4100b79045daa0ff16466ac83b7f')) {
3239
+ if (this.productId?.includes('333e4100b79045daa0ff16466ac83b7f')) {
2949
3240
  //global dynamic landCover
2950
3241
  this.findDatasetBoundingBox(elem);
2951
3242
 
2952
3243
  BBoxes = this.parseBBOXJSON(this.dataBBox);
2953
3244
  } else if (
2954
- this.productId.includes('fe8209dffe13454891cea05998c8e456') || // Low Resolution Vegetation Parameters
2955
- this.productId.includes('8914fde2241a4035818af8f0264fd55e') // Water Parameters
3245
+ this.productId?.includes('fe8209dffe13454891cea05998c8e456') || // Low Resolution Vegetation Parameters
3246
+ this.productId?.includes('8914fde2241a4035818af8f0264fd55e') // Water Parameters
2956
3247
  ) {
2957
3248
  if (
2958
3249
  this.layers[elem.id].fullExtents &&
@@ -2971,7 +3262,7 @@ class MenuWidget extends React.Component {
2971
3262
  }
2972
3263
  } else if (this.url?.toLowerCase().endsWith('mapserver')) {
2973
3264
  BBoxes = await this.parseBBOXMAPSERVER(this.layers[elem.id]);
2974
- } else if (this.url?.toLowerCase().includes('wms')) {
3265
+ } else if (this.url?.toLowerCase().includes('wms') || serviceLayer) {
2975
3266
  await this.getCapabilities(this.url, 'wms');
2976
3267
  BBoxes = this.parseBBOXWMS(this.xml);
2977
3268
  } else if (this.url?.toLowerCase().includes('wmts')) {
@@ -2986,19 +3277,19 @@ class MenuWidget extends React.Component {
2986
3277
  ) {
2987
3278
  if (
2988
3279
  this.extentInitiated === false &&
2989
- !this.productId.includes('333e4100b79045daa0ff16466ac83b7f') &&
3280
+ !this.productId?.includes('333e4100b79045daa0ff16466ac83b7f') &&
2990
3281
  this.location.search !== ''
2991
3282
  ) {
2992
3283
  firstLayer = BBoxes.dataset;
2993
3284
  }
2994
- if (productIds.includes(this.productId)) {
3285
+ if (productIds?.includes(this.productId)) {
2995
3286
  // Your code here for when productIds includes this.productId
2996
3287
  let str = elem.parentNode.outerHTML;
2997
3288
  let match = str.match(/layerid="([a-zA-Z0-9_:-]+)"/);
2998
3289
  let layerid = match ? match[1] : null;
2999
3290
  if (layerid === null || layerid === undefined) return;
3000
3291
  if (
3001
- this.productId.includes('130299ac96e54c30a12edd575eff80f7') &&
3292
+ this.productId?.includes('130299ac96e54c30a12edd575eff80f7') &&
3002
3293
  layerid.length <= 2
3003
3294
  ) {
3004
3295
  //let match = str.match(/layerid="(\d+)"/);
@@ -3064,7 +3355,7 @@ class MenuWidget extends React.Component {
3064
3355
  } else if (layerid.length > 2) {
3065
3356
  firstLayer = BBoxes[layerid];
3066
3357
  } else if (
3067
- this.productId.includes('333e4100b79045daa0ff16466ac83b7f')
3358
+ this.productId?.includes('333e4100b79045daa0ff16466ac83b7f')
3068
3359
  ) {
3069
3360
  firstLayer = BBoxes[0];
3070
3361
  }
@@ -3075,6 +3366,9 @@ class MenuWidget extends React.Component {
3075
3366
  elem.id.includes('protected_areas')
3076
3367
  ) {
3077
3368
  firstLayer = BBoxes['all_present_lc_a_pol'];
3369
+ } else if (serviceLayer) {
3370
+ // Full extent treatment for service layers
3371
+ firstLayer = BBoxes['dataset'];
3078
3372
  } else {
3079
3373
  firstLayer = BBoxes[elem.attributes.layerid.value];
3080
3374
  }
@@ -3613,8 +3907,15 @@ class MenuWidget extends React.Component {
3613
3907
  }, 100);
3614
3908
  }
3615
3909
 
3910
+ waitForDataFill(obj) {
3911
+ while (obj.length === 0) {
3912
+ new Promise((resolve) => setTimeout(resolve, 100)); // wait for 100ms
3913
+ }
3914
+ return obj;
3915
+ }
3916
+
3616
3917
  setLegendOpacity() {
3617
- const collection = document.getElementsByClassName('esri-legend__symbol');
3918
+ let collection = document.getElementsByClassName('esri-legend__symbol');
3618
3919
 
3619
3920
  Array.prototype.forEach.call(collection, function (element) {
3620
3921
  let img = {};
@@ -3692,7 +3993,15 @@ class MenuWidget extends React.Component {
3692
3993
  * @param {*} id id from elem
3693
3994
  */
3694
3995
  eyeLayer(elem) {
3695
- this.findCheckedDataset(elem);
3996
+ // Check if this is a user service layer
3997
+ const isUserServiceLayer = this.state.wmsUserServiceLayers.some(
3998
+ (layer) => layer.LayerId === elem.id,
3999
+ );
4000
+
4001
+ // Only call findCheckedDataset for non-service layers (this method looks for parent datasets)
4002
+ if (!isUserServiceLayer) {
4003
+ this.findCheckedDataset(elem);
4004
+ }
3696
4005
  if (
3697
4006
  this.visibleLayers[elem.id] &&
3698
4007
  this.visibleLayers[elem.id][1] === 'eye'
@@ -3738,7 +4047,11 @@ class MenuWidget extends React.Component {
3738
4047
  this.visibleLayers[elem.id] = ['fas', 'eye'];
3739
4048
  }
3740
4049
 
3741
- if (this.productId.includes('333e4100b79045daa0ff16466ac83b7f')) {
4050
+ if (
4051
+ !isUserServiceLayer &&
4052
+ this.productId &&
4053
+ this.productId.includes('333e4100b79045daa0ff16466ac83b7f')
4054
+ ) {
3742
4055
  // global dynamic land cover
3743
4056
  if (this.visibleLayers[elem.id][1] === 'eye-slash') {
3744
4057
  this.map.findLayerById(elem.id).visible = false;
@@ -3764,9 +4077,44 @@ class MenuWidget extends React.Component {
3764
4077
  this.setState({});
3765
4078
  }
3766
4079
 
3767
- componentDidUpdate(prevState, prevProps) {
4080
+ componentDidUpdate(prevProps, prevState) {
3768
4081
  if (this.props.download) return;
3769
4082
 
4083
+ if (prevProps.wmsServiceUrl !== this.props.wmsServiceUrl) {
4084
+ const { wmsServiceUrl } = this.props;
4085
+ if (
4086
+ wmsServiceUrl &&
4087
+ typeof wmsServiceUrl === 'string' &&
4088
+ wmsServiceUrl !== ''
4089
+ ) {
4090
+ this.handleNewMapServiceLayer(wmsServiceUrl);
4091
+ }
4092
+ }
4093
+
4094
+ if (prevState.wmsUserServiceLayers !== this.state.wmsUserServiceLayers) {
4095
+ this.createUserServices(this.state.wmsUserServiceLayers);
4096
+ }
4097
+ if (
4098
+ this.state.wmsUserServiceLayers.length > 0 &&
4099
+ prevState.wmsUserServiceLayers.length === 0
4100
+ ) {
4101
+ // Close other tabs and open "My Services" tab
4102
+ let dropdownsMapMenu = document.querySelectorAll('.map-menu-dropdown');
4103
+ let i = dropdownsMapMenu.length - 1;
4104
+ // let j = 0;
4105
+ let dropdownId = 'dropdown_' + i;
4106
+ // let myServicesId = 'component_' + i;
4107
+ // let mapMenuServiceDropdownId = 'product_' + i + '_' + j;
4108
+ dropdownsMapMenu.forEach((dropdown) => {
4109
+ if (dropdown.id !== dropdownId) {
4110
+ dropdown
4111
+ .querySelector('.ccl-expandable__button')
4112
+ .setAttribute('aria-expanded', 'false');
4113
+ }
4114
+ });
4115
+ document.getElementById(dropdownId).setAttribute('aria-expanded', 'true');
4116
+ }
4117
+
3770
4118
  if (sessionStorage.getItem('snowAndIce') === 'true') {
3771
4119
  //grab all checkedLayers from sessionstorage store them in checkedLayeers
3772
4120
  let checkedLayers = JSON.parse(sessionStorage.getItem('checkedLayers'));
@@ -3848,6 +4196,12 @@ class MenuWidget extends React.Component {
3848
4196
  let layerId = layer.getAttribute('layer-id');
3849
4197
  let elem = document.getElementById(layerId);
3850
4198
  this.deleteCrossEvent(elem);
4199
+ if (
4200
+ this.state.wmsUserServiceLayers ||
4201
+ this.state.wmsUserServiceLayers.length > 0
4202
+ ) {
4203
+ this.setState({ wmsUserServiceLayers: [] });
4204
+ }
3851
4205
  });
3852
4206
  }
3853
4207
 
@@ -26,6 +26,7 @@ class UploadWidget extends React.Component {
26
26
  'esri-icon-sketch-rectangle esri-widget--button esri-widget esri-interactive';
27
27
  this.mapviewer_config = this.props.mapviewer_config;
28
28
  this.fileInput = createRef();
29
+ this.uploadUrlServiceHandler = this.props.uploadUrlServiceHandler;
29
30
  }
30
31
 
31
32
  loader() {
@@ -107,15 +108,15 @@ class UploadWidget extends React.Component {
107
108
  clearWidget() {
108
109
  window.document.querySelector('.pan-container').style.display = 'none';
109
110
  this.props.mapViewer.view.popup.close();
110
- const { wmsLayer } = this.state;
111
- if (wmsLayer) {
112
- this.props.view.map.remove(wmsLayer);
113
- // this.props.view.graphics.removeAll();
114
- }
115
- this.setState({
116
- wmsLayer: null,
117
- wmsServiceUrl: '',
118
- });
111
+ //const { wmsLayer } = this.state;
112
+ //if (wmsLayer) {
113
+ // this.props.view.map.remove(wmsLayer);
114
+ // // this.props.view.graphics.removeAll();
115
+ //}
116
+ //this.setState({
117
+ // wmsLayer: null,
118
+ // wmsServiceUrl: '',
119
+ //});
119
120
 
120
121
  document.querySelector('.esri-attribution__powered-by').style.display =
121
122
  'none';
@@ -125,33 +126,32 @@ class UploadWidget extends React.Component {
125
126
  this.setState({ wmsServiceUrl: event.target.value });
126
127
  };
127
128
 
128
- handleUploadService = async () => {
129
- const { wmsServiceUrl, wmsLayer } = this.state;
130
- if (wmsLayer) {
131
- this.props.view.map.remove(wmsLayer);
132
- }
129
+ handleUploadService = () => {
130
+ const { wmsServiceUrl } = this.state;
133
131
 
134
- try {
135
- const newWmsLayer = new WMSLayer({
136
- url: wmsServiceUrl,
137
- });
138
-
139
- await newWmsLayer.load();
140
- this.props.view.map.add(newWmsLayer);
132
+ if (
133
+ wmsServiceUrl &&
134
+ wmsServiceUrl.trim() !== '' &&
135
+ wmsServiceUrl.toLowerCase().includes('wms')
136
+ ) {
137
+ this.uploadUrlServiceHandler(wmsServiceUrl);
141
138
  this.setState({
142
- wmsLayer: newWmsLayer,
143
- showInfoPopup: false,
144
- infoPopupType: '',
145
139
  wmsServiceUrl: '',
146
140
  });
147
- } catch (error) {
141
+ } else {
148
142
  this.setState({
149
143
  showInfoPopup: true,
150
144
  infoPopupType: 'uploadError',
151
- wmsServiceUrl: '',
152
145
  });
146
+ setTimeout(() => {
147
+ this.setState({
148
+ showInfoPopup: false,
149
+ infoPopupType: '',
150
+ });
151
+ }, 3000);
153
152
  }
154
153
  };
154
+
155
155
  /**
156
156
  * This method is executed after the render method is executed
157
157
  */
@@ -160,6 +160,14 @@ class UploadWidget extends React.Component {
160
160
  this.props.view.when(() => {
161
161
  this.container.current !== null &&
162
162
  this.props.view.ui.add(this.container.current, 'top-right');
163
+ //load an empty wms layer to use the variable
164
+ const wmsLayer = new WMSLayer({
165
+ url: '',
166
+ title: 'WMS Layer',
167
+ });
168
+ this.setState({
169
+ wmsLayer: wmsLayer,
170
+ });
163
171
  });
164
172
  }
165
173
 
@@ -773,6 +773,10 @@ div.upload-container.esri-component
773
773
  text-decoration: none;
774
774
  }
775
775
 
776
+ .land .map-menu-icon.map-menu-service-icon {
777
+ margin-left: 1.75rem;
778
+ }
779
+
776
780
  .map-menu-dropdown i:hover {
777
781
  color: black;
778
782
  }