@eeacms/volto-arcgis-block 0.1.469 → 0.1.471

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,20 @@ 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.471](https://github.com/eea/volto-arcgis-block/compare/0.1.470...0.1.471) - 14 August 2026
8
+
9
+ #### :house: Internal changes
10
+
11
+ - style: Automated code fix [eea-jenkins - [`8f37c43`](https://github.com/eea/volto-arcgis-block/commit/8f37c43a97b75692ffe14653b8295408e3b1b157)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - (bug): fix time slider integration for dual resolution layer datasets [Unai Bolivar - [`c12c740`](https://github.com/eea/volto-arcgis-block/commit/c12c740f7f5e04dcc14f7cdecb7488832c5b61ab)]
16
+ ### [0.1.470](https://github.com/eea/volto-arcgis-block/compare/0.1.469...0.1.470) - 13 August 2026
17
+
18
+ #### :hammer_and_wrench: Others
19
+
20
+ - Merge pull request #1195 from eea/develop [Unai Bolivar - [`83e7db1`](https://github.com/eea/volto-arcgis-block/commit/83e7db1ea05eafe8acecbe6169f596f65ed96df5)]
7
21
  ### [0.1.469](https://github.com/eea/volto-arcgis-block/compare/0.1.468...0.1.469) - 12 August 2026
8
22
 
9
23
  #### :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.469",
3
+ "version": "0.1.471",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -952,7 +952,17 @@ class MenuWidget extends React.Component {
952
952
  }
953
953
  }
954
954
 
955
- supportDualLayers(layer, inheritedIndexLayer, datasetCollectionId) {
955
+ supportDualLayers(
956
+ layer,
957
+ inheritedIndexLayer,
958
+ datasetCollectionId,
959
+ isTimeSeries,
960
+ datasetDownloadInformation,
961
+ viewService,
962
+ datasetId,
963
+ datasetTitle,
964
+ productId,
965
+ ) {
956
966
  const resolutionData = layer.resolution_data || {};
957
967
  const thresholdScale = Number(resolutionData.thresholdScale);
958
968
  const normalizeScaleValue = (scaleValue, fallbackValue = 0) => {
@@ -991,6 +1001,7 @@ class MenuWidget extends React.Component {
991
1001
  catalogByoc: null,
992
1002
  evalscript: null,
993
1003
  processUrl: '/++api++/@proxyrunprocessapi',
1004
+ selectedTimeRange: null,
994
1005
  },
995
1006
 
996
1007
  _resolveCatalogDateRange: function (options) {
@@ -1036,11 +1047,23 @@ class MenuWidget extends React.Component {
1036
1047
  })
1037
1048
  .then((payload) => {
1038
1049
  const dates = Array.isArray(payload?.dates) ? payload.dates : [];
1050
+ const resolveTimeValue = (value, isStart) => {
1051
+ if (!value) {
1052
+ return null;
1053
+ }
1054
+ const stringValue = String(value);
1055
+ if (/^\d{4}-\d{2}-\d{2}$/.test(stringValue)) {
1056
+ return isStart
1057
+ ? `${stringValue}T00:00:00Z`
1058
+ : `${stringValue}T23:59:59Z`;
1059
+ }
1060
+ return stringValue;
1061
+ };
1039
1062
  const resolvedDateRange =
1040
1063
  dates.length > 0
1041
1064
  ? {
1042
- from: dates[0],
1043
- to: dates[dates.length - 1],
1065
+ from: resolveTimeValue(dates[0], true),
1066
+ to: resolveTimeValue(dates[dates.length - 1], false),
1044
1067
  }
1045
1068
  : null;
1046
1069
  cacheByCollection[normalizedCollectionId] = resolvedDateRange;
@@ -1087,94 +1110,97 @@ class MenuWidget extends React.Component {
1087
1110
  const width = this.tileInfo.size[0];
1088
1111
  const height = this.tileInfo.size[1];
1089
1112
 
1090
- return this._resolveCatalogDateRange(options).then(
1091
- (catalogDateRange) => {
1092
- const resolvedTimeRangeFrom = catalogDateRange?.from || null;
1093
- const resolvedTimeRangeTo = catalogDateRange?.to || null;
1094
- const timeRange = {};
1095
- if (resolvedTimeRangeFrom) {
1096
- timeRange.from = resolvedTimeRangeFrom;
1097
- }
1098
- if (resolvedTimeRangeTo) {
1099
- timeRange.to = resolvedTimeRangeTo;
1100
- }
1101
- const dataFilter = {};
1102
- if (Object.keys(timeRange).length) {
1103
- dataFilter.timeRange = timeRange;
1104
- }
1113
+ const selectedTimeRange = this.selectedTimeRange || null;
1114
+ const resolveTimeRange = selectedTimeRange
1115
+ ? Promise.resolve(selectedTimeRange)
1116
+ : this._resolveCatalogDateRange(options);
1117
+
1118
+ return resolveTimeRange.then((catalogDateRange) => {
1119
+ const resolvedTimeRangeFrom = catalogDateRange?.from || null;
1120
+ const resolvedTimeRangeTo = catalogDateRange?.to || null;
1121
+ const timeRange = {};
1122
+ if (resolvedTimeRangeFrom) {
1123
+ timeRange.from = resolvedTimeRangeFrom;
1124
+ }
1125
+ if (resolvedTimeRangeTo) {
1126
+ timeRange.to = resolvedTimeRangeTo;
1127
+ }
1128
+ const dataFilter = {};
1129
+ if (Object.keys(timeRange).length) {
1130
+ dataFilter.timeRange = timeRange;
1131
+ }
1105
1132
 
1106
- const payload = {
1107
- input: {
1108
- bounds: {
1109
- bbox: [
1110
- bbox3857.west,
1111
- bbox3857.south,
1112
- bbox3857.east,
1113
- bbox3857.north,
1114
- ],
1115
- properties: {
1116
- crs: 'http://www.opengis.net/def/crs/EPSG/0/3857',
1117
- },
1118
- },
1119
- data: [
1120
- {
1121
- type: this.collectionId,
1122
- ...(Object.keys(dataFilter).length ? { dataFilter } : {}),
1123
- },
1124
- ],
1125
- },
1126
- output: {
1127
- width: width,
1128
- height: height,
1129
- responses: [
1130
- {
1131
- identifier: 'default',
1132
- format: { type: 'image/png' },
1133
- },
1133
+ const payload = {
1134
+ input: {
1135
+ bounds: {
1136
+ bbox: [
1137
+ bbox3857.west,
1138
+ bbox3857.south,
1139
+ bbox3857.east,
1140
+ bbox3857.north,
1134
1141
  ],
1142
+ properties: {
1143
+ crs: 'http://www.opengis.net/def/crs/EPSG/0/3857',
1144
+ },
1135
1145
  },
1136
- evalscript: this.evalscript,
1137
- };
1146
+ data: [
1147
+ {
1148
+ type: this.collectionId,
1149
+ ...(Object.keys(dataFilter).length ? { dataFilter } : {}),
1150
+ },
1151
+ ],
1152
+ },
1153
+ output: {
1154
+ width: width,
1155
+ height: height,
1156
+ responses: [
1157
+ {
1158
+ identifier: 'default',
1159
+ format: { type: 'image/png' },
1160
+ },
1161
+ ],
1162
+ },
1163
+ evalscript: this.evalscript,
1164
+ };
1138
1165
 
1139
- return fetch(this.processUrl, {
1140
- method: 'POST',
1141
- headers: {
1142
- 'Content-Type': 'application/json',
1143
- Accept: 'image/png',
1144
- },
1145
- body: JSON.stringify(payload),
1146
- signal: options && options.signal,
1166
+ return fetch(this.processUrl, {
1167
+ method: 'POST',
1168
+ headers: {
1169
+ 'Content-Type': 'application/json',
1170
+ Accept: 'image/png',
1171
+ },
1172
+ body: JSON.stringify(payload),
1173
+ signal: options && options.signal,
1174
+ })
1175
+ .then((response) => {
1176
+ if (!response.ok) {
1177
+ throw new Error(
1178
+ `Process API error ${response.status}: ${response.statusText}`,
1179
+ );
1180
+ }
1181
+ return response.blob();
1147
1182
  })
1148
- .then((response) => {
1149
- if (!response.ok) {
1150
- throw new Error(
1151
- `Process API error ${response.status}: ${response.statusText}`,
1152
- );
1153
- }
1154
- return response.blob();
1155
- })
1156
- .then((blob) => {
1157
- return new Promise((resolve, reject) => {
1158
- const url = URL.createObjectURL(blob);
1159
- const img = new Image();
1160
- img.onload = () => {
1161
- const canvas = document.createElement('canvas');
1162
- canvas.width = width;
1163
- canvas.height = height;
1164
- const ctx = canvas.getContext('2d');
1165
- ctx.drawImage(img, 0, 0, width, height);
1166
- URL.revokeObjectURL(url);
1167
- resolve(canvas);
1168
- };
1169
- img.onerror = (err) => {
1170
- URL.revokeObjectURL(url);
1171
- reject(err);
1172
- };
1173
- img.src = url;
1174
- });
1183
+ .then((blob) => {
1184
+ return new Promise((resolve, reject) => {
1185
+ const url = URL.createObjectURL(blob);
1186
+ const img = new Image();
1187
+ img.onload = () => {
1188
+ const canvas = document.createElement('canvas');
1189
+ canvas.width = width;
1190
+ canvas.height = height;
1191
+ const ctx = canvas.getContext('2d');
1192
+ ctx.drawImage(img, 0, 0, width, height);
1193
+ URL.revokeObjectURL(url);
1194
+ resolve(canvas);
1195
+ };
1196
+ img.onerror = (err) => {
1197
+ URL.revokeObjectURL(url);
1198
+ reject(err);
1199
+ };
1200
+ img.src = url;
1175
1201
  });
1176
- },
1177
- );
1202
+ });
1203
+ });
1178
1204
  },
1179
1205
  });
1180
1206
  const tileInfo = {
@@ -1237,6 +1263,15 @@ class MenuWidget extends React.Component {
1237
1263
  title: baseLayerTitle,
1238
1264
  visibilityMode: 'independent',
1239
1265
  layers: dualResolutionLayers,
1266
+ isTimeSeries: isTimeSeries,
1267
+ DatasetDownloadInformation: datasetDownloadInformation || {},
1268
+ datasetDownloadInformation: datasetDownloadInformation || {},
1269
+ ViewService: viewService,
1270
+ url: viewService,
1271
+ DatasetId: datasetId,
1272
+ DatasetTitle: datasetTitle,
1273
+ ProductId: productId,
1274
+ LayerTitle: baseLayerTitle,
1240
1275
  });
1241
1276
  }
1242
1277
 
@@ -2359,7 +2394,17 @@ class MenuWidget extends React.Component {
2359
2394
  ) {
2360
2395
  const datasetCollectionId =
2361
2396
  dataset_download_information?.items?.[0]?.byoc_collection || null;
2362
- this.supportDualLayers(layer, inheritedIndexLayer, datasetCollectionId);
2397
+ this.supportDualLayers(
2398
+ layer,
2399
+ inheritedIndexLayer,
2400
+ datasetCollectionId,
2401
+ isTimeSeries,
2402
+ dataset_download_information,
2403
+ viewService,
2404
+ DatasetId,
2405
+ DatasetTitle,
2406
+ ProductId,
2407
+ );
2363
2408
  } else if (viewService?.toLowerCase().includes('wms')) {
2364
2409
  viewService = viewService?.includes('?')
2365
2410
  ? viewService + '&'
@@ -45,6 +45,8 @@ class TimesliderWidget extends React.Component {
45
45
  this.layerName = this.layer.sublayers.items[0].name;
46
46
  } else if (this.layer.type === 'wmts') {
47
47
  this.layerName = this.layer.activeLayer.id; //WMTS
48
+ } else if (this.layer.type === 'group') {
49
+ this.layerName = this.layer.name || this.layer.title || this.layer.id;
48
50
  }
49
51
  this.drag = {};
50
52
  if (
@@ -61,6 +63,54 @@ class TimesliderWidget extends React.Component {
61
63
  this.containerParentNode = null;
62
64
  }
63
65
 
66
+ resolveLayerServiceUrl() {
67
+ return (
68
+ this.layer?.ViewService ||
69
+ this.layer?.viewService ||
70
+ this.layer?.url ||
71
+ null
72
+ );
73
+ }
74
+
75
+ applyLayerTimeSelection(timeValue) {
76
+ if (!timeValue) return;
77
+ if (this.layer.type === 'wmts') {
78
+ this.layer.customLayerParameters = {
79
+ SHOWLOGO: false,
80
+ };
81
+ this.layer.customLayerParameters['TIME'] = timeValue + '/' + timeValue;
82
+ this.layer.refresh();
83
+ return;
84
+ }
85
+ if (this.layer.type === 'group') {
86
+ const selectedTimeText = String(timeValue);
87
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(selectedTimeText);
88
+ const selectedTimeRange = isDateOnly
89
+ ? {
90
+ from: `${selectedTimeText}T00:00:00Z`,
91
+ to: `${selectedTimeText}T23:59:59Z`,
92
+ }
93
+ : {
94
+ from: selectedTimeText,
95
+ to: selectedTimeText,
96
+ };
97
+ const childLayers = this.layer?.layers?.items || [];
98
+ childLayers.forEach((childLayer) => {
99
+ childLayer.selectedTimeRange = selectedTimeRange;
100
+ if (typeof childLayer.refresh === 'function') {
101
+ childLayer.refresh();
102
+ }
103
+ });
104
+ if (typeof this.layer.refresh === 'function') {
105
+ this.layer.refresh();
106
+ }
107
+ return;
108
+ }
109
+ this.layer.customLayerParameters = {};
110
+ this.layer.customLayerParameters['TIME'] = timeValue;
111
+ this.layer.refresh();
112
+ }
113
+
64
114
  restoreContainerContext() {
65
115
  if (
66
116
  !this.container ||
@@ -344,20 +394,9 @@ class TimesliderWidget extends React.Component {
344
394
  if (!this.container.current ? true : false) {
345
395
  this.TimesliderWidget.stop();
346
396
  }
347
- if (this.layer.type === 'wmts') {
348
- this.layer.customLayerParameters = {
349
- SHOWLOGO: false,
350
- };
351
- this.layer.customLayerParameters['TIME'] =
352
- timeDict[this.TimesliderWidget.timeExtent.end] +
353
- '/' +
354
- timeDict[this.TimesliderWidget.timeExtent.end];
355
- } else {
356
- this.layer.customLayerParameters = {};
357
- this.layer.customLayerParameters['TIME'] =
358
- timeDict[this.TimesliderWidget.timeExtent.end];
359
- }
360
- this.layer.refresh();
397
+ this.applyLayerTimeSelection(
398
+ timeDict[this.TimesliderWidget.timeExtent.end],
399
+ );
361
400
  },
362
401
  );
363
402
  this._watchHandles.push(watchHandle);
@@ -473,7 +512,8 @@ class TimesliderWidget extends React.Component {
473
512
  .whenLayerView(this.layer, this.TimesliderWidget)
474
513
  .then(async (lv) => {
475
514
  if (!this._isMounted) return;
476
- this.url = lv?.layer?.viewService ?? this.layer?.url ?? null;
515
+ this.url =
516
+ lv?.layer?.viewService ?? this.resolveLayerServiceUrl() ?? null;
477
517
  const urlNorm =
478
518
  typeof this.url === 'string'
479
519
  ? this.url.replace(/%2F/gi, '/').toLowerCase()
@@ -538,6 +578,10 @@ class TimesliderWidget extends React.Component {
538
578
  } else if (this.layer.type === 'wmts') {
539
579
  times = this.parseTimeWMTS(xml);
540
580
  }
581
+ if (!this.layerName || !times[this.layerName]) {
582
+ this.TimesliderWidget.disabled = true;
583
+ return;
584
+ }
541
585
  // Capabilities have time enabled
542
586
  if (
543
587
  times[this.layerName].hasOwnProperty('dimension') === false
@@ -639,32 +683,24 @@ class TimesliderWidget extends React.Component {
639
683
  // }
640
684
  // this.props.time.dataset.setAttribute('time-start', start);
641
685
  // this.props.time.dataset.setAttribute('time-end', end);
642
- if (this.layer.type === 'wmts') {
643
- this.layer.customLayerParameters = {};
644
- this.layer.customLayerParameters['TIME'] =
645
- timeDict[this.TimesliderWidget.timeExtent.end] +
646
- '/' +
647
- timeDict[this.TimesliderWidget.timeExtent.end];
686
+ if (times[this.layerName].hasOwnProperty('array')) {
687
+ this.applyLayerTimeSelection(
688
+ timeDict[this.TimesliderWidget.timeExtent.end],
689
+ );
648
690
  } else {
649
- this.layer.customLayerParameters = {};
650
- if (times[this.layerName].hasOwnProperty('array')) {
651
- this.layer.customLayerParameters['TIME'] =
652
- timeDict[this.TimesliderWidget.timeExtent.end];
653
- } else {
654
- const newDateTimeObject = new Date(
655
- this.TimesliderWidget.timeExtent.start.toISOString(),
656
- );
657
- newDateTimeObject.setMinutes(
658
- this.TimesliderWidget.timeExtent.start.getMinutes() +
659
- this.TimesliderWidget.stops['interval'].value,
660
- );
661
- this.layer.customLayerParameters['TIME'] =
662
- this.TimesliderWidget.timeExtent.start.toISOString() +
691
+ const newDateTimeObject = new Date(
692
+ this.TimesliderWidget.timeExtent.start.toISOString(),
693
+ );
694
+ newDateTimeObject.setMinutes(
695
+ this.TimesliderWidget.timeExtent.start.getMinutes() +
696
+ this.TimesliderWidget.stops['interval'].value,
697
+ );
698
+ this.applyLayerTimeSelection(
699
+ this.TimesliderWidget.timeExtent.start.toISOString() +
663
700
  '/' +
664
- newDateTimeObject.toISOString(); //OK
665
- }
701
+ newDateTimeObject.toISOString(),
702
+ );
666
703
  }
667
- this.layer.refresh();
668
704
  },
669
705
  );
670
706
  this._watchHandles.push(watchHandle);
@@ -721,7 +757,7 @@ class TimesliderWidget extends React.Component {
721
757
 
722
758
  componentDidUpdate(prevProps) {
723
759
  if (!this.TimesliderWidget) return;
724
- const url = this.layer?.url || '';
760
+ const url = this.resolveLayerServiceUrl() || '';
725
761
  const urlNorm =
726
762
  typeof url === 'string' ? url.replace(/%2F/gi, '/').toLowerCase() : '';
727
763
  const isCDSE = urlNorm.includes('/ogc/') || urlNorm.includes('/cdse/');