@eodash/eodash 5.6.1 → 5.7.1

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 (133) hide show
  1. package/core/client/components/DashboardLayout.vue +1 -1
  2. package/core/client/components/EodashOverlay.vue +27 -4
  3. package/core/client/composables/DefineEodash.js +0 -1
  4. package/core/client/composables/DefineWidgets.js +3 -1
  5. package/core/client/composables/index.js +6 -2
  6. package/core/client/eodashSTAC/EodashCollection.js +79 -45
  7. package/core/client/eodashSTAC/cql.js +64 -0
  8. package/core/client/eodashSTAC/createLayers.js +192 -125
  9. package/core/client/eodashSTAC/helpers.js +423 -97
  10. package/core/client/eodashSTAC/mosaic.js +416 -0
  11. package/core/client/plugins/axios.js +31 -1
  12. package/core/client/store/actions.js +1 -1
  13. package/core/client/store/stac.js +25 -4
  14. package/core/client/store/states.js +20 -2
  15. package/core/client/types.ts +155 -29
  16. package/core/client/utils/bands-editor/exampleSchema.json +50 -50
  17. package/core/client/utils/index.js +4 -0
  18. package/core/client/utils/states.js +10 -0
  19. package/dist/client/{DashboardLayout-CqMb7OXc.js → DashboardLayout-ePbdvUTE.js} +5 -5
  20. package/dist/client/{DynamicWebComponent-DqKqTf7l.js → DynamicWebComponent-g5SdRbjY.js} +1 -1
  21. package/dist/client/{EodashChart-PkUQfH0k.js → EodashChart-ChrkA5oS.js} +7 -6
  22. package/dist/client/{EodashDatePicker-DLJGdTtX.js → EodashDatePicker-CY1TrsDo.js} +3 -3
  23. package/dist/client/{EodashItemFilter-Dk8KjFcX.js → EodashItemFilter-DzshHDlS.js} +2 -1
  24. package/dist/client/{EodashLayerControl-CC1Cogrn.js → EodashLayerControl-DMCeMkSd.js} +70 -22
  25. package/dist/client/{EodashLayoutSwitcher-5WZYd6rr.js → EodashLayoutSwitcher-iJ8mX1rF.js} +3 -3
  26. package/dist/client/EodashMapBtns-C7Cr7q5Y.js +592 -0
  27. package/dist/client/EodashStacInfo-K7-iuXa7.js +169 -0
  28. package/dist/client/{EodashTools-CGZtTh45.js → EodashTools-BEK7e7JM.js} +4 -4
  29. package/dist/client/{ExportState-Ce0ZnrX_.js → ExportState-CqIzfMYQ.js} +6 -168
  30. package/dist/client/{Footer-CnZqEiop.js → Footer-1P0cmBOZ.js} +1 -1
  31. package/dist/client/{Header-Dr5PBIm8.js → Header-CkkBaBNh.js} +2 -2
  32. package/dist/client/MobileLayout-BPf49kDU.js +118 -0
  33. package/dist/client/{PopUp-DjvE_71e.js → PopUp-DPZq5SWI.js} +4 -168
  34. package/dist/client/{ProcessList-kf3olI7W.js → ProcessList-DuI6WpS1.js} +5 -5
  35. package/dist/client/VDivider-Bj9LG_JV.js +168 -0
  36. package/dist/client/{VImg-Bj6uWt6j.js → VImg-Ddv5OyMz.js} +2 -2
  37. package/dist/client/{VMain-Cm0UmKGi.js → VMain-7yqk_qC3.js} +1 -1
  38. package/dist/client/{VTooltip-DbBu-ky3.js → VTooltip-2hAJyEoa.js} +2 -2
  39. package/dist/client/{WidgetsContainer-CfLnd44U.js → WidgetsContainer-2DOrlwCB.js} +1 -1
  40. package/dist/client/{asWebComponent-DfULAbpq.js → asWebComponent-BlFN2lkB.js} +493 -335
  41. package/dist/client/{async-CIbKXEEe.js → async-B2H8ZwUK.js} +21 -10
  42. package/dist/client/create-layers-config-u_vn5hPk.js +182 -0
  43. package/dist/client/dialog-transition-BrMkMiNa.js +170 -0
  44. package/dist/client/eo-dash.js +1 -1
  45. package/dist/client/{forwardRefs-yj3VWOK5.js → forwardRefs-DAlror6d.js} +8 -87
  46. package/dist/client/{handling-s8xt2JwF.js → handling-B6I1V5eG.js} +123 -85
  47. package/dist/client/{helpers-DAVGYl_f.js → helpers-CBqIUSXJ.js} +3034 -1649
  48. package/dist/client/index-BWq1ZKaV.js +3168 -0
  49. package/dist/client/{index-CYz6dT7K.js → index-CJ9UI3ln.js} +2 -2
  50. package/dist/client/index-DXLz4sFR.js +442 -0
  51. package/dist/client/{index-4TrjfJ32.js → index-DhKU6KqM.js} +230 -231
  52. package/dist/client/{index-Bu8BMA9q.js → index-DjQlbWbJ.js} +70 -83
  53. package/dist/client/material-symbols-outlined.woff2 +0 -0
  54. package/dist/client/material-symbols-rounded.woff2 +0 -0
  55. package/dist/client/material-symbols-sharp.woff2 +0 -0
  56. package/dist/client/material-symbols-subset.woff2 +0 -0
  57. package/dist/client/mosaic-BFYLEwyx.js +455 -0
  58. package/dist/client/templates.js +250 -119
  59. package/dist/client/{transition-DaOsiD4u.js → transition-D_W6Wn9v.js} +1 -1
  60. package/dist/node/cli.js +5 -5
  61. package/dist/node/types.d.ts +4 -0
  62. package/dist/types/core/client/composables/DefineEodash.d.ts +1 -1
  63. package/dist/types/core/client/composables/index.d.ts +1 -3
  64. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +62 -31
  65. package/dist/types/core/client/eodashSTAC/cql.d.ts +15 -0
  66. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +44 -25
  67. package/dist/types/core/client/eodashSTAC/helpers.d.ts +78 -18
  68. package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
  69. package/dist/types/core/client/store/stac.d.ts +31 -7
  70. package/dist/types/core/client/store/states.d.ts +24 -2
  71. package/dist/types/core/client/types.d.ts +131 -25
  72. package/dist/types/core/client/utils/states.d.ts +12 -0
  73. package/dist/types/templates/baseConfig.d.ts +1 -88
  74. package/dist/types/templates/compare.d.ts +32 -3
  75. package/dist/types/templates/expert.d.ts +18 -0
  76. package/dist/types/templates/explore.d.ts +9 -1
  77. package/dist/types/templates/lite.d.ts +11 -0
  78. package/dist/types/widgets/EodashChart.vue.d.ts +5 -3
  79. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +129 -2
  80. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +28 -6
  81. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +17 -4
  82. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +16 -4
  83. package/dist/types/widgets/EodashLayerControl.vue.d.ts +36 -17
  84. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +23 -10
  85. package/dist/types/widgets/EodashMap/index.vue.d.ts +91 -35
  86. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -8
  87. package/dist/types/widgets/EodashMap/methods/index.d.ts +5 -1
  88. package/dist/types/widgets/EodashMap/methods/use-map-loading.d.ts +1 -0
  89. package/dist/types/widgets/EodashProcess/index.vue.d.ts +5 -3
  90. package/dist/types/widgets/EodashProcess/methods/composables.d.ts +1 -1
  91. package/dist/types/widgets/EodashProcess/methods/handling.d.ts +13 -8
  92. package/dist/types/widgets/EodashProcess/states.d.ts +1 -1
  93. package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
  94. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +54 -0
  95. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +34 -0
  96. package/dist/types/widgets/EodashTimeSlider/types.d.ts +52 -0
  97. package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
  98. package/package.json +46 -46
  99. package/templates/baseConfig.js +36 -6
  100. package/templates/compare.js +62 -7
  101. package/templates/expert.js +23 -0
  102. package/templates/explore.js +11 -5
  103. package/templates/lite.js +14 -0
  104. package/widgets/EodashChart.vue +1 -0
  105. package/widgets/EodashItemCatalog/index.vue +363 -35
  106. package/widgets/EodashItemCatalog/methods/filters.js +134 -70
  107. package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
  108. package/widgets/EodashItemCatalog/methods/map.js +142 -28
  109. package/widgets/EodashLayerControl.vue +64 -14
  110. package/widgets/EodashMap/EodashMapBtns.vue +153 -47
  111. package/widgets/EodashMap/index.vue +101 -105
  112. package/widgets/EodashMap/methods/btns.js +37 -5
  113. package/widgets/EodashMap/methods/create-layers-config.js +18 -16
  114. package/widgets/EodashMap/methods/index.js +89 -8
  115. package/widgets/EodashMap/methods/use-map-loading.js +52 -0
  116. package/widgets/EodashProcess/index.vue +2 -1
  117. package/widgets/EodashProcess/methods/async.js +19 -8
  118. package/widgets/EodashProcess/methods/composables.js +63 -77
  119. package/widgets/EodashProcess/methods/custom-endpoints/chart/sentinelhub-endpoint.js +9 -5
  120. package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +7 -4
  121. package/widgets/EodashProcess/methods/handling.js +105 -72
  122. package/widgets/EodashProcess/states.js +1 -1
  123. package/widgets/EodashStacInfo.vue +64 -2
  124. package/widgets/EodashTimeSlider/index.vue +208 -0
  125. package/widgets/EodashTimeSlider/methods/index.js +236 -0
  126. package/widgets/EodashTimeSlider/types.ts +54 -0
  127. package/dist/client/EodashMapBtns-BHrSDNoC.js +0 -435
  128. package/dist/client/EodashStacInfo-B90wobPc.js +0 -107
  129. package/dist/client/EodashTimeSlider-5vm7F3ju.js +0 -53
  130. package/dist/client/MobileLayout-DyMjaeif.js +0 -118
  131. package/dist/client/index-CugS__t5.js +0 -570
  132. package/dist/types/widgets/EodashTimeSlider.vue.d.ts +0 -15
  133. package/widgets/EodashTimeSlider.vue +0 -40
