@eodash/eodash 5.6.1 → 5.7.0

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.
Files changed (81) hide show
  1. package/core/client/components/DashboardLayout.vue +1 -1
  2. package/core/client/composables/index.js +6 -2
  3. package/core/client/eodashSTAC/EodashCollection.js +5 -25
  4. package/core/client/eodashSTAC/createLayers.js +23 -9
  5. package/core/client/eodashSTAC/helpers.js +33 -0
  6. package/core/client/types.ts +8 -1
  7. package/core/client/utils/index.js +4 -0
  8. package/dist/client/{DashboardLayout-CqMb7OXc.js → DashboardLayout-kRsmsNW5.js} +5 -5
  9. package/dist/client/{DynamicWebComponent-DqKqTf7l.js → DynamicWebComponent-CnKhYo0K.js} +1 -1
  10. package/dist/client/{EodashChart-PkUQfH0k.js → EodashChart-CLDqE93C.js} +4 -4
  11. package/dist/client/{EodashDatePicker-DLJGdTtX.js → EodashDatePicker-CMPDT-ce.js} +3 -3
  12. package/dist/client/{EodashItemFilter-Dk8KjFcX.js → EodashItemFilter-BpdA8QQa.js} +1 -1
  13. package/dist/client/{EodashLayerControl-CC1Cogrn.js → EodashLayerControl-BHpDA4_0.js} +25 -7
  14. package/dist/client/{EodashLayoutSwitcher-5WZYd6rr.js → EodashLayoutSwitcher-B1fRUt7G.js} +3 -3
  15. package/dist/client/EodashMapBtns-Cpned0tc.js +478 -0
  16. package/dist/client/{EodashStacInfo-B90wobPc.js → EodashStacInfo-BQspAHKH.js} +1 -1
  17. package/dist/client/{EodashTools-CGZtTh45.js → EodashTools-1zHz26gv.js} +4 -4
  18. package/dist/client/{ExportState-Ce0ZnrX_.js → ExportState-DbOm8hBI.js} +4 -4
  19. package/dist/client/{Footer-CnZqEiop.js → Footer-BKg-VVkj.js} +1 -1
  20. package/dist/client/{Header-Dr5PBIm8.js → Header-DCrwCvmY.js} +2 -2
  21. package/dist/client/MobileLayout-vWSHpluo.js +118 -0
  22. package/dist/client/{PopUp-DjvE_71e.js → PopUp-9uimfK49.js} +2 -2
  23. package/dist/client/{ProcessList-kf3olI7W.js → ProcessList-CTTIZa06.js} +4 -4
  24. package/dist/client/{VImg-Bj6uWt6j.js → VImg-CrmB76ZY.js} +2 -2
  25. package/dist/client/{VMain-Cm0UmKGi.js → VMain-OKZNpcEG.js} +1 -1
  26. package/dist/client/{VTooltip-DbBu-ky3.js → VTooltip-CJw2FyWk.js} +2 -2
  27. package/dist/client/{WidgetsContainer-CfLnd44U.js → WidgetsContainer-BaWOnE5R.js} +1 -1
  28. package/dist/client/{asWebComponent-DfULAbpq.js → asWebComponent-BHWpMuE3.js} +47 -39
  29. package/dist/client/{async-CIbKXEEe.js → async-DSKj6g1f.js} +21 -10
  30. package/dist/client/create-layers-config-CBoYedqB.js +182 -0
  31. package/dist/client/eo-dash.js +1 -1
  32. package/dist/client/{forwardRefs-yj3VWOK5.js → forwardRefs-DH51zPUr.js} +2 -2
  33. package/dist/client/{handling-s8xt2JwF.js → handling-BrcDDlgS.js} +123 -85
  34. package/dist/client/{helpers-DAVGYl_f.js → helpers-DjZVl_WF.js} +2273 -1500
  35. package/dist/client/{index-4TrjfJ32.js → index-1JKE68n3.js} +49 -194
  36. package/dist/client/{index-CugS__t5.js → index-5Jhsa8Xe.js} +3 -3
  37. package/dist/client/index-CSG_JoOA.js +436 -0
  38. package/dist/client/{index-CYz6dT7K.js → index-CUjl-ZsI.js} +2 -2
  39. package/dist/client/{index-Bu8BMA9q.js → index-Cu3-13Xg.js} +69 -83
  40. package/dist/client/material-symbols-outlined.woff2 +0 -0
  41. package/dist/client/material-symbols-rounded.woff2 +0 -0
  42. package/dist/client/material-symbols-sharp.woff2 +0 -0
  43. package/dist/client/material-symbols-subset.woff2 +0 -0
  44. package/dist/client/templates.js +1 -1
  45. package/dist/client/{transition-DaOsiD4u.js → transition-OHcz5Qq1.js} +1 -1
  46. package/dist/node/cli.js +1 -1
  47. package/dist/types/core/client/composables/index.d.ts +1 -3
  48. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +6 -7
  49. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +3 -3
  50. package/dist/types/core/client/eodashSTAC/helpers.d.ts +1 -0
  51. package/dist/types/core/client/types.d.ts +8 -1
  52. package/dist/types/templates/baseConfig.d.ts +12 -0
  53. package/dist/types/widgets/EodashLayerControl.vue.d.ts +1 -1
  54. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +9 -0
  55. package/dist/types/widgets/EodashMap/index.vue.d.ts +7 -0
  56. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -8
  57. package/dist/types/widgets/EodashProcess/methods/composables.d.ts +1 -1
  58. package/dist/types/widgets/EodashProcess/methods/handling.d.ts +13 -8
  59. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +34 -0
  60. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +39 -0
  61. package/dist/types/widgets/EodashTimeSlider/types.d.ts +62 -0
  62. package/package.json +32 -32
  63. package/widgets/EodashLayerControl.vue +22 -3
  64. package/widgets/EodashMap/EodashMapBtns.vue +46 -1
  65. package/widgets/EodashMap/index.vue +17 -5
  66. package/widgets/EodashMap/methods/create-layers-config.js +2 -0
  67. package/widgets/EodashMap/methods/index.js +23 -5
  68. package/widgets/EodashProcess/index.vue +1 -1
  69. package/widgets/EodashProcess/methods/async.js +19 -8
  70. package/widgets/EodashProcess/methods/composables.js +63 -77
  71. package/widgets/EodashProcess/methods/custom-endpoints/chart/sentinelhub-endpoint.js +9 -5
  72. package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +7 -4
  73. package/widgets/EodashProcess/methods/handling.js +105 -72
  74. package/widgets/EodashTimeSlider/index.vue +152 -0
  75. package/widgets/EodashTimeSlider/methods/index.js +279 -0
  76. package/widgets/EodashTimeSlider/types.ts +64 -0
  77. package/dist/client/EodashMapBtns-BHrSDNoC.js +0 -435
  78. package/dist/client/EodashTimeSlider-5vm7F3ju.js +0 -53
  79. package/dist/client/MobileLayout-DyMjaeif.js +0 -118
  80. package/dist/types/widgets/EodashTimeSlider.vue.d.ts +0 -15
  81. package/widgets/EodashTimeSlider.vue +0 -40
