@eodash/eodash 5.7.0 → 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 (113) hide show
  1. package/core/client/components/EodashOverlay.vue +27 -4
  2. package/core/client/composables/DefineEodash.js +0 -1
  3. package/core/client/composables/DefineWidgets.js +3 -1
  4. package/core/client/eodashSTAC/EodashCollection.js +74 -20
  5. package/core/client/eodashSTAC/cql.js +64 -0
  6. package/core/client/eodashSTAC/createLayers.js +169 -116
  7. package/core/client/eodashSTAC/helpers.js +391 -98
  8. package/core/client/eodashSTAC/mosaic.js +416 -0
  9. package/core/client/plugins/axios.js +31 -1
  10. package/core/client/store/actions.js +1 -1
  11. package/core/client/store/stac.js +25 -4
  12. package/core/client/store/states.js +20 -2
  13. package/core/client/types.ts +147 -28
  14. package/core/client/utils/bands-editor/exampleSchema.json +50 -50
  15. package/core/client/utils/states.js +10 -0
  16. package/dist/client/{DashboardLayout-kRsmsNW5.js → DashboardLayout-ePbdvUTE.js} +2 -2
  17. package/dist/client/{DynamicWebComponent-CnKhYo0K.js → DynamicWebComponent-g5SdRbjY.js} +1 -1
  18. package/dist/client/{EodashChart-CLDqE93C.js → EodashChart-ChrkA5oS.js} +7 -6
  19. package/dist/client/{EodashDatePicker-CMPDT-ce.js → EodashDatePicker-CY1TrsDo.js} +3 -3
  20. package/dist/client/{EodashItemFilter-BpdA8QQa.js → EodashItemFilter-DzshHDlS.js} +2 -1
  21. package/dist/client/{EodashLayerControl-BHpDA4_0.js → EodashLayerControl-DMCeMkSd.js} +50 -20
  22. package/dist/client/{EodashLayoutSwitcher-B1fRUt7G.js → EodashLayoutSwitcher-iJ8mX1rF.js} +3 -3
  23. package/dist/client/{EodashMapBtns-Cpned0tc.js → EodashMapBtns-C7Cr7q5Y.js} +200 -86
  24. package/dist/client/EodashStacInfo-K7-iuXa7.js +169 -0
  25. package/dist/client/{EodashTools-1zHz26gv.js → EodashTools-BEK7e7JM.js} +4 -4
  26. package/dist/client/{ExportState-DbOm8hBI.js → ExportState-CqIzfMYQ.js} +6 -168
  27. package/dist/client/{Footer-BKg-VVkj.js → Footer-1P0cmBOZ.js} +1 -1
  28. package/dist/client/{Header-DCrwCvmY.js → Header-CkkBaBNh.js} +2 -2
  29. package/dist/client/{MobileLayout-vWSHpluo.js → MobileLayout-BPf49kDU.js} +2 -2
  30. package/dist/client/{PopUp-9uimfK49.js → PopUp-DPZq5SWI.js} +4 -168
  31. package/dist/client/{ProcessList-CTTIZa06.js → ProcessList-DuI6WpS1.js} +5 -5
  32. package/dist/client/VDivider-Bj9LG_JV.js +168 -0
  33. package/dist/client/{VImg-CrmB76ZY.js → VImg-Ddv5OyMz.js} +2 -2
  34. package/dist/client/{VMain-OKZNpcEG.js → VMain-7yqk_qC3.js} +1 -1
  35. package/dist/client/{VTooltip-CJw2FyWk.js → VTooltip-2hAJyEoa.js} +2 -2
  36. package/dist/client/{WidgetsContainer-BaWOnE5R.js → WidgetsContainer-2DOrlwCB.js} +1 -1
  37. package/dist/client/{asWebComponent-BHWpMuE3.js → asWebComponent-BlFN2lkB.js} +456 -306
  38. package/dist/client/{async-DSKj6g1f.js → async-B2H8ZwUK.js} +2 -2
  39. package/dist/client/{create-layers-config-CBoYedqB.js → create-layers-config-u_vn5hPk.js} +17 -17
  40. package/dist/client/dialog-transition-BrMkMiNa.js +170 -0
  41. package/dist/client/eo-dash.js +1 -1
  42. package/dist/client/{forwardRefs-DH51zPUr.js → forwardRefs-DAlror6d.js} +8 -87
  43. package/dist/client/{handling-BrcDDlgS.js → handling-B6I1V5eG.js} +3 -3
  44. package/dist/client/{helpers-DjZVl_WF.js → helpers-CBqIUSXJ.js} +794 -182
  45. package/dist/client/index-BWq1ZKaV.js +3168 -0
  46. package/dist/client/{index-CUjl-ZsI.js → index-CJ9UI3ln.js} +2 -2
  47. package/dist/client/{index-CSG_JoOA.js → index-DXLz4sFR.js} +62 -56
  48. package/dist/client/{index-1JKE68n3.js → index-DhKU6KqM.js} +211 -67
  49. package/dist/client/{index-Cu3-13Xg.js → index-DjQlbWbJ.js} +7 -6
  50. package/dist/client/mosaic-BFYLEwyx.js +455 -0
  51. package/dist/client/templates.js +250 -119
  52. package/dist/client/{transition-OHcz5Qq1.js → transition-D_W6Wn9v.js} +1 -1
  53. package/dist/node/cli.js +5 -5
  54. package/dist/node/types.d.ts +4 -0
  55. package/dist/types/core/client/composables/DefineEodash.d.ts +1 -1
  56. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +57 -25
  57. package/dist/types/core/client/eodashSTAC/cql.d.ts +15 -0
  58. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +42 -23
  59. package/dist/types/core/client/eodashSTAC/helpers.d.ts +77 -18
  60. package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
  61. package/dist/types/core/client/store/stac.d.ts +31 -7
  62. package/dist/types/core/client/store/states.d.ts +24 -2
  63. package/dist/types/core/client/types.d.ts +123 -24
  64. package/dist/types/core/client/utils/states.d.ts +12 -0
  65. package/dist/types/templates/baseConfig.d.ts +1 -100
  66. package/dist/types/templates/compare.d.ts +32 -3
  67. package/dist/types/templates/expert.d.ts +18 -0
  68. package/dist/types/templates/explore.d.ts +9 -1
  69. package/dist/types/templates/lite.d.ts +11 -0
  70. package/dist/types/widgets/EodashChart.vue.d.ts +5 -3
  71. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +129 -2
  72. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +28 -6
  73. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +17 -4
  74. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +16 -4
  75. package/dist/types/widgets/EodashLayerControl.vue.d.ts +35 -16
  76. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +14 -10
  77. package/dist/types/widgets/EodashMap/index.vue.d.ts +86 -37
  78. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -1
  79. package/dist/types/widgets/EodashMap/methods/index.d.ts +5 -1
  80. package/dist/types/widgets/EodashMap/methods/use-map-loading.d.ts +1 -0
  81. package/dist/types/widgets/EodashProcess/index.vue.d.ts +5 -3
  82. package/dist/types/widgets/EodashProcess/states.d.ts +1 -1
  83. package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
  84. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +20 -0
  85. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +3 -8
  86. package/dist/types/widgets/EodashTimeSlider/types.d.ts +6 -16
  87. package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
  88. package/package.json +33 -33
  89. package/templates/baseConfig.js +36 -6
  90. package/templates/compare.js +62 -7
  91. package/templates/expert.js +23 -0
  92. package/templates/explore.js +11 -5
  93. package/templates/lite.js +14 -0
  94. package/widgets/EodashChart.vue +1 -0
  95. package/widgets/EodashItemCatalog/index.vue +363 -35
  96. package/widgets/EodashItemCatalog/methods/filters.js +134 -70
  97. package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
  98. package/widgets/EodashItemCatalog/methods/map.js +142 -28
  99. package/widgets/EodashLayerControl.vue +42 -11
  100. package/widgets/EodashMap/EodashMapBtns.vue +109 -48
  101. package/widgets/EodashMap/index.vue +89 -105
  102. package/widgets/EodashMap/methods/btns.js +37 -5
  103. package/widgets/EodashMap/methods/create-layers-config.js +16 -16
  104. package/widgets/EodashMap/methods/index.js +71 -8
  105. package/widgets/EodashMap/methods/use-map-loading.js +52 -0
  106. package/widgets/EodashProcess/index.vue +1 -0
  107. package/widgets/EodashProcess/states.js +1 -1
  108. package/widgets/EodashStacInfo.vue +64 -2
  109. package/widgets/EodashTimeSlider/index.vue +58 -2
  110. package/widgets/EodashTimeSlider/methods/index.js +5 -48
  111. package/widgets/EodashTimeSlider/types.ts +7 -17
  112. package/dist/client/EodashStacInfo-BQspAHKH.js +0 -107
  113. package/dist/client/index-5Jhsa8Xe.js +0 -570
