@eeacms/volto-arcgis-block 0.1.352 → 0.1.354

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,14 @@ 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.354](https://github.com/eea/volto-arcgis-block/compare/0.1.353...0.1.354) - 24 April 2025
8
+
9
+ ### [0.1.353](https://github.com/eea/volto-arcgis-block/compare/0.1.352...0.1.353) - 15 April 2025
10
+
11
+ #### :hammer_and_wrench: Others
12
+
13
+ - CLMS-286928 (bug): linter error fixed. [Unai Bolivar - [`d7afc23`](https://github.com/eea/volto-arcgis-block/commit/d7afc238ad25575b7e4857bb8ef6f693b3f1f736)]
14
+ - CLMS-286928 (bug): Fixed issues pertaining user upload wms services for logged in users [Unai Bolivar - [`49eaf4c`](https://github.com/eea/volto-arcgis-block/commit/49eaf4cfa94fdd406709cd50b50aafd18a13f25e)]
7
15
  ### [0.1.352](https://github.com/eea/volto-arcgis-block/compare/0.1.351...0.1.352) - 11 April 2025
8
16
 
9
17
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.352",
3
+ "version": "0.1.354",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -77,6 +77,7 @@ class MapViewer extends React.Component {
77
77
  this.uploadFileErrorHandler = this.uploadFileErrorHandler.bind(this);
78
78
  this.uploadUrlServiceHandler = this.uploadUrlServiceHandler.bind(this);
79
79
  this.getTaxonomy = this.props.getTaxonomy.bind(this);
80
+ this.tax = null;
80
81
  }
81
82
 
82
83
  mapLayersHandler(newLayers) {
@@ -232,6 +233,7 @@ class MapViewer extends React.Component {
232
233
  async componentDidMount() {
233
234
  loadCss();
234
235
  await this.loader();
236
+ this.tax = await this.getTaxonomy('collective.taxonomy.family');
235
237
  this.positronCompositeBasemap = new Basemap({
236
238
  title: 'Positron composite',
237
239
  thumbnailUrl: this.cfgUrls.positronCompositeThumbnail,
@@ -650,6 +652,7 @@ export const CheckUserID = ({ reference }) => {
650
652
  uploadFileErrorHandler={reference.uploadFileErrorHandler}
651
653
  userID={user_id}
652
654
  getTaxonomy={reference.getTaxonomy}
655
+ tax={reference.tax}
653
656
  />
654
657
  </>
655
658
  )}
@@ -502,6 +502,7 @@ class MenuWidget extends React.Component {
502
502
 
503
503
  this.activeLayersHandler = this.props.activeLayersHandler;
504
504
  this.getTaxonomy = this.props.getTaxonomy;
505
+ this.tax = this.props.tax;
505
506
  }
506
507
 
507
508
  loader() {
@@ -1251,6 +1252,7 @@ class MenuWidget extends React.Component {
1251
1252
  metodProcessProduct(product, prodIndex, inheritedIndex) {
1252
1253
  var dataset_def = [];
1253
1254
  var datasets = [];
1255
+ var families = [];
1254
1256
  var index = 0;
1255
1257
  var inheritedIndexProduct = inheritedIndex + '_' + prodIndex;
1256
1258
  var checkProduct = 'map_product_' + inheritedIndexProduct;
@@ -1260,33 +1262,62 @@ class MenuWidget extends React.Component {
1260
1262
  : product.ProductDescription;
1261
1263
 
1262
1264
  if (product.Datasets && Array.isArray(product.Datasets)) {
1263
- for (var i in product.Datasets) {
1264
- if (this.filtersApplied) {
1265
- dataset_def = document
1266
- .querySelector('#' + checkProduct)
1267
- ?.getAttribute('defcheck');
1268
- } else if (
1269
- product.Datasets[i] &&
1270
- product.Datasets[i].Default_active === true
1271
- ) {
1272
- var idDataset = 'map_dataset_' + inheritedIndexProduct + '_' + index;
1273
- dataset_def.push(idDataset);
1265
+ let familiesMap = new Map();
1266
+ familiesMap['noFamily'] = [];
1267
+ for (let index = 0; index < product.Datasets.length; index++) {
1268
+ if (familiesMap[product.Datasets[index].FamilyTitle]) {
1269
+ familiesMap[product.Datasets[index].FamilyTitle].push(
1270
+ product.Datasets[index],
1271
+ );
1272
+ } else if (product.Datasets[index].FamilyTitle) {
1273
+ familiesMap[product.Datasets[index].FamilyTitle] = [
1274
+ product.Datasets[index],
1275
+ ];
1276
+ } else {
1277
+ familiesMap['noFamily'].push(product.Datasets[index]);
1274
1278
  }
1279
+ }
1280
+ for (var j in familiesMap) {
1281
+ if (j === 'noFamily') {
1282
+ for (var i in familiesMap[j]) {
1283
+ if (this.filtersApplied) {
1284
+ dataset_def = document
1285
+ .querySelector('#' + checkProduct)
1286
+ ?.getAttribute('defcheck');
1287
+ } else if (
1288
+ product.Datasets[i] &&
1289
+ product.Datasets[i].Default_active === true
1290
+ ) {
1291
+ var idDataset =
1292
+ 'map_dataset_' + inheritedIndexProduct + '_' + index;
1293
+ dataset_def.push(idDataset);
1294
+ }
1275
1295
 
1276
- // CLMS-1545
1277
- // if (!product.Datasets[i].MarkAsDownloadableNoServiceToVisualize) {
1278
- if (product.Datasets[i]) {
1279
- datasets.push(
1280
- this.metodProcessDataset(
1281
- product.Datasets[i],
1282
- index,
1296
+ // CLMS-1545
1297
+ // if (!product.Datasets[i].MarkAsDownloadableNoServiceToVisualize) {
1298
+ if (product.Datasets[i]) {
1299
+ datasets.push(
1300
+ this.metodProcessDataset(
1301
+ product.Datasets[i],
1302
+ index,
1303
+ inheritedIndexProduct,
1304
+ checkProduct,
1305
+ ),
1306
+ );
1307
+ index++;
1308
+ }
1309
+ // }
1310
+ }
1311
+ } else {
1312
+ families.push(
1313
+ this.metodProcessFamily(
1314
+ familiesMap[j],
1315
+ j,
1283
1316
  inheritedIndexProduct,
1284
1317
  checkProduct,
1285
1318
  ),
1286
1319
  );
1287
- index++;
1288
1320
  }
1289
- // }
1290
1321
  }
1291
1322
  }
1292
1323
 
@@ -1361,6 +1392,131 @@ class MenuWidget extends React.Component {
1361
1392
  <div
1362
1393
  className="ccl-form map-menu-products-container"
1363
1394
  id={'datasets_container_' + inheritedIndexProduct}
1395
+ >
1396
+ {families}
1397
+ {datasets}
1398
+ </div>
1399
+ </fieldset>
1400
+ </div>
1401
+ );
1402
+ }
1403
+
1404
+ metodProcessFamily(family, familyTitle, inheritedIndex, checkProduct) {
1405
+ var dataset_def = [];
1406
+ var datasets = [];
1407
+ var index = 0;
1408
+ var familyId = familyTitle.replace(/\s+/g, '');
1409
+ var inheritedIndexProduct = inheritedIndex + '_' + familyId;
1410
+ checkProduct = 'map_product_' + inheritedIndexProduct;
1411
+ var familyTitleName = '';
1412
+ this.tax.tree.forEach((element) => {
1413
+ element.children.forEach((element) => {
1414
+ if (element.key === familyTitle) {
1415
+ familyTitleName = element.title;
1416
+ }
1417
+ });
1418
+ });
1419
+ if (family && Array.isArray(family)) {
1420
+ for (var i in family) {
1421
+ // if (this.filtersApplied) {
1422
+ // dataset_def = document
1423
+ // .querySelector('#' + checkProduct)
1424
+ // ?.getAttribute('defcheck');
1425
+ // } else if (
1426
+ // product.Datasets[i] &&
1427
+ // product.Datasets[i].Default_active === true
1428
+ // ) {
1429
+ // var idDataset = 'map_dataset_' + inheritedIndexProduct + '_' + index;
1430
+ // dataset_def.push(idDataset);
1431
+ // }
1432
+
1433
+ // CLMS-1545
1434
+ // if (!product.Datasets[i].MarkAsDownloadableNoServiceToVisualize) {
1435
+ if (family[i]) {
1436
+ datasets.push(
1437
+ this.metodProcessDataset(
1438
+ family[i],
1439
+ index,
1440
+ inheritedIndexProduct,
1441
+ checkProduct,
1442
+ ),
1443
+ );
1444
+ index++;
1445
+ }
1446
+ // }
1447
+ }
1448
+ }
1449
+
1450
+ // Empty vector, add the first dataset
1451
+ if (!dataset_def.length) {
1452
+ var idDatasetB = 'map_dataset_' + inheritedIndexProduct + '_0';
1453
+ dataset_def.push(idDatasetB);
1454
+ }
1455
+ let style = this.props.download ? { display: 'none' } : {};
1456
+
1457
+ return (
1458
+ <div
1459
+ className="map-menu-family-dropdown"
1460
+ id={'product_' + inheritedIndexProduct}
1461
+ productid={familyId}
1462
+ key={'a' + familyId}
1463
+ >
1464
+ <fieldset className="ccl-fieldset" key={'b' + familyId}>
1465
+ <div
1466
+ id={'dropdown_' + inheritedIndexProduct}
1467
+ className="ccl-expandable__button"
1468
+ aria-expanded="false"
1469
+ key={'c' + familyId}
1470
+ onClick={this.toggleDropdownContent.bind(this)}
1471
+ onKeyDown={this.toggleDropdownContent.bind(this)}
1472
+ tabIndex="0"
1473
+ role="button"
1474
+ style={style}
1475
+ >
1476
+ <div className="dropdown-icon">
1477
+ <FontAwesomeIcon icon={['fas', 'caret-right']} />
1478
+ </div>
1479
+ <div className="ccl-form map-product-checkbox" key={'d' + familyId}>
1480
+ <div className="ccl-form-group" key={'e' + familyId}>
1481
+ <input
1482
+ type="checkbox"
1483
+ id={checkProduct}
1484
+ name=""
1485
+ value="name"
1486
+ className="ccl-checkbox ccl-required ccl-form-check-input"
1487
+ key={'h' + familyId}
1488
+ defcheck={dataset_def}
1489
+ onChange={(e) =>
1490
+ this.toggleProduct(e.target.checked, checkProduct, e)
1491
+ }
1492
+ ></input>
1493
+ <label
1494
+ className="ccl-form-check-label"
1495
+ htmlFor={checkProduct}
1496
+ key={'f' + familyId}
1497
+ >
1498
+ <legend className="ccl-form-legend">
1499
+ {
1500
+ /* {description ? (
1501
+ <Popup
1502
+ trigger={<span>{familyTitle}</span>}
1503
+ content={description}
1504
+ basic
1505
+ className="custom"
1506
+ style={{ transform: 'translateX(-4rem)' }}
1507
+ />
1508
+ ) : (*/
1509
+ <span>{familyTitleName}</span>
1510
+ /*)} */
1511
+ }
1512
+ </legend>
1513
+ </label>
1514
+ </div>
1515
+ </div>
1516
+ </div>
1517
+ <div
1518
+ className="ccl-form map-menu-family-container"
1519
+ id={'family_container_' + inheritedIndexProduct}
1364
1520
  >
1365
1521
  {datasets}
1366
1522
  </div>
@@ -1532,7 +1688,11 @@ class MenuWidget extends React.Component {
1532
1688
 
1533
1689
  return (
1534
1690
  <div
1535
- className="map-menu-dataset-dropdown"
1691
+ className={
1692
+ dataset.FamilyTitle
1693
+ ? 'map-menu-family-dataset-dropdown'
1694
+ : 'map-menu-dataset-dropdown'
1695
+ }
1536
1696
  id={'dataset_' + inheritedIndexDataset}
1537
1697
  datasetid={dataset.DatasetId}
1538
1698
  key={'a' + datIndex}
@@ -2035,15 +2195,25 @@ class MenuWidget extends React.Component {
2035
2195
  layerId = title.toUpperCase().replace(/ /g, '_');
2036
2196
  const constructedSublayers = resourceLayer.sublayers?.items?.map(
2037
2197
  (sublayer) => {
2038
- const { index, name, title, legendUrl, featureInfoUrl } = sublayer;
2198
+ const {
2199
+ index,
2200
+ name,
2201
+ title,
2202
+ legendUrl,
2203
+ featureInfoUrl,
2204
+ queryable,
2205
+ popupEnabled,
2206
+ visible,
2207
+ legendEnabled,
2208
+ } = sublayer;
2039
2209
  return {
2040
2210
  index,
2041
2211
  name,
2042
2212
  title,
2043
- popupEnabled: true,
2044
- queryable: true,
2045
- visible: true,
2046
- legendEnabled: true,
2213
+ popupEnabled,
2214
+ queryable,
2215
+ visible,
2216
+ legendEnabled,
2047
2217
  legendUrl: legendUrl
2048
2218
  ? legendUrl
2049
2219
  : viewService + legendRequest + name,
@@ -2202,7 +2372,9 @@ class MenuWidget extends React.Component {
2202
2372
  const newWmsUserServiceLayers = prevState.wmsUserServiceLayers.filter(
2203
2373
  (layer) => layer.LayerId !== elemId,
2204
2374
  );
2205
- this.saveUserServicesToStorage(newWmsUserServiceLayers);
2375
+ if (this.userID && this.userID !== null) {
2376
+ this.saveUserServicesToStorage(newWmsUserServiceLayers);
2377
+ }
2206
2378
  return { wmsUserServiceLayers: newWmsUserServiceLayers };
2207
2379
  }
2208
2380
  return null;
@@ -2214,6 +2386,11 @@ class MenuWidget extends React.Component {
2214
2386
 
2215
2387
  try {
2216
2388
  const layersToSave = layers.map((layer) => {
2389
+ // First, see if this layer exists in previous saved services to retain checked state
2390
+ const checkedLayers =
2391
+ JSON.parse(sessionStorage.getItem('checkedLayers')) || [];
2392
+ const isChecked = checkedLayers.includes(layer.LayerId);
2393
+
2217
2394
  // Create a simplified object for storage
2218
2395
  return {
2219
2396
  url: layer.url,
@@ -2234,6 +2411,9 @@ class MenuWidget extends React.Component {
2234
2411
  })),
2235
2412
  ViewService: layer.ViewService,
2236
2413
  LayerId: layer.LayerId,
2414
+ visibility: layer.visible !== false,
2415
+ opacity: layer.opacity || 1,
2416
+ checked: isChecked || layer.checked || false,
2237
2417
  };
2238
2418
  });
2239
2419
 
@@ -2246,6 +2426,7 @@ class MenuWidget extends React.Component {
2246
2426
 
2247
2427
  async loadUserServicesFromStorage() {
2248
2428
  if (this.userID != null) {
2429
+ sessionStorage.clear();
2249
2430
  try {
2250
2431
  const savedServices = JSON.parse(
2251
2432
  localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
@@ -2258,6 +2439,20 @@ class MenuWidget extends React.Component {
2258
2439
  try {
2259
2440
  // Create a new WMSLayer with the saved properties
2260
2441
  const newLayer = new WMSLayer(serviceData);
2442
+
2443
+ // Set visibility property based on saved value
2444
+ newLayer.visible = serviceData.visibility !== false;
2445
+
2446
+ // Remember the original checked state and visibility
2447
+ newLayer.checked = serviceData.checked;
2448
+
2449
+ // Initialize visibleLayers for this layer
2450
+ if (!this.visibleLayers) this.visibleLayers = {};
2451
+ this.visibleLayers[serviceData.LayerId] =
2452
+ serviceData.visibility !== false
2453
+ ? ['fas', 'eye']
2454
+ : ['fas', 'eye-slash'];
2455
+
2261
2456
  // Add to this.layers
2262
2457
  this.layers[serviceData.LayerId] = newLayer;
2263
2458
  return newLayer;
@@ -2271,27 +2466,87 @@ class MenuWidget extends React.Component {
2271
2466
  const validLayers = recreatedLayers.filter((layer) => layer !== null);
2272
2467
 
2273
2468
  // Update state with recreated layers
2274
- this.setState({ wmsUserServiceLayers: validLayers });
2275
- // Get checked layers from session storage
2276
- // const checkedLayers =
2277
- // JSON.parse(sessionStorage.getItem('checkedLayers')) || [];
2278
-
2279
- // For each valid layer that was previously checked, make it visible
2280
- validLayers.forEach((layer) => {
2281
- // if (checkedLayers.includes(layer.LayerId)) {
2282
- const node = document.getElementById(layer.LayerId);
2283
- if (node) {
2284
- layer.visible === true
2285
- ? (node.checked = true)
2286
- : (node.checked = false);
2287
- this.toggleLayer(node);
2288
- }
2469
+ this.setState({ wmsUserServiceLayers: validLayers }, () => {
2470
+ // For each layer, update the checkbox state based on localStorage
2471
+ setTimeout(() => {
2472
+ validLayers.forEach((layer) => {
2473
+ const node = document.getElementById(layer.LayerId);
2474
+ if (node) {
2475
+ // Check the checkbox if it was saved as checked
2476
+ if (layer.checked === true) {
2477
+ // First add to checkedLayers in sessionStorage if not already there
2478
+ const checkedLayers =
2479
+ JSON.parse(sessionStorage.getItem('checkedLayers')) || [];
2480
+
2481
+ if (!checkedLayers.includes(layer.LayerId)) {
2482
+ checkedLayers.unshift(layer.LayerId);
2483
+ sessionStorage.setItem(
2484
+ 'checkedLayers',
2485
+ JSON.stringify(checkedLayers),
2486
+ );
2487
+ window.dispatchEvent(new Event('storage'));
2488
+ }
2489
+
2490
+ // Then check the checkbox and call toggleLayer with a flag to preserve visibility
2491
+ node.checked = true;
2492
+
2493
+ // Custom addition to toggleLayer to bypass visibility override
2494
+ this.toggleLayerWithoutVisibilityReset(node);
2495
+ }
2496
+ }
2497
+ });
2498
+ }, 100);
2289
2499
  });
2290
2500
  }
2291
2501
  } catch (error) {}
2292
2502
  }
2293
2503
  }
2294
2504
 
2505
+ toggleLayerWithoutVisibilityReset(elem) {
2506
+ // Copy most of toggleLayer behavior but don't set this.visibleLayers[elem.id]
2507
+ if (this.layers[elem.id] === undefined) return;
2508
+ if (!this.visibleLayers) this.visibleLayers = {};
2509
+ if (!this.timeLayers) this.timeLayers = {};
2510
+
2511
+ // Add the layer to the map
2512
+ this.layers[elem.id].visible = this.visibleLayers[elem.id][1] === 'eye';
2513
+ this.map.add(this.layers[elem.id]);
2514
+
2515
+ // Continue with other toggleLayer operations
2516
+ this.timeLayers[elem.id] = ['far', 'clock'];
2517
+
2518
+ // Add to active layers
2519
+ this.activeLayersJSON[elem.id] = this.addActiveLayer(
2520
+ elem,
2521
+ Object.keys(this.activeLayersJSON).length,
2522
+ );
2523
+
2524
+ this.saveCheckedLayer(elem.id);
2525
+
2526
+ // Reorder layers
2527
+ let nuts = this.map.layers.items.find((layer) => layer.title === 'nuts');
2528
+ if (nuts) {
2529
+ this.map.reorder(nuts, this.map.layers.items.length + 1);
2530
+ }
2531
+
2532
+ this.layersReorder();
2533
+ this.checkInfoWidget();
2534
+
2535
+ // Toggle custom legend
2536
+ if (
2537
+ this.layers[elem.id].ViewService?.toLowerCase().includes('wmts') ||
2538
+ this.layers[elem.id].ViewService?.toLowerCase().endsWith('file')
2539
+ ) {
2540
+ this.toggleCustomLegendItem(this.layers[elem.id]);
2541
+ }
2542
+
2543
+ if (!this.props.download && this.props.hotspotData) {
2544
+ this.activeLayersToHotspotData(elem.id);
2545
+ }
2546
+
2547
+ this.renderHotspot();
2548
+ }
2549
+
2295
2550
  /**
2296
2551
  * Method to show/hide a layer. Update checkboxes from dataset and products
2297
2552
  * @param {*} elem Is the checkbox
@@ -4033,22 +4288,28 @@ class MenuWidget extends React.Component {
4033
4288
  let layerOpacities = JSON.parse(sessionStorage.getItem('layerOpacities'));
4034
4289
  if (layerOpacities === null) {
4035
4290
  layerOpacities = {};
4036
- layerOpacities[layer] = value;
4037
- sessionStorage.setItem('layerOpacities', JSON.stringify(layerOpacities));
4038
- } else {
4039
- layerOpacities[layer] = value;
4040
- sessionStorage.setItem('layerOpacities', JSON.stringify(layerOpacities));
4041
4291
  }
4042
- let savedServices = JSON.parse(
4292
+ layerOpacities[layer] = value;
4293
+ sessionStorage.setItem('layerOpacities', JSON.stringify(layerOpacities));
4294
+
4295
+ // Save to localStorage for user service layers
4296
+ const savedServices = JSON.parse(
4043
4297
  localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4044
4298
  );
4045
- if (savedServices === null) {
4046
- return;
4047
- } else {
4048
- for (const service of Object.values(savedServices)) {
4299
+
4300
+ if (savedServices && Array.isArray(savedServices)) {
4301
+ let servicesUpdated = false;
4302
+
4303
+ // Update opacity for matching layer in user services
4304
+ savedServices.forEach((service) => {
4049
4305
  if (service.LayerId === layer) {
4050
4306
  service.opacity = value;
4307
+ servicesUpdated = true;
4051
4308
  }
4309
+ });
4310
+
4311
+ // Only save if we made changes
4312
+ if (servicesUpdated) {
4052
4313
  localStorage.setItem(
4053
4314
  USER_SERVICES_KEY + '_' + this.userID,
4054
4315
  JSON.stringify(savedServices),
@@ -4065,43 +4326,44 @@ class MenuWidget extends React.Component {
4065
4326
  loadOpacity() {
4066
4327
  let layerOpacities =
4067
4328
  JSON.parse(sessionStorage.getItem('layerOpacities')) || {};
4329
+
4330
+ // Load from localStorage for user service layers
4068
4331
  const savedUserServices = JSON.parse(
4069
4332
  localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4070
4333
  );
4071
- if (savedUserServices) {
4072
- Object.values(savedUserServices).forEach((service) => {
4073
- let layer = service.LayerId;
4074
- let value = service.opacity;
4075
- // Check if the layer is already in layerOpacities
4076
- if (layerOpacities[layer] === undefined) {
4077
- //add to layerOpacties
4078
- layerOpacities[layer] = value;
4079
- // if (this.layers[service]) {
4080
- // // set map
4081
- // this.layers[service].opacity = value;
4082
- // // set slider
4083
- // let nodeValue = `.active-layer[layer-id="${layer}"] .active-layer-opacity`;
4084
- // let node = document.querySelector(nodeValue);
4085
- // if (node) {
4086
- // node.dataset.opacity = value * 100;
4087
- // }
4088
- // }
4334
+
4335
+ // Import opacity values from user services if not already in session storage
4336
+ if (savedUserServices && Array.isArray(savedUserServices)) {
4337
+ savedUserServices.forEach((service) => {
4338
+ const layerId = service.LayerId;
4339
+ const opacity = service.opacity;
4340
+
4341
+ // If this layer's opacity isn't in session storage yet, add it
4342
+ if (
4343
+ layerId &&
4344
+ opacity !== undefined &&
4345
+ layerOpacities[layerId] === undefined
4346
+ ) {
4347
+ layerOpacities[layerId] = opacity;
4089
4348
  }
4090
4349
  });
4091
4350
  }
4092
- if (layerOpacities) {
4093
- for (const layer in layerOpacities) {
4094
- if (this.layers[layer]) {
4095
- let value = layerOpacities[layer];
4096
- // set map
4097
- this.layers[layer].opacity = value;
4098
- // set slider
4099
- let node = document.querySelector(
4100
- '.active-layer[layer-id="' + layer + '"] .active-layer-opacity',
4101
- );
4102
- if (node) {
4103
- node.dataset.opacity = value * 100;
4104
- }
4351
+ //save layerOpacities to sessionStorage
4352
+ sessionStorage.setItem('layerOpacities', JSON.stringify(layerOpacities));
4353
+ // Apply opacity values to layers and UI
4354
+ for (const layerId in layerOpacities) {
4355
+ if (this.layers[layerId]) {
4356
+ const value = layerOpacities[layerId];
4357
+
4358
+ // Set opacity on the map layer
4359
+ this.layers[layerId].opacity = value;
4360
+
4361
+ // Update UI opacity slider
4362
+ const node = document.querySelector(
4363
+ `.active-layer[layer-id="${layerId}"] .active-layer-opacity`,
4364
+ );
4365
+ if (node) {
4366
+ node.dataset.opacity = value * 100;
4105
4367
  }
4106
4368
  }
4107
4369
  }
@@ -4287,7 +4549,13 @@ class MenuWidget extends React.Component {
4287
4549
  }
4288
4550
  this.setLegendOpacity();
4289
4551
  this.loadOpacity();
4290
- this.loadVisibility();
4552
+ if (
4553
+ prevProps.hotspotData !== this.props.hotspotData ||
4554
+ !this._visibilityInitialized
4555
+ ) {
4556
+ this.loadVisibility();
4557
+ this._visibilityInitialized = true;
4558
+ }
4291
4559
  }
4292
4560
 
4293
4561
  /**
@@ -4295,6 +4563,34 @@ class MenuWidget extends React.Component {
4295
4563
  */
4296
4564
  saveVisibility() {
4297
4565
  if (this.props.download) return;
4566
+
4567
+ // Get services from localStorage - it's an array of objects
4568
+ const savedServices = JSON.parse(
4569
+ localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4570
+ );
4571
+
4572
+ if (savedServices && Array.isArray(savedServices)) {
4573
+ let servicesUpdated = false;
4574
+
4575
+ // Update visibility state for each service in the array
4576
+ savedServices.forEach((service) => {
4577
+ const layerId = service.LayerId;
4578
+ if (layerId && this.visibleLayers[layerId]) {
4579
+ // Update visibility based on eye icon state
4580
+ service.visibility =
4581
+ this.visibleLayers[layerId][1] === 'eye' ? true : false;
4582
+ servicesUpdated = true;
4583
+ }
4584
+ });
4585
+
4586
+ // Only save if we made changes
4587
+ if (servicesUpdated) {
4588
+ localStorage.setItem(
4589
+ USER_SERVICES_KEY + '_' + this.userID,
4590
+ JSON.stringify(savedServices),
4591
+ );
4592
+ }
4593
+ }
4298
4594
  sessionStorage.setItem('visibleLayers', JSON.stringify(this.visibleLayers));
4299
4595
  }
4300
4596
 
@@ -4304,6 +4600,8 @@ class MenuWidget extends React.Component {
4304
4600
  loadVisibility() {
4305
4601
  if (this.props.download) return;
4306
4602
 
4603
+ let hasChanges = false;
4604
+
4307
4605
  // Load visibility settings from sessionStorage
4308
4606
  let vl = JSON.parse(sessionStorage.getItem('visibleLayers'));
4309
4607
  if (vl) {
@@ -4317,44 +4615,52 @@ class MenuWidget extends React.Component {
4317
4615
  localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4318
4616
  );
4319
4617
 
4320
- if (savedUserServices) {
4321
- Object.values(savedUserServices).forEach((service) => {
4322
- let layerId = service.LayerId;
4323
- let visibility = service.visibility;
4324
- // Only add visibility from localStorage if not already in sessionStorage
4325
- if (this.visibleLayers[layerId] === undefined) {
4326
- if (visibility) {
4327
- this.visibleLayers[layerId] = ['fas', 'eye'];
4328
- } else {
4329
- this.visibleLayers[layerId] = ['fas', 'eye-slash'];
4330
- }
4618
+ if (savedUserServices && Array.isArray(savedUserServices)) {
4619
+ savedUserServices.forEach((service) => {
4620
+ const layerId = service.LayerId;
4621
+ // Check if visibility is explicitly defined (could be true, false, or undefined)
4622
+ if (
4623
+ layerId &&
4624
+ this.visibleLayers[layerId] === undefined &&
4625
+ service.visibility !== undefined
4626
+ ) {
4627
+ // Set visible icon based on saved visibility boolean value
4628
+ this.visibleLayers[layerId] =
4629
+ service.visibility === true ? ['fas', 'eye'] : ['fas', 'eye-slash'];
4630
+ hasChanges = true;
4331
4631
  }
4332
4632
  });
4333
4633
  }
4334
-
4335
- // Apply visibility settings to layers
4634
+ //add this.visibleLayers to session storage
4635
+ sessionStorage.setItem('visibleLayers', JSON.stringify(this.visibleLayers));
4336
4636
  for (const key in this.visibleLayers) {
4337
4637
  if (this.layers[key]) {
4338
- if (this.visibleLayers[key][1] === 'eye') {
4339
- this.layers[key].visible = true;
4340
- } else {
4341
- this.layers[key].visible = false;
4342
- }
4638
+ // Set layer visibility based on eye/eye-slash
4639
+ const shouldBeVisible = this.visibleLayers[key][1] === 'eye';
4640
+ this.layers[key].visible = shouldBeVisible;
4343
4641
 
4642
+ // Update the active layer UI if it exists
4344
4643
  let elem = document.getElementById(key);
4345
4644
  if (elem && this.activeLayersJSON[key]) {
4645
+ // Get the current order of the active layer
4346
4646
  let order = this.activeLayersJSON[key].props['layer-order'];
4347
- // add active layer to DOM
4647
+
4648
+ // Force recreate the active layer component with the correct visibility state
4348
4649
  this.activeLayersJSON[key] = this.addActiveLayer(elem, order);
4349
- // reorder layers
4650
+
4651
+ // Update related UI
4350
4652
  this.layersReorder();
4351
- // show/hide info widget
4352
4653
  this.checkInfoWidget();
4353
- // update
4354
- this.setState({});
4654
+ this.toggleCustomLegendItem(this.layers[key]);
4655
+ hasChanges = true;
4355
4656
  }
4356
4657
  }
4357
4658
  }
4659
+
4660
+ // Only update state once if there were changes
4661
+ if (hasChanges) {
4662
+ this.setState({});
4663
+ }
4358
4664
  }
4359
4665
 
4360
4666
  /**
@@ -4393,6 +4699,8 @@ class MenuWidget extends React.Component {
4393
4699
  */
4394
4700
  saveCheckedLayer(layer) {
4395
4701
  if (this.props.download) return;
4702
+
4703
+ // Update sessionStorage as before
4396
4704
  let checkedLayers = JSON.parse(sessionStorage.getItem('checkedLayers'));
4397
4705
  if (checkedLayers === null) {
4398
4706
  checkedLayers = [layer];
@@ -4404,6 +4712,31 @@ class MenuWidget extends React.Component {
4404
4712
  sessionStorage.setItem('checkedLayers', JSON.stringify(checkedLayers));
4405
4713
  }
4406
4714
  window.dispatchEvent(new Event('storage'));
4715
+
4716
+ // Also update localStorage for user service layers
4717
+ const savedServices = JSON.parse(
4718
+ localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4719
+ );
4720
+
4721
+ if (savedServices && Array.isArray(savedServices)) {
4722
+ let servicesUpdated = false;
4723
+
4724
+ // Update checked state for matching layer in user services
4725
+ savedServices.forEach((service) => {
4726
+ if (service.LayerId === layer) {
4727
+ service.checked = true;
4728
+ servicesUpdated = true;
4729
+ }
4730
+ });
4731
+
4732
+ // Only save if we made changes
4733
+ if (servicesUpdated) {
4734
+ localStorage.setItem(
4735
+ USER_SERVICES_KEY + '_' + this.userID,
4736
+ JSON.stringify(savedServices),
4737
+ );
4738
+ }
4739
+ }
4407
4740
  }
4408
4741
 
4409
4742
  /**
@@ -4442,6 +4775,31 @@ class MenuWidget extends React.Component {
4442
4775
  sessionStorage.setItem('visibleLayers', JSON.stringify(visibleLayers));
4443
4776
  }
4444
4777
  }
4778
+
4779
+ // Also update localStorage for user service layers
4780
+ const savedServices = JSON.parse(
4781
+ localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
4782
+ );
4783
+
4784
+ if (savedServices && Array.isArray(savedServices)) {
4785
+ let servicesUpdated = false;
4786
+
4787
+ // Update checked state for matching layer in user services
4788
+ savedServices.forEach((service) => {
4789
+ if (service.LayerId === layer) {
4790
+ service.checked = false;
4791
+ servicesUpdated = true;
4792
+ }
4793
+ });
4794
+
4795
+ // Only save if we made changes
4796
+ if (servicesUpdated) {
4797
+ localStorage.setItem(
4798
+ USER_SERVICES_KEY + '_' + this.userID,
4799
+ JSON.stringify(savedServices),
4800
+ );
4801
+ }
4802
+ }
4445
4803
  }
4446
4804
 
4447
4805
  deleteFilteredLayer(layer) {
@@ -4872,74 +5230,80 @@ class MenuWidget extends React.Component {
4872
5230
  let datasetElem = document.querySelector(
4873
5231
  '[datasetid="' + dataset.DatasetId + '"]',
4874
5232
  );
4875
- for (let l = 0; l < Object.keys(this.activeLayersJSON).length; l++) {
4876
- if (
4877
- dataset.DatasetTitle ===
4878
- this.layers[Object.keys(this.activeLayersJSON)[l]].DatasetTitle
5233
+ if (datasetElem) {
5234
+ for (
5235
+ let l = 0;
5236
+ l < Object.keys(this.activeLayersJSON).length;
5237
+ l++
4879
5238
  ) {
4880
- componentChecked = true;
4881
- productChecked = true;
4882
- datasetChecked = true;
4883
- }
4884
- }
4885
- if (
4886
- searchText === '' &&
4887
- componentFilter === 'default' &&
4888
- productFilter === 'default' &&
4889
- (familyFilter === 'default' || familyFilter === '')
4890
- ) {
4891
- this.filtersApplied = false;
4892
- componentFound = true;
4893
- productFound = true;
4894
- result = true;
4895
- componentElem
4896
- .querySelector('.ccl-expandable__button')
4897
- .setAttribute('aria-expanded', 'false');
4898
- productElem
4899
- .querySelector('.ccl-expandable__button')
4900
- .setAttribute('aria-expanded', 'false');
4901
- datasetElem.removeAttribute('style');
4902
- productElem.removeAttribute('style');
4903
- componentElem.removeAttribute('style');
4904
- if (!defaultActive) {
4905
- defaultActive = 'map_' + datasetElem.id;
4906
- }
4907
- if (dataset.Default_active) {
4908
- defaultActive = 'map_' + datasetElem.id;
4909
- }
4910
- } else if (
4911
- datasetChecked ||
4912
- (dataset?.DatasetTitle?.toUpperCase().includes(searchText) &&
4913
- (product.ProductId === productFilter ||
4914
- productFilter === 'default') &&
4915
- (this.compCfg[index].ComponentPosition.toString() ===
4916
- componentFilter ||
4917
- componentFilter === 'default') &&
4918
- (dataset?.FamilyTitle === familyFilter ||
4919
- familyFilter === 'default' ||
4920
- familyFilter === ''))
4921
- ) {
4922
- this.filtersApplied = true;
4923
- componentFound = true;
4924
- productFound = true;
4925
- result = true;
4926
- componentElem
4927
- .querySelector('.ccl-expandable__button')
4928
- .setAttribute('aria-expanded', 'true');
4929
- productElem
4930
- .querySelector('.ccl-expandable__button')
4931
- .setAttribute('aria-expanded', 'true');
4932
- datasetElem.removeAttribute('style');
4933
- productElem.removeAttribute('style');
4934
- componentElem.removeAttribute('style');
4935
- if (!defaultActive) {
4936
- defaultActive = 'map_' + datasetElem.id;
5239
+ if (
5240
+ dataset.DatasetTitle ===
5241
+ this.layers[Object.keys(this.activeLayersJSON)[l]].DatasetTitle
5242
+ ) {
5243
+ componentChecked = true;
5244
+ productChecked = true;
5245
+ datasetChecked = true;
5246
+ }
4937
5247
  }
4938
- if (dataset.Default_active) {
4939
- defaultActive = 'map_' + datasetElem.id;
5248
+ if (
5249
+ searchText === '' &&
5250
+ componentFilter === 'default' &&
5251
+ productFilter === 'default' &&
5252
+ (familyFilter === 'default' || familyFilter === '')
5253
+ ) {
5254
+ this.filtersApplied = false;
5255
+ componentFound = true;
5256
+ productFound = true;
5257
+ result = true;
5258
+ componentElem
5259
+ .querySelector('.ccl-expandable__button')
5260
+ .setAttribute('aria-expanded', 'false');
5261
+ productElem
5262
+ .querySelector('.ccl-expandable__button')
5263
+ .setAttribute('aria-expanded', 'false');
5264
+ datasetElem.removeAttribute('style');
5265
+ productElem.removeAttribute('style');
5266
+ componentElem.removeAttribute('style');
5267
+ if (!defaultActive) {
5268
+ defaultActive = 'map_' + datasetElem.id;
5269
+ }
5270
+ if (dataset.Default_active) {
5271
+ defaultActive = 'map_' + datasetElem.id;
5272
+ }
5273
+ } else if (
5274
+ datasetChecked ||
5275
+ (dataset?.DatasetTitle?.toUpperCase().includes(searchText) &&
5276
+ (product.ProductId === productFilter ||
5277
+ productFilter === 'default') &&
5278
+ (this.compCfg[index].ComponentPosition.toString() ===
5279
+ componentFilter ||
5280
+ componentFilter === 'default') &&
5281
+ (dataset?.FamilyTitle === familyFilter ||
5282
+ familyFilter === 'default' ||
5283
+ familyFilter === ''))
5284
+ ) {
5285
+ this.filtersApplied = true;
5286
+ componentFound = true;
5287
+ productFound = true;
5288
+ result = true;
5289
+ componentElem
5290
+ .querySelector('.ccl-expandable__button')
5291
+ .setAttribute('aria-expanded', 'true');
5292
+ productElem
5293
+ .querySelector('.ccl-expandable__button')
5294
+ .setAttribute('aria-expanded', 'true');
5295
+ datasetElem.removeAttribute('style');
5296
+ productElem.removeAttribute('style');
5297
+ componentElem.removeAttribute('style');
5298
+ if (!defaultActive) {
5299
+ defaultActive = 'map_' + datasetElem.id;
5300
+ }
5301
+ if (dataset.Default_active) {
5302
+ defaultActive = 'map_' + datasetElem.id;
5303
+ }
5304
+ } else {
5305
+ datasetElem.setAttribute('style', 'display: none;');
4940
5306
  }
4941
- } else {
4942
- datasetElem.setAttribute('style', 'display: none;');
4943
5307
  }
4944
5308
  }
4945
5309
  productCheckbox.setAttribute('defcheck', defaultActive);
@@ -128,20 +128,37 @@ class UploadWidget extends React.Component {
128
128
  this.setState({ wmsServiceUrl: event.target.value });
129
129
  };
130
130
 
131
- handleUploadService = () => {
131
+ handleUploadService = async () => {
132
132
  const { wmsServiceUrl } = this.state;
133
+ try {
134
+ // Use a CORS proxy or add mode: 'no-cors' if you just need to check if service exists
135
+ let urlResult = await fetch(wmsServiceUrl, {
136
+ method: 'GET',
137
+ mode: 'cors', // You might need to change this to 'no-cors' if proxy isn't available
138
+ });
133
139
 
140
+ // Check if service is valid and properly responds
141
+ if (!urlResult || !urlResult.ok) {
142
+ this.errorPopup();
143
+ this.setState({ wmsServiceUrl: '' });
144
+ return;
145
+ }
146
+ } catch (error) {
147
+ this.errorPopup();
148
+ this.setState({ wmsServiceUrl: '' });
149
+ return;
150
+ }
151
+ // If service is valid and is a WMS service
134
152
  if (
135
153
  wmsServiceUrl &&
136
154
  wmsServiceUrl.trim() !== '' &&
137
155
  wmsServiceUrl.toLowerCase().includes('wms')
138
156
  ) {
139
157
  this.uploadUrlServiceHandler(wmsServiceUrl);
140
- this.setState({
141
- wmsServiceUrl: '',
142
- });
158
+ this.setState({ wmsServiceUrl: '' });
143
159
  } else {
144
160
  this.errorPopup();
161
+ this.setState({ wmsServiceUrl: '' });
145
162
  }
146
163
  };
147
164
 
@@ -742,6 +742,14 @@ div.upload-container.esri-component
742
742
  margin-bottom: 0.5rem;
743
743
  }
744
744
 
745
+ .map-menu-family-dataset-dropdown {
746
+ padding-left: 2rem;
747
+ }
748
+
749
+ .map-menu-family-dropdown {
750
+ padding-left: 2rem;
751
+ }
752
+
745
753
  .map-menu-dataset > div:first-of-type {
746
754
  display: flex;
747
755
  width: 100%;