@@ -1,5 +1,5 @@
1
- import { t as replaceLayer, b as extractLayerConfig, x as extractLayerLegend, m as mergeGeojsons, d as axios, w as getCompareLayers, v as getLayers, $ as axios$1, I as compareIndicator, F as indicator } from './helpers-DAVGYl_f.js';
2
- import { S as useEmitLayersUpdate } from './asWebComponent-DfULAbpq.js';
1
+ import { t as replaceLayer, b as extractLayerConfig, w as extractLayerLegend, m as mergeGeojsons, d as axios, Y as getCompareLayers, v as getLayers, $ as axios$1, H as compareIndicator, E as indicator } from './helpers-DjZVl_WF.js';
2
+ import { R as useEmitLayersUpdate } from './asWebComponent-BHWpMuE3.js';
3
3
  import { isMulti } from '@eox/jsonform/src/custom-inputs/spatial/utils';
4
4
  import log from 'loglevel';
5
5
 
@@ -649,16 +649,27 @@ async function updateJobsStatus(jobs, indicator) {
649
649
  jobsUrls.map((url) =>
650
650
  axios$1
651
651
  .get(url, { params: { t: Date.now() } })
652
- .then((response) => response.data),
652
+ .then((response) => response.data)
653
+ .catch((error) => {
654
+ if (axios$1.isAxiosError(error)) {
655
+ console.warn(
656
+ `Job URL not accessible: ${url}. Maybe was deleted from the processing endpoint or endpoint is unavailable. Skipping...`,
657
+ );
658
+ return null;
659
+ }
660
+ throw error;
661
+ }),
653
662
  ),
654
663
  );