@@ -35,6 +35,7 @@ import { getLayers, getCompareLayers } from "@/store/actions";
35
35
  * @param {import("vue").Ref<boolean>} params.isProcessed
36
36
  * @param {import("vue").Ref<boolean>} params.loading
37
37
  * @param {import("vue").Ref<boolean>} params.isPolling
38
+ * @param {import("@eox/map").EOxMap | null} params.mapElement
38
39
  * @param {boolean} params.enableCompare
39
40
  */
40
41
  export async function initProcess({
@@ -45,6 +46,7 @@ export async function initProcess({
45
46
  loading,
46
47
  isPolling,
47
48
  enableCompare,
49
+ mapElement,
48
50
  }) {
49
51
  const isPoiAlive = enableCompare ? !!comparePoi.value : !!poi.value;
50
52
  let updatedJsonform = null;
@@ -59,6 +61,27 @@ export async function initProcess({
59
61
  jsonformSchema.value = null;
60
62
  return;
61
63
  }
64
+
65
+ if (!updatedJsonform) {
66
+ resetProcess({
67
+ loading,
68
+ isProcessed,
69
+ jsonformSchema,
70
+ isPolling,
71
+ processResults,
72
+ enableCompare,
73
+ });
74
+ return;
75
+ }
76
+
77
+ const newJsonForm = await updateJsonformIdentifier({
78
+ jsonformSchema: updatedJsonform,
79
+ newLayers: enableCompare ? getCompareLayers() : getLayers(),
80
+ enableCompare,
81
+ mapElement,
82
+ });
83
+ if (!newJsonForm) return;
84
+
62
85
  resetProcess({
63
86
  loading,
64
87
  isProcessed,
@@ -67,99 +90,109 @@ export async function initProcess({
67
90
  processResults,
68
91
  enableCompare,
69
92
  });
93
+ jsonformSchema.value = newJsonForm;
94
+ }
70
95
 
71
- if (updatedJsonform) {
72
- // make sure correct target layer id is used in jsonform
73
- const newJsonForm = await updateJsonformIdentifier({
74
- jsonformSchema: updatedJsonform,
75
- newLayers: enableCompare ? getCompareLayers() : getLayers(),
76
- enableCompare,
77
- });
78
- // trigger jsonform update in next tick
79
- jsonformSchema.value = null;
80
- await new Promise((resolve) => setTimeout(resolve, 0));
81
- jsonformSchema.value = newJsonForm;
96
+ /**
97
+ * Recursively search grouped layer tree for a layer whose id starts with the given prefix
98
+ *
99
+ * @param {Record<string, any>[]} layers
100
+ * @param {string} prefix
101
+ * @returns {string | null}
102
+ */
103
+ function findLayerIdByPrefix(layers, prefix) {
104
+ for (const layer of layers) {
105
+ if (layer.type === "Group" && Array.isArray(layer.layers)) {
106
+ const found = findLayerIdByPrefix(layer.layers, prefix);
107
+ if (found) return found;
108
+ } else if (layer.properties?.id?.startsWith(prefix)) {
109
+ return layer.properties.id;
110
+ }
82
111
  }
112
+ return null;
83
113
  }
84
114
 
85
115
  /**
86
- * Update the jsonform schema to have the correct layer id from the map
116
+ * Wait until a layer's features are loaded for the drawtools to attach correctly
117
+ * Resolves immediately if already loaded.
118
+ *
119
+ * @param {import("@eox/map").AnyLayerWithSource | null | undefined} layer
120
+ * @returns {Promise<void>}
121
+ */
122
+ function waitForLayerRender(layer) {
123
+ return new Promise((resolve, reject) => {
124
+ if (!layer || layer.get("type") !== "Vector" || !("getSource" in layer))
125
+ return resolve();
126
+ const source = /** @type {import("ol/source/Vector").default} */ (
127
+ layer.getSource()
128
+ );
129
+ if (!source || source.getFeatures().length > 0) return resolve();
130
+ source.once("featuresloadend", () => resolve());
131
+ source.once("featuresloaderror", () =>
132
+ reject(new Error(`Layer ${layer.get("id")} failed to load features`)),
133
+ );
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Update the jsonform schema to have the correct layer id from the map.
139
+ * Returns null if any required layerId cannot be resolved (layer not yet in map).
140
+ * Callers should treat null as "retry when layers:updated fires".
87
141
  *
88
142
  * @export
89
- * @async
90
143
  * @param {Object} params
91
- * @param {Record<string,any> | null} params.jsonformSchema params.jsonformSchema
92
- * @param {Record<string, any>[] | undefined} params.newLayers params.newLayers
93
- * @param { boolean } params.enableCompare params.enableCompare
94
- * @returns {Promise<Record<string,any> | null>} updated jsonform schema
144
+ * @param {Record<string,any> | null} params.jsonformSchema
145
+ * @param {Record<string, any>[] | undefined} params.newLayers
146
+ * @param {boolean} params.enableCompare
147
+ * @param {import("@eox/map").EOxMap | null | undefined} params.mapElement
148
+ * @returns {Promise<Record<string,any> | null>} updated schema, or null if a layerId could not be resolved
95
149
  */
96
150
  export async function updateJsonformIdentifier({
97
151
  jsonformSchema,
98
152
  newLayers,
99
153
  enableCompare,
154
+ mapElement,
100
155
  }) {
101
- const form = jsonformSchema;
102
- if (!form) {
103
- return null;
104
- }
156
+ if (!jsonformSchema || !newLayers) return null;
157
+
158
+ const form = JSON.parse(JSON.stringify(jsonformSchema));
105
159
  const drawToolsProperties = getDrawToolsProperties(form);
106
- drawToolsProperties.forEach((drawToolsProperty) => {
107
- if (
108
- drawToolsProperty &&
109
- form?.properties[drawToolsProperty]?.options?.drawtools?.for &&
110
- enableCompare
111
- ) {
112
- form.properties[drawToolsProperty].options.drawtools.for =
113
- "eox-map#compare";
160
+ /** @type {Promise<void>[]} */
161
+ const renderPromises = [];
162
+
163
+ for (const drawToolsProperty of drawToolsProperties) {
164
+ if (!drawToolsProperty) continue;
165
+
166
+ const drawtoolsOptions =
167
+ form?.properties[drawToolsProperty]?.options?.drawtools;
168
+ if (!drawtoolsOptions) continue;
169
+
170
+ if (drawtoolsOptions.for && enableCompare) {
171
+ drawtoolsOptions.for = "eox-map#compare";
114
172
  }
115
- if (
116
- drawToolsProperty &&
117
- newLayers &&
118
- form?.properties[drawToolsProperty]?.options?.drawtools?.layerId
119
- ) {
120
- // get partial or full id and try to match with correct eoxmap layer
121
- // check if newLayers is an array or an object with layers property
122
- let layers = newLayers;
123
- // @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
124
- if (newLayers.layers && Array.isArray(newLayers.layers)) {
125
- // @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
126
- layers = newLayers.layers;
127
- }
128
173
 
129
- const layerId =
130
- form.properties[drawToolsProperty].options.drawtools.layerId.split(
131
- ";:;",
132
- )[0];
133
- let matchedLayerId = null;
134
- // layers are not flat can be grouped, we need to recursively search
135
- const traverseLayers = (
136
- /** @type {Record<string, any>[] | undefined} */ layersArray,
137
- ) => {
138
- if (!layersArray) {
139
- return;
140
- }
141
- for (const layer of layersArray) {
142
- if (layer.type === "Group" && Array.isArray(layer.layers)) {
143
- traverseLayers(layer.layers);
144
- } else {
145
- if (layer.properties?.id?.startsWith(layerId)) {
146
- matchedLayerId = layer.properties.id;
147
- break;
148
- }
149
- }
174
+ if (drawtoolsOptions.layerId) {
175
+ let layer = mapElement?.getLayerById(drawtoolsOptions.layerId);
176
+ if (!layer) {
177
+ const prefix = drawtoolsOptions.layerId.split(";:;")[0];
178
+ const resolvedId = findLayerIdByPrefix(newLayers, prefix);
179
+ if (!resolvedId) {
180
+ console.warn(
181
+ `Could not find matching layer for processing form with id: ${prefix}`,
182
+ );
183
+ return null;
150
184
  }
151
- };
152
- traverseLayers(layers);
153
- if (matchedLayerId) {
154
- form.properties[drawToolsProperty].options.drawtools.layerId =
155
- matchedLayerId;
156
- } else {
157
- console.warn(
158
- `Could not find matching layer for processing form with id: ${layerId}`,
159
- );
185
+ drawtoolsOptions.layerId = resolvedId;
186
+ layer = mapElement?.getLayerById(resolvedId);
160
187
  }
188
+ renderPromises.push(waitForLayerRender(layer));
161
189
  }
190
+ }
191
+
192
+ await Promise.all(renderPromises).catch((err) => {
193
+ console.warn("[eodash] layer load failed before jsonform attach:", err);
162
194
  });
195
+
163
196
  return form;
164
197
  }
165
198
 
@@ -4,7 +4,7 @@ import { ref } from "vue";
4
4
  * The list of job result from the server
5
5
  * {job_start_datetime: string, job_end_datetime: string,status: string}
6
6
  * @type {import("vue").Ref<import("./types").AsyncJob[]>}
7
- **/
7
+ */
8
8
  export const jobs = ref([]);
9
9
  /**
10
10
  * The list of jobs results from the server for the compare map
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <div class="flex-grow-1">
3
3
  <eox-stacinfo
4
- .for="currentUrl"
4
+ v-if="stacInfoURL"
5
+ .for="stacInfoURL"
5
6
  .allowHtml="allowHtml"
6
7
  .body="body"
7
8
  .featured="featured"
@@ -9,6 +10,7 @@
9
10
  .header="header"
10
11
  .tags="tags"
11
12
  style="--color-primary-lighter: none"
13
+ .styleOverride="styleOverride"
12
14
  >
13
15
  </eox-stacinfo>
14
16
  </div>
@@ -17,8 +19,21 @@
17
19
  <script setup>
18
20
  import "@eox/stacinfo";
19
21
  import { currentUrl } from "@/store/states";
22
+ import { storeToRefs } from "pinia";
23
+ import { useSTAcStore } from "@/store/stac";
24
+ import { computed, onUnmounted, ref, watch } from "vue";
25
+ import { isSTACItem } from "@/eodashSTAC/helpers";
20
26
 
21
- const { allowHtml, featured, footer, header, body, tags } = defineProps({
27
+ const { level, allowHtml, featured, footer, header, body, tags } = defineProps({
28
+ level: {
29
+ type: /** @type {import("vue").PropType<"item" | "collection">} */ (String),
30
+ default: "collection",
31
+ validator: (/** @type {string} */ v) => ["collection", "item"].includes(v),
32
+ },
33
+ styleOverride: {
34
+ type: String,
35
+ default: "",
36
+ },
22
37
  allowHtml: {
23
38
  type: Boolean,
24
39
  default: true,
@@ -92,4 +107,51 @@ const { allowHtml, featured, footer, header, body, tags } = defineProps({
92
107
  default: () => ["sci:citation", "sci:doi", "sci:publication"],
93
108
  },
94
109
  });
110
+
111
+ const { selectedItem } = storeToRefs(useSTAcStore());
112
+
113
+ /** @type {import("vue").Ref<string | null>} */
114
+ const itemUrl = ref(null);
115
+ /**
116
+ * Active object URL (only set for the blob fallback)
117
+ * @type {string | null}
118
+ */
119
+ let activeItemUrl = null;
120
+
121
+ const revokeItem = () => {
122
+ if (activeItemUrl) {
123
+ URL.revokeObjectURL(activeItemUrl);
124
+ activeItemUrl = null;
125
+ }
126
+ };
127
+
128
+ watch(
129
+ selectedItem,
130
+ (item) => {
131
+ if (level !== "item" || !item) return;
132
+ revokeItem();
133
+ if (!isSTACItem(item)) {
134
+ itemUrl.value = item.href;
135
+ return;
136
+ }
137
+ const selfHref = item?.links?.find((l) => l.rel === "self")?.href;
138
+ if (selfHref) {
139
+ itemUrl.value = selfHref;
140
+ } else if (item) {
141
+ const blob = new Blob([JSON.stringify(item)], {
142
+ type: "application/json",
143
+ });
144
+ activeItemUrl = URL.createObjectURL(blob);
145
+ itemUrl.value = activeItemUrl;
146
+ } else {
147
+ itemUrl.value = null;
148
+ }
149
+ },
150
+ { immediate: true },
151
+ );
152
+ onUnmounted(revokeItem);
153
+
154
+ const stacInfoURL = computed(() =>
155
+ level === "item" ? itemUrl.value : currentUrl.value,
156
+ );
95
157
  </script>
@@ -0,0 +1,208 @@
1
+ <template>
2
+ <eox-timecontrol
3
+ v-if="hasMultipleItems"
4
+ :key="unref(mapEl)"
5
+ ref="eoxTimecontrol"
6
+ .for="mapEl"
7
+ @select="onSelect"
8
+ titleKey="title"
9
+ class="pa-4"
10
+ .externalMapRendering="true"
11
+ .animate="animate"
12
+ .initDate="initDate"
13
+ >
14
+ <div class="d-flex g-10 align-center">
15
+ <eox-timecontrol-date class="flex-grow-1"></eox-timecontrol-date>
16
+ <eox-timecontrol-picker
17
+ .range="false"
18
+ .showDots="true"
19
+ .showItems="true"
20
+ .popup="true"
21
+ ></eox-timecontrol-picker>
22
+
23
+ <eox-itemfilter
24
+ v-if="filters.length"
25
+ class="mx-4"
26
+ .inlineMode="true"
27
+ :showResults="false"
28
+ .filterProperties="filters"
29
+ @filter="onFilter"
30
+ ></eox-itemfilter>
31
+ <eox-timecontrol-timelapse @export="onExport"></eox-timecontrol-timelapse>
32
+ </div>
33
+
34
+ <eox-timecontrol-timeline class="mt-2"></eox-timecontrol-timeline>
35
+ </eox-timecontrol>
36
+ </template>
37
+ <script setup>
38
+ import { datetime, mapEl } from "@/store/states";
39
+ import { eodashCollections } from "@/utils/states";
40
+ import "@eox/timecontrol";
41
+ import "@eox/itemfilter";
42
+
43
+ import { computed, onMounted, ref, unref, useTemplateRef } from "vue";
44
+ import { storeToRefs } from "pinia";
45
+ import { useSTAcStore } from "@/store/stac";
46
+ import { createAnimationLayers } from "./methods";
47
+ import {
48
+ useInitMosaic,
49
+ useScheduleMosaicUpdate,
50
+ useMosaicState,
51
+ } from "@/eodashSTAC/mosaic";
52
+
53
+ const { animate, useMosaic, mosaicIndicators } = defineProps({
54
+ filters: {
55
+ /** @type {import("vue").PropType<import("@eox/itemfilter").EOxItemFilter["filterProperties"]>} */
56
+ type: Array,
57
+ default: () => [],
58
+ },
59
+ animate: {
60
+ type: Boolean,
61
+ default: true,
62
+ },
63
+ useMosaic: {
64
+ type: Boolean,
65
+ default: false,
66
+ },
67
+ mosaicIndicators: {
68
+ /** @type {import("vue").PropType<string[]>} */
69
+ type: Array,
70
+ default: () => [],
71
+ },
72
+ });
73
+
74
+ /** @type {import("vue").ShallowRef<HTMLElement>} */
75
+ const timesliderEl = useTemplateRef("eoxTimecontrol");
76
+
77
+ const startDate = new Date(datetime.value);
78
+ const endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
79
+
80
+ const selectedRange = /** @type {import("vue").Ref<[string, string]>} */ (
81
+ ref([startDate, endDate].map((d) => d.toISOString()))
82
+ );
83
+ const initDate = [startDate.toISOString().split("T")[0]];
84
+
85
+ /** @type {import("vue").Ref<import("@/types").ItemFilterFilters>} */
86
+ const currentFilters = ref({});
87
+
88
+ const hasMultipleItems = computed(() => {
89
+ return eodashCollections.some((ec) => {
90
+ const itemLinks = ec.collectionStac?.links.filter((l) => l.rel === "item");
91
+ const itemsLink = ec.collectionStac?.links.some((l) => l.rel === "items");
92
+ return (itemLinks && itemLinks.length > 1) || itemsLink;
93
+ });
94
+ });
95
+
96
+ const store = useSTAcStore();
97
+ const { selectedStac, stacEndpoint } = storeToRefs(store);
98
+ const { mosaicEndpoint } = useMosaicState();
99
+
100
+ const isMosaicEnabled = computed(() => useMosaic && !!mosaicEndpoint.value);
101
+
102
+ const scheduleMosaicUpdate = useScheduleMosaicUpdate();
103
+
104
+ useInitMosaic(
105
+ useMosaic ? mosaicEndpoint.value : null,
106
+ selectedRange,
107
+ mosaicIndicators,
108
+ );
109
+
110
+ /**
111
+ * Handles the selection event from the time control component.
112
+ * It finds the closest item to the "from" selected date
113
+ * and updates the global datetime state with that item's original date.
114
+ *
115
+ * @param {CustomEvent<import("./types").TimelineSelectionEventDetail>} e
116
+ */
117
+ const onSelect = (e) => {
118
+ const { selectedItems, date } = e.detail;
119
+ // Update the selected range with the new dates
120
+ selectedRange.value = date;
121
+
122
+ // if mosaic is enabled, we don't need to find the closest item,
123
+ // we just update the mosaic layer with the new time range and filters
124
+ if (isMosaicEnabled.value) {
125
+ scheduleMosaicUpdate(
126
+ mosaicEndpoint.value,
127
+ selectedRange.value,
128
+ currentFilters.value,
129
+ );
130
+ return;
131
+ }
132
+
133
+ const allItems = Object.keys(selectedItems ?? {}).flatMap(
134
+ (id) => selectedItems[id],
135
+ );
136
+ if (!allItems.length) {
137
+ return;
138
+ }
139
+ const [from, _to] = date;
140
+ const fromDate = new Date(from).getTime();
141
+
142
+ const closestItem = allItems.reduce((prev, curr) => {
143
+ const prevDiff = Math.abs(new Date(prev.originalDate).getTime() - fromDate);
144
+ const currDiff = Math.abs(new Date(curr.originalDate).getTime() - fromDate);
145
+ return currDiff < prevDiff ? curr : prev;
146
+ });
147
+
148
+ if (closestItem) {
149
+ datetime.value = closestItem.originalDate;
150
+ }
151
+ };
152
+
153
+ /**
154
+ * Handles filter changes from eox-itemfilter (e.g. cloud cover slider).
155
+ * Updates the mosaic layer with the new filter state.
156
+ *
157
+ * @param {CustomEvent<import("./types").ItemFilterEventDetail>} e
158
+ */
159
+ const onFilter = (e) => {
160
+ if (!isMosaicEnabled.value) return;
161
+ const { filters } = e.detail;
162
+ currentFilters.value = filters;
163
+ scheduleMosaicUpdate(mosaicEndpoint.value, selectedRange.value, filters);
164
+ };
165
+
166
+ /**
167
+ *
168
+ * @param {CustomEvent<import("./types").TimelineExportEventDetail>} evt
169
+ */
170
+ const onExport = async (evt) => {
171
+ const { generate, selectedRangeItems, filters } = evt.detail;
172
+
173
+ if (!stacEndpoint.value) {
174
+ return;
175
+ }
176
+
177
+ const mapLayers = await createAnimationLayers(
178
+ stacEndpoint.value,
179
+ selectedRange.value,
180
+ selectedRangeItems,
181
+ selectedStac,
182
+ filters,
183
+ );
184
+
185
+ if (!mapLayers?.length) {
186
+ console.warn("[eodash] No map layers generated for the animation.");
187
+ return;
188
+ }
189
+
190
+ generate({
191
+ mapLayers,
192
+ });
193
+ };
194
+
195
+ onMounted(() => {
196
+ const parentDiv = timesliderEl.value?.parentElement;
197
+ const layoutItem = parentDiv?.parentElement;
198
+ if (parentDiv && layoutItem && animate) {
199
+ parentDiv.style.overflow = "visible";
200
+ layoutItem.style.overflow = "visible";
201
+ }
202
+ });
203
+ </script>
204
+ <style scoped>
205
+ eox-itemfilter {
206
+ --inline-container-height: 40px;
207
+ }
208
+ </style>