@@ -1,17 +1,29 @@
1
1
  import { useOnLayersUpdate } from "@/composables";
2
- import { mapEl } from "@/store/states";
3
2
  import { onMounted, onUnmounted } from "vue";
3
+ import { createOnSelectHandler } from "./handlers";
4
+ import { tooltipAdapter } from "@/store/states";
4
5
 
5
6
  /**
6
7
  *
7
8
  * @param {import("@/types").GeoJsonFeature[]} features
9
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
10
+ * @param {string[] | undefined} hoverProperties
11
+ * @param {object} [stacItemsStyle]
12
+ * @param {object} [stacItemsInteractionStyle]
8
13
  */
9
- export function renderItemsFeatures(features) {
14
+ export function renderItemsFeatures(
15
+ features,
16
+ mapElement,
17
+ hoverProperties,
18
+ stacItemsStyle,
19
+ stacItemsInteractionStyle,
20
+ ) {
21
+ const currentMap = mapElement.value;
10
22
  let analysisLayers =
11
23
  /** @type {import("@eox/map/src/layers").EOxLayerTypeGroup} */ (
12
- mapEl.value?.layers?.find((l) => l.properties?.id === "AnalysisGroup")
24
+ currentMap?.layers?.find((l) => l.properties?.id === "AnalysisGroup")
13
25
  );
14
- if (!mapEl.value || !features) {
26
+ if (!currentMap || !features) {
15
27
  return;
16
28
  }
17
29
  if (!analysisLayers) {
@@ -23,9 +35,9 @@ export function renderItemsFeatures(features) {
23
35
  },
24
36
  layers: [],
25
37
  };
26
- mapEl.value.layers = [...mapEl.value.layers, analysisLayers];
38
+ currentMap.layers = [...currentMap.layers, analysisLayers];
27
39
  }
28
-
40
+ /** @type {import("@eox/map/src/layers").EOxLayerType<"Vector","Vector">} */
29
41
  const stacItemsLayer = {
30
42
  type: "Vector",
31
43
  properties: {
@@ -34,14 +46,16 @@ export function renderItemsFeatures(features) {
34
46
  },
35
47
  source: {
36
48
  type: "Vector",
49
+ //@ts-expect-error todo
37
50
  url:
38
51
  "data:application/geo+json," +
39
52
  encodeURIComponent(
40
53
  JSON.stringify({ type: "FeatureCollection", features }),
41
54
  ),
42
55
  format: "GeoJSON",
56
+ projection: "EPSG:4326",
43
57
  },
44
- style: {
58
+ style: stacItemsStyle || {
45
59
  "fill-color": "transparent",
46
60
  "stroke-color": "#003170",
47
61
  },
@@ -49,35 +63,47 @@ export function renderItemsFeatures(features) {
49
63
  {
50
64
  type: "select",
51
65
  options: {
52
- id: "stac-items",
66
+ id: "stac-item-hover",
53
67
  condition: "pointermove",
54
- style: {
68
+ tooltip: hoverProperties?.length,
69
+ style: stacItemsInteractionStyle || {
55
70
  "stroke-color": "white",
56
71
  "stroke-width": 3,
57
72
  },
58
73
  },
59
74
  },
75
+ {
76
+ type: "select",
77
+ options: {
78
+ id: "stac-items",
79
+ condition: "click",
80
+ tooltip: false,
81
+ style: stacItemsInteractionStyle || {
82
+ "stroke-color": "#003047",
83
+ "stroke-width": 3.2,
84
+ },
85
+ },
86
+ },
60
87
  ],
61
88
  };
62
89
  const exists = analysisLayers.layers.some(
63
- (l) => l.properties?.id === "stac-items",
90
+ (l) => l.properties?.id === stacItemsLayer.properties?.id,
64
91
  );
65
92
  if (exists) {
66
- //@ts-expect-error todo
67
- mapEl.value.addOrUpdateLayer(stacItemsLayer);
93
+ currentMap.addOrUpdateLayer(stacItemsLayer);
68
94
  return;
69
95
  } else {
70
- //@ts-expect-error todo
71
- analysisLayers.layers.push(stacItemsLayer);
72
- mapEl.value.layers = [...mapEl.value.layers];
96
+ analysisLayers.layers.unshift(stacItemsLayer);
97
+ currentMap.layers = [...currentMap.layers];
73
98
  }
74
99
  }
75
100
 
76
101
  /**
77
102
  * @param {import("vue").Ref<any>} itemFilter
78
103
  * @param {boolean} bboxFilter
104
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
79
105
  */
80
- export const useSearchOnMapMove = (itemFilter, bboxFilter) => {
106
+ export const useSearchOnMapMove = (itemFilter, bboxFilter, mapElement) => {
81
107
  if (!bboxFilter) {
82
108
  return;
83
109
  }
@@ -86,58 +112,146 @@ export const useSearchOnMapMove = (itemFilter, bboxFilter) => {
86
112
  const handler = () => {
87
113
  clearTimeout(timeout);
88
114
  timeout = setTimeout(() => {
115
+ if (mapElement.value?.projection == "globe") {
116
+ return;
117
+ }
89
118
  itemFilter.value?.search();
90
- }, 800); // 800ms debounce
119
+ }, 500);
91
120
  };
92
121
  onMounted(() => {
93
- mapEl.value?.map.on("moveend", handler);
122
+ mapElement.value?.map.on("moveend", handler);
94
123
  });
95
124
  onUnmounted(() => {
96
- mapEl.value?.map.un("moveend", handler);
125
+ mapElement.value?.map.un("moveend", handler);
97
126
  });
98
127
  };
99
128
  /**
100
129
  *
101
130
  * @param {import("vue").Ref<import("@/types").GeoJsonFeature[]>} currentItems
131
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
132
+ * @param {string[] | undefined} hoverProperties
133
+ * @param {object} [stacItemsStyle]
134
+ * @param {object} [stacItemsInteractionStyle]
102
135
  */
103
- export const useRenderItemsFeatures = (currentItems) => {
136
+ export const useRenderItemsFeatures = (
137
+ currentItems,
138
+ mapElement,
139
+ hoverProperties,
140
+ stacItemsStyle,
141
+ stacItemsInteractionStyle,
142
+ ) => {
104
143
  onMounted(() => {
105
- renderItemsFeatures(currentItems.value);
144
+ renderItemsFeatures(
145
+ currentItems.value,
146
+ mapElement,
147
+ hoverProperties,
148
+ stacItemsStyle,
149
+ stacItemsInteractionStyle,
150
+ );
106
151
  });
107
152
 
108
153
  useOnLayersUpdate(() => {
109
154
  // consider cases where this is not needed
110
- renderItemsFeatures(currentItems.value);
155
+ renderItemsFeatures(
156
+ currentItems.value,
157
+ mapElement,
158
+ hoverProperties,
159
+ stacItemsStyle,
160
+ stacItemsInteractionStyle,
161
+ );
111
162
  });
112
163
  };
164
+ /**
165
+ * Registers a tooltip adapter that displays item properties when hovering over map features.
166
+ * @param {string[]} [hoverProperties]
167
+ */
168
+ export function useHoverTooltip(hoverProperties) {
169
+ /**
170
+ * @param {*} val
171
+ */
172
+ const formatValue = (val) => {
173
+ if (typeof val === "number") {
174
+ return val.toFixed(0);
175
+ }
176
+
177
+ if (typeof val === "string") {
178
+ // Attempt to parse as date if string looks like ISO date
179
+ if (val.length > 9 && /\d{4}-\d{2}-\d{2}/.test(val)) {
180
+ const timestamp = Date.parse(val);
181
+ if (!isNaN(timestamp)) {
182
+ return new Date(timestamp).toUTCString();
183
+ }
184
+ }
185
+ }
186
+ return val;
187
+ };
188
+ /**
189
+ * @param {string} key
190
+ */
191
+ const formatKey = (key) => {
192
+ let formattedKey = key;
193
+ if (key.includes(":")) {
194
+ formattedKey = key.split(":")[1];
195
+ }
196
+ return formattedKey
197
+ .replace(/_/g, " ")
198
+ .replace(/\b\w/g, (c) => c.toUpperCase());
199
+ };
200
+ onMounted(() => {
201
+ tooltipAdapter.value = ({ key, value }) => {
202
+ if (hoverProperties && hoverProperties.includes(key)) {
203
+ return { key: formatKey(key), value: formatValue(value) };
204
+ }
205
+ return undefined;
206
+ };
207
+ });
208
+ onUnmounted(() => {
209
+ tooltipAdapter.value = null;
210
+ });
211
+ }
212
+
113
213
  /**
114
214
  *
115
215
  * @param {import("vue").Ref<any>} itemfilterEl
216
+ * @param {ReturnType< typeof import("@/store/stac").useSTAcStore>} store
217
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
218
+ * @param {boolean} enableCompare
116
219
  */
117
- export function useHighlightOnFeatureHover(itemfilterEl) {
220
+ export function useRenderOnFeatureClick(
221
+ itemfilterEl,
222
+ store,
223
+ mapElement,
224
+ enableCompare,
225
+ ) {
226
+ const onSelectItem = createOnSelectHandler(store, enableCompare, mapElement);
118
227
  /**
119
228
  *
120
229
  * @param {CustomEvent} evt
121
230
  */
122
231
  const handler = (evt) => {
232
+ if (evt.detail.originalEvent.type !== "click") {
233
+ return;
234
+ }
123
235
  const itemId = evt.detail?.feature?.getId();
124
236
  if (!itemId) {
125
237
  return;
126
238
  }
127
- const item = itemfilterEl.value.items?.find(
239
+ const item = itemfilterEl.value.results?.find(
128
240
  //@ts-expect-error todo
129
- (r) => r.id === itemId,
241
+ (r) => r?.id === itemId,
130
242
  );
243
+
131
244
  if (item) {
132
- itemfilterEl.value.selectedResult = item;
245
+ onSelectItem(new CustomEvent("select", { detail: item }));
133
246
  }
134
247
  };
248
+
135
249
  onMounted(() => {
136
250
  //@ts-expect-error todo
137
- mapEl.value?.addEventListener("select", handler);
251
+ mapElement.value?.addEventListener("select", handler);
138
252
  });
139
253
  onUnmounted(() => {
140
254
  //@ts-expect-error todo
141
- mapEl.value?.removeEventListener("select", handler);
255
+ mapElement.value?.removeEventListener("select", handler);
142
256
  });
143
257
  }
@@ -5,19 +5,24 @@
5
5
  :key="mapElement"
6
6
  v-bind="config"
7
7
  :for="mapElement"
8
+ .showLayerZoomState="true"
8
9
  .customEditorInterfaces="bandsEditorInterface"
9
10
  @datetime:updated="debouncedHandleDateTime"
10
11
  toolsAsList="true"
11
12
  ref="eoxLayercontrol"
12
13
  @layerConfig:change="onLayerConfigChange"
13
14
  >
14
- <slot name="layerstitle">
15
- <div>
16
- <p v-if="title" class="mt-2 mb-2">
17
- <strong>{{ title }}</strong>
18
- </p>
19
- </div>
20
- </slot>
15
+ <span
16
+ slot="layerstitle"
17
+ class="d-flex justify-space-between ma-2 pa-2 flex-shrink-0"
18
+ >
19
+ <h4 v-if="title">{{ title }}</h4>
20
+ <EodashLayoutSwitcher
21
+ v-if="enableLayoutSwitcher"
22
+ :target="layoutTarget"
23
+ :icon="layoutIcon"
24
+ />
25
+ </span>
21
26
  </eox-layercontrol>
22
27
  </span>
23
28
  </template>
@@ -33,9 +38,12 @@ import {
33
38
  layerControlFormValue,
34
39
  layerControlFormValueCompare,
35
40
  } from "@/utils/states";
41
+ import { updateGeoZarrBands } from "@/eodashSTAC/helpers";
36
42
  import { storeToRefs } from "pinia";
37
43
  import { useSTAcStore } from "@/store/stac";
38
44
  import { bandsEditorInterface } from "@/utils/bands-editor";
45
+ import EodashLayoutSwitcher from "^/EodashLayoutSwitcher.vue";
46
+ import { mdiViewDashboard } from "@mdi/js";
39
47
  import { useEmitLayersUpdate } from "@/composables";
40
48
 
41
49
  if (!customElements.get("eox-layercontrol")) {
@@ -46,22 +54,39 @@ if (!customElements.get("eox-jsonform")) {
46
54
  }
47
55
 
48
56
  const props = defineProps({
57
+ /** Which map instance this control is bound to. Use `"second"` in a compare-mode layout. */
49
58
  map: {
50
59
  /** @type {import("vue").PropType<"first" | "second">} */
51
60
  //@ts-expect-error todo
52
61
  type: String,
53
62
  default: "first",
54
63
  },
64
+ /** Tool tabs shown inside `eox-layercontrol`. Remove entries to hide individual tabs. */
55
65
  tools: {
56
- type: Array,
66
+ type: /** @type {import("vue").PropType<string[]>} */ (Array),
57
67
  default: () => ["datetime", "info", "config", "legend", "opacity"],
58
68
  },
69
+ /** Heading rendered above the layer list. Set to `false` to hide it. */
59
70
  title: {
60
- type: String || Boolean,
71
+ type: /** @type {import("vue").PropType<string | false>} */ ([
72
+ String,
73
+ Boolean,
74
+ ]),
61
75
  default: "Layers",
62
76
  },
77
+ /** CSS custom-property overrides forwarded to the underlying `eox-layercontrol` element via its `style` attribute. */
63
78
  cssVars: {
64
- type: Object,
79
+ type: /** @type {import("vue").PropType<Record<string, string>>} */ (
80
+ Object
81
+ ),
82
+ default: {},
83
+ },
84
+ layoutIcon: {
85
+ type: String,
86
+ default: mdiViewDashboard,
87
+ },
88
+ layoutTarget: {
89
+ type: String,
65
90
  },
66
91
  });
67
92
 
@@ -70,6 +95,10 @@ const config = {
70
95
  style: props.cssVars,
71
96
  };
72
97
 
98
+ const enableLayoutSwitcher = computed(
99
+ () => !!props.layoutTarget && !!props.layoutIcon,
100
+ );
101
+
73
102
  const { selectedCompareStac, selectedStac } = storeToRefs(useSTAcStore());
74
103
 
75
104
  const showControls = computed(() => {
@@ -83,7 +112,7 @@ const eodashCols =
83
112
  props.map === "second" ? eodashCompareCollections : eodashCollections;
84
113
  const mapElement = props.map === "second" ? mapCompareEl : mapEl;
85
114
 
86
- /** @type { import("vue").Ref<HTMLElement & Record<string,any> | null>} */
115
+ /** @type { import("vue").Ref<import("@eox/layercontrol").EOxLayerControl | null>} */
87
116
  const eoxLayercontrol = ref(null);
88
117
 
89
118
  // eox-timecontrol re-fires datetime:updated after layer reassignment;
@@ -155,6 +184,8 @@ const debouncedHandleDateTime = (evt) => {
155
184
  * @param {Event & {detail:{layer:import("ol/layer").Layer;jsonformValue:Record<string,any>}}} evt
156
185
  */
157
186
  const onLayerConfigChange = (evt) => {
187
+ updateGeoZarrBands(evt.detail.layer, evt.detail.jsonformValue);
188
+
158
189
  if (props.map === "second") {
159
190
  layerControlFormValueCompare.value = evt.detail.jsonformValue;
160
191
  } else {
@@ -5,30 +5,28 @@
5
5
  class="primary small circle small-elevate"
6
6
  @click="onMapZoomIn"
7
7
  >
8
- <i class="small"
9
- ><svg viewBox="0 0 24 24"><path :d="mdiPlus" /></svg>
8
+ <i class="small">
9
+ <svg viewBox="0 0 24 24"><path :d="mdiPlus"></path></svg>
10
10
  </i>
11
11
  <div class="tooltip left">Zoom in</div>
12
12
  </button>
13
-
14
13
  <button
15
14
  v-if="enableZoom && !isGlobe"
16
15
  class="primary small circle small-elevate"
17
16
  @click="onMapZoomOut"
18
17
  >
19
- <i class="small"
20
- ><svg viewBox="0 0 24 24"><path :d="mdiMinus" /></svg>
18
+ <i class="small">
19
+ <svg viewBox="0 0 24 24"><path :d="mdiMinus"></path></svg>
21
20
  </i>
22
21
  <div class="tooltip left">Zoom out</div>
23
22
  </button>
24
-
25
23
  <button
26
24
  v-if="exportMap"
27
25
  class="primary small circle small-elevate"
28
26
  @click="showMapState = !showMapState"
29
27
  >
30
- <i class="small"
31
- ><svg viewBox="0 0 24 24"><path :d="mdiMapPlus" /></svg>
28
+ <i class="small">
29
+ <svg viewBox="0 0 24 24"><path :d="mdiMapPlus"></path></svg>
32
30
  </i>
33
31
  <div class="tooltip left">Extract storytelling configuration</div>
34
32
  </button>
@@ -39,8 +37,8 @@
39
37
  class="primary small circle small-elevate"
40
38
  @click="changeMapProjection(availableMapProjection)"
41
39
  >
42
- <i class="small"
43
- ><svg viewBox="0 0 24 24"><path :d="mdiEarthBox" /></svg>
40
+ <i class="small">
41
+ <svg viewBox="0 0 24 24"><path :d="mdiEarthBox"></path></svg>
44
42
  </i>
45
43
  <div class="tooltip left">Change map projection</div>
46
44
  </button>
@@ -49,23 +47,11 @@
49
47
  class="primary small circle small-elevate"
50
48
  @click="onCompareClick(compareIndicators)"
51
49
  >
52
- <i class="small"
53
- ><svg viewBox="0 0 24 24"><path :d="compareIcon" /></svg>
50
+ <i class="small">
51
+ <svg viewBox="0 0 24 24"><path :d="compareIcon"></path></svg>
54
52
  </i>
55
53
  <div class="tooltip left">Compare mode</div>
56
54
  </button>
57
- <button
58
- v-if="backToPOIs && (poi || comparePoi)"
59
- class="primary small circle small-elevate"
60
- @click="loadPOiIndicator()"
61
- >
62
- <i class="small"
63
- ><svg viewBox="0 0 24 24">
64
- <path :d="mdiStarFourPointsCircleOutline" />
65
- </svg>
66
- </i>
67
- <div class="tooltip left">Back to POIs</div>
68
- </button>
69
55
  <button
70
56
  v-if="enableGlobe && !isInCompareMode"
71
57
  class="primary small circle small-elevate"
@@ -102,20 +88,36 @@
102
88
  @close="showFeedback = false"
103
89
  ></eox-feedback>
104
90
  </div>
105
- <eox-geosearch
91
+ <button
106
92
  v-if="mapEl && !isGlobe && enableSearch"
107
- :for="mapEl"
108
- :endpoint="opencageUrl"
109
- :params="searchParams"
110
- class="geosearch-detached"
111
- label="Search"
112
- small
113
- button
114
- list-direction="left"
115
- results-direction="down"
116
- tooltip="Search"
117
- tooltip-direction="left"
118
- ></eox-geosearch>
93
+ class="primary small circle small-elevate"
94
+ >
95
+ <eox-geosearch
96
+ :for="mapEl"
97
+ :endpoint="opencageUrl"
98
+ :params="searchParams"
99
+ class="geosearch-detached"
100
+ label="Search"
101
+ small
102
+ button
103
+ list-direction="left"
104
+ results-direction="down"
105
+ tooltip="Search"
106
+ tooltip-direction="left"
107
+ ></eox-geosearch>
108
+ </button>
109
+ <button
110
+ v-if="backToPOIs && (poi || comparePoi)"
111
+ class="primary small circle small-elevate"
112
+ @click="loadPOiIndicator()"
113
+ >
114
+ <i class="small">
115
+ <svg viewBox="0 0 24 24">
116
+ <path :d="mdiStarFourPointsCircleOutline"></path>
117
+ </svg>
118
+ </i>
119
+ <div class="tooltip left">Back to POIs</div>
120
+ </button>
119
121
  <PopUp
120
122
  v-model="showCompareIndicators"
121
123
  :maxWidth="popupWidth"
@@ -126,9 +128,29 @@
126
128
  <EodashItemFilter
127
129
  v-bind="itemFilterConfig"
128
130
  :enableCompare="true"
129
- @select="onSelectCompareIndicator"
131
+ @select="onSelectCompareIndicator(compareIndicators)"
130
132
  />
131
133
  </PopUp>
134
+ <v-alert
135
+ v-if="showMosaicHint"
136
+ class="mosaic-hint pa-2"
137
+ color="secondary"
138
+ type="info"
139
+ variant="elevated"
140
+ density="compact"
141
+ elevation="4"
142
+ >
143
+ <template v-if="showItemViewHint">
144
+ Viewing individual item —
145
+ <a class="mosaic-hint-link" @click.prevent="returnToOverview.emit()">
146
+ Back to overview
147
+ </a>
148
+ </template>
149
+ <template v-else-if="showZoomHint">Zoom in to explore the data</template>
150
+ <template v-else-if="showNoDataHint">
151
+ No data here - pan or change filters
152
+ </template>
153
+ </v-alert>
132
154
  </div>
133
155
  </template>
134
156
  <script setup>
@@ -140,8 +162,10 @@ import {
140
162
  comparePoi,
141
163
  isGlobe,
142
164
  mapEl,
165
+ mapPosition,
143
166
  poi,
144
167
  } from "@/store/states";
168
+ import { useMosaicState, normalizeGlobeZoom } from "@/eodashSTAC/mosaic";
145
169
  import {
146
170
  mdiCompare,
147
171
  mdiCompareRemove,
@@ -195,11 +219,13 @@ const {
195
219
  default: true,
196
220
  },
197
221
  compareIndicators: {
198
- /** @type {import("vue").PropType<boolean | {
199
- compareTemplate?:string;
200
- fallbackTemplate?:string;
201
- itemFilterConfig?:Partial<InstanceType<import("./EodashItemFilter.vue").default>["$props"]>
202
- }> }*/
222
+ /**
223
+ * @type {import("vue").PropType<boolean | {
224
+ * compareTemplate?:string;
225
+ * fallbackTemplate?:string;
226
+ * itemFilterConfig?:Partial<InstanceType<import("./EodashItemFilter.vue").default>["$props"]>
227
+ * }> }
228
+ */
203
229
  type: [Boolean, Object],
204
230
  default: true,
205
231
  },
@@ -267,6 +293,32 @@ useTransparentPanel(rootRef);
267
293
 
268
294
  const opencageApiKey = process.env.EODASH_OPENCAGE || "NO_KEY_FOUND";
269
295
  const opencageUrl = `https://api.opencagedata.com/geocode/v1/json?key=${opencageApiKey}`;
296
+
297
+ const {
298
+ latestLayer,
299
+ isItemView,
300
+ visibilityThreshold,
301
+ returnToOverview,
302
+ hasDataInView,
303
+ } = useMosaicState();
304
+
305
+ const showItemViewHint = computed(
306
+ () => isItemView.value && !!latestLayer.value,
307
+ );
308
+
309
+ const showZoomHint = computed(() => {
310
+ if (!latestLayer.value || isItemView.value) return false;
311
+ const rawZ = mapPosition.value?.[2] ?? 4;
312
+ return normalizeGlobeZoom(rawZ) < visibilityThreshold.value;
313
+ });
314
+
315
+ const showNoDataHint = computed(
316
+ () => !!latestLayer.value && !isItemView.value && !hasDataInView.value,
317
+ );
318
+
319
+ const showMosaicHint = computed(
320
+ () => showItemViewHint.value || showZoomHint.value || showNoDataHint.value,
321
+ );
270
322
  </script>
271
323
 
272
324
  <style scoped>
@@ -296,11 +348,20 @@ const opencageUrl = `https://api.opencagedata.com/geocode/v1/json?key=${opencage
296
348
  pointer-events: auto !important;
297
349
  }
298
350
 
299
- /* Container constraints removal */
300
- eox-geosearch {
301
- position: relative !important;
302
- width: 40px;
303
- overflow: visible !important;
351
+ .mosaic-hint {
352
+ position: fixed;
353
+ left: 50%;
354
+ transform: translateX(-50%);
304
355
  z-index: 10;
356
+ opacity: 0.8;
357
+ border-radius: 8px;
358
+ pointer-events: auto;
359
+ }
360
+
361
+ .mosaic-hint-link {
362
+ color: inherit;
363
+ cursor: pointer;
364
+ text-decoration: underline;
365
+ pointer-events: auto;
305
366
  }
306
367
  </style>