655
- jobResults.sort((a, b) => {
656
- return (
657
- new Date(b.job_start_datetime).getTime() -
658
- new Date(a.job_start_datetime).getTime()
659
- );
660
- });
661
- jobs.value.splice(0, jobs.value.length, ...jobResults);
664
+ const validResults = jobResults
665
+ .filter((job) => job !== null)
666
+ .sort((a, b) => {
667
+ return (
668
+ new Date(b.job_start_datetime).getTime() -
669
+ new Date(a.job_start_datetime).getTime()
670
+ );
671
+ });
672
+ jobs.value.splice(0, jobs.value.length, ...validResults);
662
673
  }
663
674
 
664
675
  /**
@@ -0,0 +1,182 @@
1
+ import { a1 as EodashCollection } from './asWebComponent-BHWpMuE3.js';
2
+ import log from 'loglevel';
3
+
4
+ /**
5
+ * Creates full layer configuration from indicator and time information
6
+ * @param {import("stac-ts").StacCatalog
7
+ * | import("stac-ts").StacCollection
8
+ * | import("stac-ts").StacItem
9
+ * | null
10
+ * } selectedIndicator
11
+ * @param {EodashCollection[]} eodashCols
12
+ * @param {string | import("stac-ts").StacItem | null} [timeOrItem] - time as a string, or a stac item
13
+ * @returns {Promise<Record<string, any>[]>}
14
+ */
15
+
16
+ const createLayersConfig = async (
17
+ selectedIndicator,
18
+ eodashCols,
19
+ timeOrItem,
20
+ ) => {
21
+ log.debug(
22
+ "Creating layers config",
23
+ selectedIndicator,
24
+ eodashCols,
25
+ timeOrItem,
26
+ );
27
+ const layersCollection = [];
28
+ const dataLayers = {
29
+ type: "Group",
30
+ properties: {
31
+ id: "AnalysisGroup",
32
+ title: "Data Layers",
33
+ layerControlExpand: true,
34
+ },
35
+ layers: /** @type {Record<string,any>[]}*/ ([]),
36
+ };
37
+
38
+ for (const ec of eodashCols) {
39
+ /** @type {Record<string,any>[]} */
40
+ let layers;
41
+ if (timeOrItem) {
42
+ const dateOrItem =
43
+ typeof timeOrItem === "string" ? new Date(timeOrItem) : timeOrItem;
44
+ layers = await ec.createLayersJson(dateOrItem);
45
+ } else {
46
+ layers = await ec.createLayersJson(undefined);
47
+ }
48
+ // Add expand to all analysis layers
49
+ layers.forEach((dl) => {
50
+ dl.properties.layerControlExpand = true;
51
+ dl.properties.layerControlToolsExpand = true;
52
+ });
53
+ dataLayers.layers.push(...layers);
54
+ }
55
+
56
+ /* check for roles in indicator links to assign visibility property for the data layers */
57
+ if (selectedIndicator?.links) {
58
+ const visibilityLinks = selectedIndicator.links.filter(
59
+ (link) =>
60
+ Array.isArray(link.roles) &&
61
+ (link.roles.includes("disable") || link.roles.includes("hidden")),
62
+ );
63
+ if (visibilityLinks.length > 0) {
64
+ visibilityLinks.forEach((vl) => {
65
+ const targetLayerId = vl.id;
66
+ const targetLayer = dataLayers.layers.find(
67
+ (dl) => dl.properties.id.split(";:;")[0] === targetLayerId,
68
+ );
69
+ if (targetLayer) {
70
+ if (Array.isArray(vl.roles) && vl.roles.includes("disable")) {
71
+ targetLayer.properties.visible = false;
72
+ targetLayer.properties.layerControlExpand = false;
73
+ } else if (Array.isArray(vl.roles) && vl.roles.includes("hidden")) {
74
+ targetLayer.properties.layerControlHide = true;
75
+ }
76
+ }
77
+ });
78
+ }
79
+ }
80
+ layersCollection.push(dataLayers);
81
+ const indicatorLayers =
82
+ //@ts-expect-error indicator is collection
83
+ await EodashCollection.getIndicatorLayers(selectedIndicator);
84
+ const observationPointsLayer =
85
+ EodashCollection.getObservationPointsLayer(eodashCols);
86
+ if (observationPointsLayer) {
87
+ dataLayers.layers.push(observationPointsLayer);
88
+ }
89
+ const baseLayers = {
90
+ type: "Group",
91
+ properties: {
92
+ id: "BaseLayersGroup",
93
+ title: "Base Layers",
94
+ },
95
+ layers: /** @type {Record<string,any>[]}*/ ([]),
96
+ };
97
+
98
+ const indicatorBaseLayers = indicatorLayers.filter(
99
+ (l) => l.properties.group === "baselayer",
100
+ );
101
+
102
+ if (indicatorBaseLayers.length) {
103
+ // Only one baselayer can be set to visible, let's first set all to
104
+ // false that have not a dedicated property visible, then check
105
+ // if there are more then one visible and only allow one
106
+ let counter = 0;
107
+ let lastPos = 0;
108
+ for (let indx = 0; indx < indicatorBaseLayers.length; indx++) {
109
+ const bl = indicatorBaseLayers[indx];
110
+ if (!("visible" in bl.properties)) {
111
+ bl.properties.visible = false;
112
+ }
113
+
114
+ if (bl.properties.visible) {
115
+ counter++;
116
+ lastPos = indx;
117
+ }
118
+ }
119
+
120
+ // if none visible set the last one as visible
121
+ if (counter === 0) {
122
+ indicatorBaseLayers[0].properties.visible = true;
123
+ }
124
+
125
+ // disable all apart from last
126
+ if (counter > 0) {
127
+ indicatorBaseLayers.forEach((bl, indx) => {
128
+ if (indx !== lastPos) {
129
+ bl.properties.visible = false;
130
+ } else {
131
+ bl.properties.visible = true;
132
+ }
133
+ });
134
+ }
135
+
136
+ baseLayers.layers.push(...indicatorBaseLayers);
137
+
138
+ // Add exclusive to baselayers and make sure only one is selected
139
+ baseLayers.layers.forEach((bl) => {
140
+ bl.properties.layerControlExclusive = true;
141
+ });
142
+ } else {
143
+ // Default to some baselayer
144
+ baseLayers.layers.push({
145
+ type: "Tile",
146
+ properties: {
147
+ id: "osm",
148
+ title: "Background",
149
+ layerControlExclusive: true,
150
+ },
151
+ source: {
152
+ type: "OSM",
153
+ },
154
+ });
155
+ }
156
+
157
+ if (baseLayers.layers.length) {
158
+ layersCollection.unshift(baseLayers);
159
+ }
160
+
161
+ const overlayLayers = {
162
+ type: "Group",
163
+ properties: {
164
+ id: "OverlayGroup",
165
+ title: "Overlay Layers",
166
+ },
167
+ layers: /** @type {Record<string,any>[]}*/ ([]),
168
+ };
169
+
170
+ const indicatorOverlays = indicatorLayers.filter(
171
+ (l) => l.properties.group === "overlay",
172
+ );
173
+
174
+ if (indicatorOverlays.length) {
175
+ overlayLayers.layers.push(...indicatorOverlays);
176
+ layersCollection.push(overlayLayers);
177
+ }
178
+
179
+ return layersCollection;
180
+ };
181
+
182
+ export { createLayersConfig as c };
@@ -1,2 +1,2 @@
1
- export { aI as EodashConstructor, aJ as register, aK as store } from './asWebComponent-DfULAbpq.js';
1
+ export { aI as EodashConstructor, aJ as register, aK as store } from './asWebComponent-BHWpMuE3.js';
2
2
  import 'vue';
