@eeacms/volto-arcgis-block 0.1.483 → 0.1.485

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,18 @@ 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.485](https://github.com/eea/volto-arcgis-block/compare/0.1.484...0.1.485) - 24 September 2026
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - (lint): lint and prettified [Unai Bolivar - [`6caacff`](https://github.com/eea/volto-arcgis-block/commit/6caacffcda92259d4ecd9e5ba0aedea28c0999e1)]
12
+ - (bug): removed additional zoom on extent behavior [Unai Bolivar - [`b609753`](https://github.com/eea/volto-arcgis-block/commit/b6097536b13e94cd1772c0528a6ea984fb497fcb)]
13
+ - (bug): parse CDSE WMTS capabilities identifiers correctly [Unai Bolivar - [`a3c9f4b`](https://github.com/eea/volto-arcgis-block/commit/a3c9f4b285c916a0adf45168cab85950a4415890)]
14
+ ### [0.1.484](https://github.com/eea/volto-arcgis-block/compare/0.1.483...0.1.484) - 15 September 2026
15
+
16
+ #### :hammer_and_wrench: Others
17
+
18
+ - (lint): lint and prettified [Unai Bolivar - [`13294af`](https://github.com/eea/volto-arcgis-block/commit/13294aff5e09463006cbc6421800c8f71bd4ac49)]
7
19
  ### [0.1.483](https://github.com/eea/volto-arcgis-block/compare/0.1.482...0.1.483) - 15 September 2026
8
20
 
9
21
  ### [0.1.482](https://github.com/eea/volto-arcgis-block/compare/0.1.481...0.1.482) - 15 September 2026
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.483",
3
+ "version": "0.1.485",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -577,10 +577,29 @@ class InfoWidget extends React.Component {
577
577
  }
578
578
 
579
579
  identifyWMTS(layer, event) {
580
- let layerId = layer.activeLayer.id;
581
580
  let url = layer.featureInfoUrl ? layer.featureInfoUrl : layer.url;
582
581
  let featureInfoUrl = url.replace('/cdse/', '/cdse/wms/');
583
582
  return this.wmsCapabilities(url).then((xml) => {
583
+ let serviceData = layer.ViewService ? layer.ViewService : url;
584
+ let isCdseWmts = ['/ogc/', '/cdse/'].some((segment) =>
585
+ serviceData.toLowerCase().includes(segment),
586
+ );
587
+ let layerId;
588
+ if (isCdseWmts) {
589
+ let wmtsLayers = Array.from(xml.querySelectorAll('Layer'));
590
+ let layerTitle = this.getLayerTitle(layer);
591
+ let selectedLayer = wmtsLayers.find((item) => {
592
+ let titleNode =
593
+ item.querySelector('ows\\:Title') || item.querySelector('Title');
594
+ return titleNode && titleNode.textContent === layerTitle;
595
+ });
596
+ layerId = (
597
+ selectedLayer.querySelector('ows\\:Identifier') ||
598
+ selectedLayer.querySelector('Identifier')
599
+ ).textContent;
600
+ } else {
601
+ layerId = layer.activeLayer.id;
602
+ }
584
603
  let version = layer.version;
585
604
  let format = this.parseFormat(xml, layerId);
586
605
  let times = '';
@@ -7,6 +7,7 @@ import useCartState from '@eeacms/volto-clms-utils/cart/useCartState';
7
7
  import { Modal, Popup } from 'semantic-ui-react';
8
8
  import AreaWidget from './AreaWidget';
9
9
  import TimesliderWidget from './TimesliderWidget';
10
+ import { resolveServiceUrl } from './serviceUrlResolver';
10
11
 
11
12
  export const USER_SERVICES_KEY = 'user_services_session';
12
13
 
@@ -479,24 +480,12 @@ class MenuWidget extends React.Component {
479
480
  // add zoomend listener to map to show/hide zoom in message
480
481
  this.view.watch('stationary', (isStationary) => {
481
482
  let snowAndIceInSessionStorage = sessionStorage.getItem('snowAndIce');
482
- let node;
483
483
  if (this.view && this.view.center) {
484
484
  this.extentCenter = { x: this.view.center.x, y: this.view.center.y };
485
485
  }
486
486
  if (isStationary) {
487
487
  this.extentInitiated = false;
488
- let zoom = this.view.get('zoom');
489
- if (this.props.download) {
490
- node = document.querySelector('.zoom-in-message-dataset');
491
- if (node && node !== null) {
492
- node.style.display = zoom > 6 ? 'none' : 'block';
493
- }
494
- }
495
488
  if (!this.props.download && snowAndIceInSessionStorage === 'true') {
496
- node = document.getElementById('snow-and-ice-zoom-message');
497
- if (node && node !== null) {
498
- node.style.display = zoom > 6 ? 'none' : 'block';
499
- }
500
489
  let innerDropdown = document.getElementsByClassName(
501
490
  'map-product-checkbox',
502
491
  );
@@ -521,41 +510,7 @@ class MenuWidget extends React.Component {
521
510
  snowAndIce.offsetParent.nextSibling === null
522
511
  )
523
512
  return;
524
- let checks = snowAndIce.offsetParent.nextSibling.children;
525
- let checksList = [...checks];
526
- if (checksList && checksList !== null) {
527
- checksList.forEach((check) => {
528
- if (check && check !== null) {
529
- if (check.querySelector('[type="checkbox"]').checked) {
530
- let node = [
531
- ...check.getElementsByClassName('zoom-in-message-dataset'),
532
- ][0];
533
- if (node && node !== null) {
534
- node.style.display = zoom > 6 ? 'none' : 'block';
535
- }
536
- }
537
- }
538
- });
539
- }
540
513
  }
541
- let zoomInMessageContainers = document.getElementsByClassName(
542
- 'zoom-in-message-container',
543
- );
544
- let zoomInMessageContainersList = [...zoomInMessageContainers];
545
- zoomInMessageContainersList.forEach((container) => {
546
- if (container && container !== null) {
547
- let nodes = [
548
- ...document.getElementsByClassName('zoom-in-message-dataset'),
549
- ];
550
- nodes.forEach((node) => {
551
- if (node && node !== null) {
552
- if (node.innerText === 'Zoom in') {
553
- node.style.display = zoom > 6 ? 'none' : 'block';
554
- }
555
- }
556
- });
557
- }
558
- });
559
514
  if (!this.visibleLayers) this.visibleLayers = {};
560
515
  this.handleRasterVectorLegend();
561
516
  this.setState({});
@@ -2661,28 +2616,6 @@ class MenuWidget extends React.Component {
2661
2616
  );
2662
2617
  }
2663
2618
 
2664
- stripProtocol(url) {
2665
- return (url || '').replace(/^https?:\/\//i, '');
2666
- }
2667
-
2668
- getProxyBase = () => {
2669
- const origin = window?.location?.origin || '';
2670
- return origin ? `${origin}/ogcproxy/` : '/ogcproxy/';
2671
- // return 'https://clmsdemo.devel6cph.eea.europa.eu/ogcproxy/';
2672
- // return 'https://land.copernicus.eu/ogcproxy/';
2673
- };
2674
-
2675
- buildProxiedUrl(url) {
2676
- if (!url) return url;
2677
- const hasProxy = /\/ogcproxy\//i.test(url);
2678
- if (hasProxy) {
2679
- const strippedUrl = this.stripProtocol(url);
2680
- const proxyPath = strippedUrl.split(/\/ogcproxy\//i)[1] || '';
2681
- return this.getProxyBase() + proxyPath.replace(/^\/+/, '');
2682
- }
2683
- return this.getProxyBase() + this.stripProtocol(url);
2684
- }
2685
-
2686
2619
  buildCdseLegendUrl(serviceUrl, layerTitle) {
2687
2620
  if (!serviceUrl || !layerTitle) return null;
2688
2621
  const collectionMatch =
@@ -3657,9 +3590,11 @@ class MenuWidget extends React.Component {
3657
3590
 
3658
3591
  async handleNewMapServiceLayer(viewService, serviceType, serviceSelection) {
3659
3592
  let resourceLayers = [];
3660
- const proxiedUrl = this.buildProxiedUrl(viewService);
3593
+ const resolvedServiceUrl = resolveServiceUrl(viewService, {
3594
+ fromUploadWidget: true,
3595
+ });
3661
3596
  try {
3662
- const rawUrl = (proxiedUrl || '').trim();
3597
+ const rawUrl = (resolvedServiceUrl || '').trim();
3663
3598
  const baseUrl = rawUrl.split('?')[0];
3664
3599
  const resolveCapabilitiesText = (xmlData) => {
3665
3600
  if (!xmlData) {
@@ -4034,7 +3969,7 @@ class MenuWidget extends React.Component {
4034
3969
  }
4035
3970
  }
4036
3971
  if (serviceType === 'WMS' || serviceType === 'WMTS') {
4037
- const forced = (proxiedUrl || '').trim();
3972
+ const forced = (resolvedServiceUrl || '').trim();
4038
3973
  if (forced) {
4039
3974
  if (typeof resourceLayer.url === 'string') {
4040
3975
  resourceLayer.url = forced;
@@ -6195,7 +6130,9 @@ class MenuWidget extends React.Component {
6195
6130
 
6196
6131
  getCapabilities = async (url, serviceType, options = {}) => {
6197
6132
  const useProxy = !!options?.useProxy;
6198
- const targetUrl = useProxy ? this.buildProxiedUrl(url) : url;
6133
+ const targetUrl = useProxy
6134
+ ? resolveServiceUrl(url, { fromUploadWidget: true })
6135
+ : url;
6199
6136
  const evaluateXmlResponse = (responseData) => {
6200
6137
  const parser = new DOMParser();
6201
6138
  const xmlDoc = parser.parseFromString(responseData, 'text/xml');
@@ -6438,35 +6375,7 @@ class MenuWidget extends React.Component {
6438
6375
  ymax: BBoxes['dataset'].ymax,
6439
6376
  });
6440
6377
  }
6441
- if (
6442
- this.extentInitiated === false &&
6443
- (this.layers[elem.id].DatasetId === '65f8eded11d94a1ba5540ceecaddd4e6' ||
6444
- this.layers[elem.id].DatasetId === '40e056d02eed4c1fb2040cf0f06823df')
6445
- ) {
6446
- let myExtent = new Extent({
6447
- xmin: -13478905.5678019,
6448
- ymin: 23797904.386302948,
6449
- xmax: 20538395.093334593,
6450
- ymax: 11175665.272476234,
6451
- spatialReference: 3857,
6452
- });
6453
- const targetCenter = myExtent.center;
6454
- if (this.extentCenter) {
6455
- const epsilon = 1e-3;
6456
- const sameStoredCenter =
6457
- Math.abs(this.extentCenter.x - targetCenter.x) < epsilon &&
6458
- Math.abs(this.extentCenter.y - targetCenter.y) < epsilon;
6459
- if (sameStoredCenter) {
6460
- this.extentInitiated = true;
6461
- return;
6462
- }
6463
- }
6464
- this.view.goTo({ center: targetCenter, zoom: 3 });
6465
- this.extentCenter = { x: targetCenter.x, y: targetCenter.y };
6466
- this.extentInitiated = true;
6467
- } else {
6468
- this.view.goTo(myExtent);
6469
- }
6378
+ this.view.goTo(myExtent);
6470
6379
  }
6471
6380
 
6472
6381
  async fullExtent(elem) {
@@ -6819,42 +6728,7 @@ class MenuWidget extends React.Component {
6819
6728
  xmax: firstLayer.xmax,
6820
6729
  ymax: firstLayer.ymax,
6821
6730
  });
6822
- if (
6823
- this.extentInitiated === false &&
6824
- (this.layers[elem.id].DatasetId ===
6825
- '65f8eded11d94a1ba5540ceecaddd4e6' ||
6826
- this.layers[elem.id].DatasetId === '40e056d02eed4c1fb2040cf0f06823df')
6827
- ) {
6828
- let myExtent = new Extent({
6829
- xmin: -13478905.5678019,
6830
- ymin: 23797904.386302948,
6831
- xmax: 20538395.093334593,
6832
- ymax: 11175665.272476234,
6833
- spatialReference: 3857,
6834
- });
6835
- const targetCenter = myExtent.center;
6836
- if (this.extentCenter) {
6837
- const epsilon = 1e-3;
6838
- const sameStoredCenter =
6839
- Math.abs(this.extentCenter.x - targetCenter.x) < epsilon &&
6840
- Math.abs(this.extentCenter.y - targetCenter.y) < epsilon;
6841
- if (sameStoredCenter) {
6842
- if (this.toggleHotspotWidget.view.zoom !== 3) {
6843
- this.view.zoom = 3;
6844
- this.setState({}); // Force re-render
6845
- return;
6846
- } else {
6847
- this.extentInitiated = true;
6848
- return;
6849
- }
6850
- }
6851
- }
6852
- this.view.goTo({ center: targetCenter, zoom: 3 });
6853
- this.extentCenter = { x: targetCenter.x, y: targetCenter.y };
6854
- this.extentInitiated = true;
6855
- } else {
6856
- this.view.goTo(myExtent);
6857
- }
6731
+ this.view.goTo(myExtent);
6858
6732
  }
6859
6733
  this.url = null;
6860
6734
  }
@@ -2,6 +2,7 @@ import React, { createRef } from 'react';
2
2
  import { loadModules } from 'esri-loader';
3
3
  // import { FontAwesomeIcon } from '@eeacms/volto-clms-utils/components';
4
4
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
+ import { resolveServiceUrl } from './serviceUrlResolver';
5
6
 
6
7
  var WMSLayer, WMTSLayer, WFSLayer, esriRequest;
7
8
  const SHAPEFILE_MAX_SIZE_BYTES = 2097152;
@@ -334,28 +335,6 @@ class UploadWidget extends React.Component {
334
335
  this.clearUploadForm(true);
335
336
  };
336
337
 
337
- stripProtocol = (url) => {
338
- return (url || '').replace(/^https?:\/\//i, '');
339
- };
340
-
341
- getProxyBase = () => {
342
- const origin = window?.location?.origin || '';
343
- return origin ? `${origin}/ogcproxy/` : '/ogcproxy/';
344
- // return 'https://clmsdemo.devel6cph.eea.europa.eu/ogcproxy/';
345
- // return 'https://land.copernicus.eu/ogcproxy/';
346
- };
347
-
348
- buildProxiedUrl = (url) => {
349
- if (!url) return url;
350
- const hasProxy = /\/ogcproxy\//i.test(url);
351
- if (hasProxy) {
352
- const strippedUrl = this.stripProtocol(url);
353
- const proxyPath = strippedUrl.split(/\/ogcproxy\//i)[1] || '';
354
- return this.getProxyBase() + proxyPath.replace(/^\/+/, '');
355
- }
356
- return this.getProxyBase() + this.stripProtocol(url);
357
- };
358
-
359
338
  resolveNodeValue = (nodeValue) => {
360
339
  if (nodeValue === null || nodeValue === undefined) {
361
340
  return null;
@@ -510,8 +489,10 @@ class UploadWidget extends React.Component {
510
489
 
511
490
  getCapabilities = (url, serviceType) => {
512
491
  // Get the coordinates of the click on the view
513
- const proxiedUrl = this.buildProxiedUrl(url);
514
- return esriRequest(proxiedUrl, {
492
+ const resolvedServiceUrl = resolveServiceUrl(url, {
493
+ fromUploadWidget: true,
494
+ });
495
+ return esriRequest(resolvedServiceUrl, {
515
496
  responseType: 'text',
516
497
  query: {
517
498
  request: 'GetCapabilities',
@@ -617,15 +598,21 @@ class UploadWidget extends React.Component {
617
598
  trimmedServiceUrl,
618
599
  selectedServiceType,
619
600
  );
620
- const proxiedUrl = this.buildProxiedUrl(normalizedUrl);
601
+ const resolvedServiceUrl = resolveServiceUrl(normalizedUrl, {
602
+ fromUploadWidget: true,
603
+ });
621
604
  if (selectedServiceType === 'WMS') {
622
- this.uploadUrlServiceHandler(proxiedUrl, 'WMS');
605
+ this.uploadUrlServiceHandler(resolvedServiceUrl, 'WMS');
623
606
  this.setState({ serviceUrl: '' });
624
607
  } else if (selectedServiceType === 'WMTS') {
625
- this.uploadUrlServiceHandler(proxiedUrl, 'WMTS');
608
+ this.uploadUrlServiceHandler(resolvedServiceUrl, 'WMTS');
626
609
  this.setState({ serviceUrl: '' });
627
610
  } else if (selectedServiceType === 'WFS') {
628
- this.uploadUrlServiceHandler(proxiedUrl, 'WFS', selectedFeatures);
611
+ this.uploadUrlServiceHandler(
612
+ resolvedServiceUrl,
613
+ 'WFS',
614
+ selectedFeatures,
615
+ );
629
616
  this.setState({ wfsFeatures: {}, serviceUrl: '' });
630
617
  this.setState({ selectedFeatures: {} });
631
618
  } else {
@@ -0,0 +1,131 @@
1
+ const PROXY_PATH_PATTERN = /\/ogcproxy\//i;
2
+ const INTERNAL_HOST_PATTERNS = [
3
+ /^localhost$/i,
4
+ /^127\.0\.0\.1$/i,
5
+ /^0\.0\.0\.0$/i,
6
+ /(^|\.)land\.copernicus\.eu$/i,
7
+ /(^|\.)clms-staging\.eea\.europa\.eu$/i,
8
+ ];
9
+
10
+ const stripProtocol = (url) => {
11
+ return (url || '').replace(/^https?:\/\//i, '');
12
+ };
13
+
14
+ const getProxyBase = () => {
15
+ const origin = window?.location?.origin || '';
16
+ return origin ? `${origin}/ogcproxy/` : '/ogcproxy/';
17
+ };
18
+
19
+ const resolveAbsoluteUrl = (url) => {
20
+ if (!url || typeof url !== 'string') {
21
+ return null;
22
+ }
23
+ try {
24
+ return new URL(url, window?.location?.origin || undefined);
25
+ } catch (e) {
26
+ return null;
27
+ }
28
+ };
29
+
30
+ const isRelativeUrl = (url) => {
31
+ if (!url || typeof url !== 'string') {
32
+ return false;
33
+ }
34
+ return !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(url);
35
+ };
36
+
37
+ const isInternalHost = (hostname, currentHostname) => {
38
+ const hostValue = String(hostname || '').toLowerCase();
39
+ const currentHostValue = String(currentHostname || '').toLowerCase();
40
+ if (!hostValue) {
41
+ return false;
42
+ }
43
+ if (
44
+ INTERNAL_HOST_PATTERNS.some((hostPattern) => hostPattern.test(hostValue))
45
+ ) {
46
+ return true;
47
+ }
48
+ if (currentHostValue && hostValue === currentHostValue) {
49
+ return true;
50
+ }
51
+ if (currentHostValue && hostValue.endsWith(`.${currentHostValue}`)) {
52
+ return true;
53
+ }
54
+ if (currentHostValue && currentHostValue.endsWith(`.${hostValue}`)) {
55
+ return true;
56
+ }
57
+ return false;
58
+ };
59
+
60
+ const isInternalAbsoluteUrl = (absoluteUrl) => {
61
+ if (!absoluteUrl) {
62
+ return false;
63
+ }
64
+ const currentOrigin = window?.location?.origin || '';
65
+ const currentHostname = window?.location?.hostname || '';
66
+ if (currentOrigin && absoluteUrl.origin === currentOrigin) {
67
+ return true;
68
+ }
69
+ return isInternalHost(absoluteUrl.hostname, currentHostname);
70
+ };
71
+
72
+ const resolveUnproxiedUrl = (inputUrl) => {
73
+ if (!inputUrl || !PROXY_PATH_PATTERN.test(inputUrl)) {
74
+ return inputUrl;
75
+ }
76
+ const strippedUrl = stripProtocol(inputUrl);
77
+ const proxyPathData = strippedUrl.split(PROXY_PATH_PATTERN)[1] || '';
78
+ const normalizedPathData = proxyPathData.replace(/^\/+/, '');
79
+ if (!normalizedPathData) {
80
+ return inputUrl;
81
+ }
82
+ if (/^https?:\/\//i.test(normalizedPathData)) {
83
+ return normalizedPathData;
84
+ }
85
+ const secureProtocol =
86
+ window?.location?.protocol === 'http:' ? 'http://' : 'https://';
87
+ return secureProtocol + normalizedPathData;
88
+ };
89
+
90
+ export const resolveServiceUrl = (url, options = {}) => {
91
+ if (!url || typeof url !== 'string') {
92
+ return url;
93
+ }
94
+
95
+ if (!options.fromUploadWidget) {
96
+ return url;
97
+ }
98
+
99
+ const trimmedUrl = url.trim();
100
+ if (!trimmedUrl) {
101
+ return trimmedUrl;
102
+ }
103
+
104
+ if (/^(blob:|data:|about:)/i.test(trimmedUrl)) {
105
+ return trimmedUrl;
106
+ }
107
+
108
+ if (isRelativeUrl(trimmedUrl) && !PROXY_PATH_PATTERN.test(trimmedUrl)) {
109
+ return trimmedUrl;
110
+ }
111
+
112
+ const alreadyProxied = PROXY_PATH_PATTERN.test(trimmedUrl);
113
+ const unproxiedUrl = resolveUnproxiedUrl(trimmedUrl);
114
+ const absoluteUrl = resolveAbsoluteUrl(unproxiedUrl);
115
+
116
+ if (!absoluteUrl) {
117
+ return trimmedUrl;
118
+ }
119
+
120
+ if (isInternalAbsoluteUrl(absoluteUrl)) {
121
+ return absoluteUrl.toString();
122
+ }
123
+
124
+ const normalizedExternalUrl = stripProtocol(absoluteUrl.toString());
125
+ if (alreadyProxied) {
126
+ const proxyPathData = normalizedExternalUrl.replace(/^\/+/, '');
127
+ return getProxyBase() + proxyPathData;
128
+ }
129
+
130
+ return getProxyBase() + normalizedExternalUrl;
131
+ };