@@ -1,6 +1,6 @@
1
1
  import { computed, watch, onScopeDispose, nextTick, ref, watchEffect, effectScope, inject, mergeProps, toRef, toValue, shallowRef, onMounted, reactive, provide, toRaw, readonly, warn, onBeforeUnmount, createElementVNode, Fragment, createVNode, Teleport, withDirectives, vShow, Transition } from 'vue';
2
- import { aq as isOn, ar as eventName, p as propsFactory, as as destructComputed, at as parseAnchor, au as flipSide, av as flipAlign, aw as flipCorner, ax as CircularBuffer, ay as deepEqual, a0 as consoleError, az as getAxis, q as convertToUnit, x as clamp, aA as IN_BROWSER, A as useToggleScope, aB as defer, al as getCurrentInstance, aC as templateRef, aD as matchesSelector, aE as focusableChildren, G as useDisplay, r as makeThemeProps, d as makeDimensionProps, e as makeComponentProps, g as genericComponent, z as useProxiedModel, l as provideTheme, n as useRtl, f as useBackgroundColor, b as useDimension, aF as useRouter, aG as useBackButton, ao as standardEasing, c as useRender, C as omit } from './asWebComponent-DfULAbpq.js';
3
- import { m as makeTransitionProps, M as MaybeTransition } from './transition-DaOsiD4u.js';
2
+ import { aq as isOn, ar as eventName, p as propsFactory, as as destructComputed, at as parseAnchor, au as flipSide, av as flipAlign, aw as flipCorner, ax as CircularBuffer, ay as deepEqual, $ as consoleError, az as getAxis, q as convertToUnit, x as clamp, aA as IN_BROWSER, A as useToggleScope, aB as defer, al as getCurrentInstance, aC as templateRef, aD as matchesSelector, aE as focusableChildren, G as useDisplay, r as makeThemeProps, d as makeDimensionProps, e as makeComponentProps, g as genericComponent, z as useProxiedModel, l as provideTheme, n as useRtl, f as useBackgroundColor, b as useDimension, aF as useRouter, aG as useBackButton, ao as standardEasing, c as useRender, C as omit } from './asWebComponent-BHWpMuE3.js';
3
+ import { m as makeTransitionProps, M as MaybeTransition } from './transition-OHcz5Qq1.js';
4
4
 
5
5
  class Box {
6
6
  constructor(args) {
@@ -1,8 +1,8 @@
1
1
  import log from 'loglevel';
2
- import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as extractGeometries, i as applyProcessLayersToMap, g as getDrawToolsProperties } from './async-CIbKXEEe.js';
3
- import { d as axios, b as extractLayerConfig, I as compareIndicator, F as indicator, y as extractCollectionUrls, H as currentCompareUrl, G as currentUrl, z as generateLinksFromItems, o as getDatetimeProperty, J as comparePoi, B as datetime, Z as compareChartData, _ as chartData, O as compareChartSpec, N as chartSpec, C as poi, w as getCompareLayers, v as getLayers } from './helpers-DAVGYl_f.js';
2
+ import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as extractGeometries, i as applyProcessLayersToMap, g as getDrawToolsProperties } from './async-DSKj6g1f.js';
3
+ import { d as axios, b as extractLayerConfig, H as compareIndicator, E as indicator, x as extractCollectionUrls, G as currentCompareUrl, F as currentUrl, y as generateLinksFromItems, o as getDatetimeProperty, I as comparePoi, A as datetime, Z as compareChartData, _ as chartData, N as compareChartSpec, M as chartSpec, B as poi, Y as getCompareLayers, v as getLayers } from './helpers-DjZVl_WF.js';
4
4
  import mustache from 'mustache';
5
- import { T as isFirstLoad, H as useSTAcStore, X as readParquetItems, Y as useGetSubCodeId } from './asWebComponent-DfULAbpq.js';
5
+ import { S as isFirstLoad, H as useSTAcStore, W as readParquetItems, X as useGetSubCodeId } from './asWebComponent-BHWpMuE3.js';
6
6
  import { toAbsolute } from 'stac-js/src/http.js';
7
7
 
8
8
  /**
@@ -933,7 +933,7 @@ async function handleSentinelHubProcess({
933
933
  [jsonformValue["start_date"], jsonformValue["end_date"]],
934
934
  jsonformValue["distribution"]
935
935
  );
936
- return await Promise.all(
936
+ const results = await Promise.all(
937
937
  timePairs.map(([to, from]) => {
938
938
  return fetchSentinelHubData({
939
939
  url: endpoint,
@@ -942,14 +942,16 @@ async function handleSentinelHubProcess({
942
942
  from,
943
943
  to,
944
944
  exampleLink: evalScriptLink
945
- }).catch(
946
- (err) => console.error(
945
+ }).catch((err) => {
946
+ console.error(
947
947
  "[eodash] Error while fetching data from sentinel hub endpoint:",
948
948
  err
949
- )
950
- );
949
+ );
950
+ return [];
951
+ });
951
952
  })
952
- ).then((data) => data.flat().map((data2) => data2.outputs.data));
953
+ ).then((data) => data.filter((result) => result.length > 0)).then((data) => data.flat().map((data2) => data2.outputs.data));
954
+ return results;
953
955
  }
954
956
  async function sentinelHubAuth(clientId, clientSecret) {
955
957
  if (!clientId || !clientSecret) {
@@ -1135,7 +1137,7 @@ async function handleVedaEndpoint({
1135
1137
  vedaLink,
1136
1138
  );
1137
1139
  // TODO: convert jsonform bbox type to geojson in the schema to avoid the conversion here
1138
- return await Promise.all(
1140
+ const results = await Promise.all(
1139
1141
  configs.map(({ endpoint, datetime }) => {
1140
1142
  const url = new URL(vedaEndpoint);
1141
1143
  const key = vedaLink.endpoint === "veda_stac" ? "ids" : "url";
@@ -1154,14 +1156,17 @@ async function handleVedaEndpoint({
1154
1156
  fetchedSats.date = datetime;
1155
1157
  return fetchedSats;
1156
1158
  })
1157
- .catch((resp) =>
1159
+ .catch((resp) => {
1158
1160
  console.error(
1159
1161
  "[eodash] Error while fetching data from veda endpoint:",
1160
1162
  resp,
1161
- ),
1162
- );
1163
+ );
1164
+ return null;
1165
+ });
1163
1166
  }),
1164
1167
  );
1168
+ // Filter out the nulls (failed requests) before returning
1169
+ return results.filter((result) => result !== null);
1165
1170
  }
1166
1171
 
1167
1172
  /**
@@ -1292,6 +1297,7 @@ function createCustomChartEndpointsHandler(callbacks) {
1292
1297
  * @param {import("vue").Ref<boolean>} params.isProcessed
1293
1298
  * @param {import("vue").Ref<boolean>} params.loading
1294
1299
  * @param {import("vue").Ref<boolean>} params.isPolling
1300
+ * @param {import("@eox/map").EOxMap | null} params.mapElement
1295
1301
  * @param {boolean} params.enableCompare
1296
1302
  */
1297
1303
  async function initProcess({
@@ -1302,6 +1308,7 @@ async function initProcess({
1302
1308
  loading,
1303
1309
  isPolling,
1304
1310
  enableCompare,
1311
+ mapElement,
1305
1312
  }) {
1306
1313
  const isPoiAlive = enableCompare ? !!comparePoi.value : !!poi.value;
1307
1314
  let updatedJsonform = null;
@@ -1316,6 +1323,27 @@ async function initProcess({
1316
1323
  jsonformSchema.value = null;
1317
1324
  return;
1318
1325
  }
1326
+
1327
+ if (!updatedJsonform) {
1328
+ resetProcess({
1329
+ loading,
1330
+ isProcessed,
1331
+ jsonformSchema,
1332
+ isPolling,
1333
+ processResults,
1334
+ enableCompare,
1335
+ });
1336
+ return;
1337
+ }
1338
+
1339
+ const newJsonForm = await updateJsonformIdentifier({
1340
+ jsonformSchema: updatedJsonform,
1341
+ newLayers: enableCompare ? getCompareLayers() : getLayers(),
1342
+ enableCompare,
1343
+ mapElement,
1344
+ });
1345
+ if (!newJsonForm) return;
1346
+
1319
1347
  resetProcess({
1320
1348
  loading,
1321
1349
  isProcessed,
@@ -1324,99 +1352,109 @@ async function initProcess({
1324
1352
  processResults,
1325
1353
  enableCompare,
1326
1354
  });
1355
+ jsonformSchema.value = newJsonForm;
1356
+ }
1327
1357
 
1328
- if (updatedJsonform) {
1329
- // make sure correct target layer id is used in jsonform
1330
- const newJsonForm = await updateJsonformIdentifier({
1331
- jsonformSchema: updatedJsonform,
1332
- newLayers: enableCompare ? getCompareLayers() : getLayers(),
1333
- enableCompare,
1334
- });
1335
- // trigger jsonform update in next tick
1336
- jsonformSchema.value = null;
1337
- await new Promise((resolve) => setTimeout(resolve, 0));
1338
- jsonformSchema.value = newJsonForm;
1358
+ /**
1359
+ * Recursively search grouped layer tree for a layer whose id starts with the given prefix
1360
+ *
1361
+ * @param {Record<string, any>[]} layers
1362
+ * @param {string} prefix
1363
+ * @returns {string | null}
1364
+ */
1365
+ function findLayerIdByPrefix(layers, prefix) {
1366
+ for (const layer of layers) {
1367
+ if (layer.type === "Group" && Array.isArray(layer.layers)) {
1368
+ const found = findLayerIdByPrefix(layer.layers, prefix);
1369
+ if (found) return found;
1370
+ } else if (layer.properties?.id?.startsWith(prefix)) {
1371
+ return layer.properties.id;
1372
+ }
1339
1373
  }
1374
+ return null;
1340
1375
  }
1341
1376
 
1342
1377
  /**
1343
- * Update the jsonform schema to have the correct layer id from the map
1378
+ * Wait until a layer's features are loaded for the drawtools to attach correctly
1379
+ * Resolves immediately if already loaded.
1380
+ *
1381
+ * @param {import("@eox/map").AnyLayerWithSource | null | undefined} layer
1382
+ * @returns {Promise<void>}
1383
+ */
1384
+ function waitForLayerRender(layer) {
1385
+ return new Promise((resolve, reject) => {
1386
+ if (!layer || layer.get("type") !== "Vector" || !("getSource" in layer))
1387
+ return resolve();
1388
+ const source = /** @type {import("ol/source/Vector").default} */ (
1389
+ layer.getSource()
1390
+ );
1391
+ if (!source || source.getFeatures().length > 0) return resolve();
1392
+ source.once("featuresloadend", () => resolve());
1393
+ source.once("featuresloaderror", () =>
1394
+ reject(new Error(`Layer ${layer.get("id")} failed to load features`)),
1395
+ );
1396
+ });
1397
+ }
1398
+
1399
+ /**
1400
+ * Update the jsonform schema to have the correct layer id from the map.
1401
+ * Returns null if any required layerId cannot be resolved (layer not yet in map).
1402
+ * Callers should treat null as "retry when layers:updated fires".
1344
1403
  *
1345
1404
  * @export
1346
- * @async
1347
1405
  * @param {Object} params
1348
- * @param {Record<string,any> | null} params.jsonformSchema params.jsonformSchema
1349
- * @param {Record<string, any>[] | undefined} params.newLayers params.newLayers
1350
- * @param { boolean } params.enableCompare params.enableCompare
1351
- * @returns {Promise<Record<string,any> | null>} updated jsonform schema
1406
+ * @param {Record<string,any> | null} params.jsonformSchema
1407
+ * @param {Record<string, any>[] | undefined} params.newLayers
1408
+ * @param {boolean} params.enableCompare
1409
+ * @param {import("@eox/map").EOxMap | null | undefined} params.mapElement
1410
+ * @returns {Promise<Record<string,any> | null>} updated schema, or null if a layerId could not be resolved
1352
1411
  */
1353
1412
  async function updateJsonformIdentifier({
1354
1413
  jsonformSchema,
1355
1414
  newLayers,
1356
1415
  enableCompare,
1416
+ mapElement,
1357
1417
  }) {
1358
- const form = jsonformSchema;
1359
- if (!form) {
1360
- return null;
1361
- }
1418
+ if (!jsonformSchema || !newLayers) return null;
1419
+
1420
+ const form = JSON.parse(JSON.stringify(jsonformSchema));
1362
1421
  const drawToolsProperties = getDrawToolsProperties(form);
1363
- drawToolsProperties.forEach((drawToolsProperty) => {
1364
- if (
1365
- drawToolsProperty &&
1366
- form?.properties[drawToolsProperty]?.options?.drawtools?.for &&
1367
- enableCompare
1368
- ) {
1369
- form.properties[drawToolsProperty].options.drawtools.for =
1370
- "eox-map#compare";
1422
+ /** @type {Promise<void>[]} */
1423
+ const renderPromises = [];
1424
+
1425
+ for (const drawToolsProperty of drawToolsProperties) {
1426
+ if (!drawToolsProperty) continue;
1427
+
1428
+ const drawtoolsOptions =
1429
+ form?.properties[drawToolsProperty]?.options?.drawtools;
1430
+ if (!drawtoolsOptions) continue;
1431
+
1432
+ if (drawtoolsOptions.for && enableCompare) {
1433
+ drawtoolsOptions.for = "eox-map#compare";
1371
1434
  }
1372
- if (
1373
- drawToolsProperty &&
1374
- newLayers &&
1375
- form?.properties[drawToolsProperty]?.options?.drawtools?.layerId
1376
- ) {
1377
- // get partial or full id and try to match with correct eoxmap layer
1378
- // check if newLayers is an array or an object with layers property
1379
- let layers = newLayers;
1380
- // @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
1381
- if (newLayers.layers && Array.isArray(newLayers.layers)) {
1382
- // @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
1383
- layers = newLayers.layers;
1384
- }
1385
1435
 
1386
- const layerId =
1387
- form.properties[drawToolsProperty].options.drawtools.layerId.split(
1388
- ";:;",
1389
- )[0];
1390
- let matchedLayerId = null;
1391
- // layers are not flat can be grouped, we need to recursively search
1392
- const traverseLayers = (
1393
- /** @type {Record<string, any>[] | undefined} */ layersArray,
1394
- ) => {
1395
- if (!layersArray) {
1396
- return;
1397
- }
1398
- for (const layer of layersArray) {
1399
- if (layer.type === "Group" && Array.isArray(layer.layers)) {
1400
- traverseLayers(layer.layers);
1401
- } else {
1402
- if (layer.properties?.id?.startsWith(layerId)) {
1403
- matchedLayerId = layer.properties.id;
1404
- break;
1405
- }
1406
- }
1436
+ if (drawtoolsOptions.layerId) {
1437
+ let layer = mapElement?.getLayerById(drawtoolsOptions.layerId);
1438
+ if (!layer) {
1439
+ const prefix = drawtoolsOptions.layerId.split(";:;")[0];
1440
+ const resolvedId = findLayerIdByPrefix(newLayers, prefix);
1441
+ if (!resolvedId) {
1442
+ console.warn(
1443
+ `Could not find matching layer for processing form with id: ${prefix}`,
1444
+ );
1445
+ return null;
1407
1446
  }
1408
- };
1409
- traverseLayers(layers);
1410
- if (matchedLayerId) {
1411
- form.properties[drawToolsProperty].options.drawtools.layerId =
1412
- matchedLayerId;
1413
- } else {
1414
- console.warn(
1415
- `Could not find matching layer for processing form with id: ${layerId}`,
1416
- );
1447
+ drawtoolsOptions.layerId = resolvedId;
1448
+ layer = mapElement?.getLayerById(resolvedId);
1417
1449
  }
1450
+ renderPromises.push(waitForLayerRender(layer));
1418
1451
  }
1452
+ }
1453
+
1454
+ await Promise.all(renderPromises).catch((err) => {
1455
+ console.warn("[eodash] layer load failed before jsonform attach:", err);
1419
1456
  });
1457
+
1420
1458
  return form;
1421
1459
  }
1422
1460