@eodash/eodash 5.2.0 → 5.3.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 (119) hide show
  1. package/core/client/components/DashboardLayout.vue +0 -1
  2. package/core/client/composables/index.js +53 -59
  3. package/core/client/eodashSTAC/EodashCollection.js +196 -94
  4. package/core/client/eodashSTAC/auth.js +86 -0
  5. package/core/client/eodashSTAC/createLayers.js +204 -4
  6. package/core/client/eodashSTAC/helpers.js +258 -58
  7. package/core/client/eodashSTAC/parquet.js +0 -13
  8. package/core/client/eodashSTAC/triggers.js +1 -1
  9. package/core/client/store/actions.js +14 -0
  10. package/core/client/store/stac.js +46 -8
  11. package/core/client/store/states.js +6 -0
  12. package/core/client/types.ts +206 -3
  13. package/core/client/utils/bands-editor/arithmetic.js +144 -0
  14. package/core/client/utils/bands-editor/colors.js +36 -0
  15. package/core/client/utils/bands-editor/dom.js +196 -0
  16. package/core/client/utils/bands-editor/exampleSchema.json +1320 -0
  17. package/core/client/utils/bands-editor/index.js +68 -0
  18. package/core/client/utils/bands-editor/rgb.js +102 -0
  19. package/core/client/utils/index.js +5 -2
  20. package/core/client/views/Dashboard.vue +1 -1
  21. package/core/client/vite-env.d.ts +122 -0
  22. package/dist/client/{DashboardLayout-Dq9Kfe6O.js → DashboardLayout-Cq15p4TH.js} +4 -5
  23. package/dist/client/{DynamicWebComponent-DCBMXskE.js → DynamicWebComponent-Cv-fPRG1.js} +1 -1
  24. package/dist/client/{EodashDatePicker-DtngxU6s.js → EodashDatePicker-CPlJwEIO.js} +20 -22
  25. package/dist/client/{EodashItemFilter-ClQebJQt.js → EodashItemFilter-Ydebgbjj.js} +46 -31
  26. package/dist/client/EodashLayerControl-COhrkNEs.js +1517 -0
  27. package/dist/client/{EodashLayoutSwitcher-DQ8SfVDd.js → EodashLayoutSwitcher-pnKhTRZV.js} +4 -4
  28. package/dist/client/EodashMapBtns-Cj0Fx119.js +301 -0
  29. package/dist/client/{EodashStacInfo-Dt1nF06x.js → EodashStacInfo-Dadkg_Nj.js} +1 -1
  30. package/dist/client/EodashTimeSlider-CpoHX0S7.js +53 -0
  31. package/dist/client/{EodashTools-DV5ykmWc.js → EodashTools-UGBG7KC9.js} +10 -7
  32. package/dist/client/{ExportState-B6zZQUmE.js → ExportState-GtJkAqeZ.js} +145 -120
  33. package/dist/client/{Footer-DNhXs8k6.js → Footer-D3ZPG5c4.js} +1 -1
  34. package/dist/client/{Header-BjhN5JY4.js → Header-z6AK-wpN.js} +2 -2
  35. package/dist/client/MobileLayout-BXNsNftb.js +118 -0
  36. package/dist/client/{PopUp-CgpvNr3o.js → PopUp-BbQdjENV.js} +79 -43
  37. package/dist/client/{ProcessList-vecpxThi.js → ProcessList-C6VsdsYI.js} +5 -6
  38. package/dist/client/{VImg-CETuikH2.js → VImg-CxaMSB99.js} +6 -3
  39. package/dist/client/{VMain-Ci9DyaGU.js → VMain-Ds7yw0wj.js} +1 -1
  40. package/dist/client/{VTooltip-J4ac48X7.js → VTooltip-Cze6CEVh.js} +2 -2
  41. package/dist/client/{WidgetsContainer-CCML4TyV.js → WidgetsContainer-D66bj-JJ.js} +1 -1
  42. package/dist/client/asWebComponent-CWbNRdf9.js +8895 -0
  43. package/dist/client/{async-B7jIrM53.js → async-BA7oWCMX.js} +2 -2
  44. package/dist/client/easing-CH0-9wR8.js +35 -0
  45. package/dist/client/eo-dash.js +1 -1
  46. package/dist/client/{forwardRefs-BQclvjMq.js → forwardRefs-BUfxOIo-.js} +58 -45
  47. package/dist/client/{handling-BS24aG1q.js → handling-DlNTtKB-.js} +12 -6
  48. package/dist/client/{helpers-wXK7Ywio.js → helpers-CtE0W7iu.js} +572 -277
  49. package/dist/client/{index-4UCzZi8B.js → index-CeEZIjO6.js} +26 -13
  50. package/dist/client/{index-B2XpdgR6.js → index-CsKbRDeN.js} +63 -36
  51. package/dist/client/{index-9KR-G20t.js → index-D4_NRKrf.js} +2 -2
  52. package/dist/client/index-DeECc3lV.js +571 -0
  53. package/dist/client/templates.js +82 -15
  54. package/dist/client/{transition-yBii4fu6.js → transition-Byvp3L6Y.js} +1 -1
  55. package/dist/node/cli.js +6 -6
  56. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +24 -10
  57. package/dist/types/core/client/eodashSTAC/auth.d.ts +7 -0
  58. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +15 -3
  59. package/dist/types/core/client/eodashSTAC/helpers.d.ts +46 -15
  60. package/dist/types/core/client/plugins/vuetify.d.ts +14 -14
  61. package/dist/types/core/client/store/actions.d.ts +2 -0
  62. package/dist/types/core/client/store/stac.d.ts +16 -7
  63. package/dist/types/core/client/store/states.d.ts +4 -0
  64. package/dist/types/core/client/types.d.ts +170 -2
  65. package/dist/types/core/client/utils/bands-editor/arithmetic.d.ts +8 -0
  66. package/dist/types/core/client/utils/bands-editor/colors.d.ts +15 -0
  67. package/dist/types/core/client/utils/bands-editor/dom.d.ts +42 -0
  68. package/dist/types/core/client/utils/bands-editor/index.d.ts +20 -0
  69. package/dist/types/core/client/utils/bands-editor/rgb.d.ts +15 -0
  70. package/dist/types/core/client/utils/index.d.ts +1 -1
  71. package/dist/types/templates/baseConfig.d.ts +87 -1
  72. package/dist/types/templates/expert.d.ts +6 -6
  73. package/dist/types/templates/explore.d.ts +67 -0
  74. package/dist/types/templates/index.d.ts +1 -1
  75. package/dist/types/templates/{light.d.ts → lite.d.ts} +5 -5
  76. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +21 -0
  77. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +49 -0
  78. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +4 -0
  79. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +12 -0
  80. package/dist/types/widgets/EodashItemCatalog/types.d.ts +14 -0
  81. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +2 -0
  82. package/dist/types/widgets/EodashMap/index.vue.d.ts +108 -2
  83. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -1
  84. package/dist/types/widgets/EodashMap/methods/index.d.ts +1 -1
  85. package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
  86. package/dist/types/widgets/EodashTimeSlider.vue.d.ts +7 -0
  87. package/dist/types/widgets/EodashTools.vue.d.ts +10 -10
  88. package/dist/types/widgets/ExportState.vue.d.ts +2 -0
  89. package/package.json +28 -27
  90. package/templates/baseConfig.js +10 -5
  91. package/templates/compare.js +2 -2
  92. package/templates/expert.js +5 -5
  93. package/templates/explore.js +62 -0
  94. package/templates/index.js +1 -1
  95. package/templates/{light.js → lite.js} +1 -1
  96. package/widgets/EodashDatePicker.vue +15 -18
  97. package/widgets/EodashItemCatalog/index.vue +161 -0
  98. package/widgets/EodashItemCatalog/methods/filters.js +216 -0
  99. package/widgets/EodashItemCatalog/methods/handlers.js +50 -0
  100. package/widgets/EodashItemCatalog/methods/map.js +144 -0
  101. package/widgets/EodashItemCatalog/types.ts +15 -0
  102. package/widgets/EodashItemFilter.vue +35 -28
  103. package/widgets/EodashLayerControl.vue +10 -6
  104. package/widgets/EodashLayoutSwitcher.vue +1 -1
  105. package/widgets/EodashMap/EodashMapBtns.vue +18 -9
  106. package/widgets/EodashMap/index.vue +22 -12
  107. package/widgets/EodashMap/methods/create-layers-config.js +9 -6
  108. package/widgets/EodashMap/methods/index.js +27 -13
  109. package/widgets/EodashProcess/index.vue +17 -1
  110. package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +9 -3
  111. package/widgets/EodashProcess/methods/handling.js +2 -0
  112. package/widgets/EodashProcess/methods/outputs.js +1 -0
  113. package/widgets/EodashTimeSlider.vue +40 -0
  114. package/widgets/EodashTools.vue +7 -3
  115. package/widgets/ExportState.vue +53 -22
  116. package/dist/client/EodashLayerControl-BLBds28C.js +0 -154
  117. package/dist/client/EodashMapBtns-B89_YBDw.js +0 -326
  118. package/dist/client/MobileLayout-JelB6w1G.js +0 -118
  119. package/dist/client/asWebComponent-ZyEzWOOf.js +0 -19092
@@ -0,0 +1,161 @@
1
+ <template>
2
+ <span>
3
+ <v-row class="title align-center justify-space-between">
4
+ <h4>Catalog Items</h4>
5
+ <EodashLayoutSwitcher :target="layoutTarget" :icon="layoutIcon" />
6
+ </v-row>
7
+ <eox-itemfilter
8
+ ref="itemfilter"
9
+ titleProperty="id"
10
+ .imageProperty="imageProperty"
11
+ .subTitleProperty="subTitleProperty"
12
+ .filterProperties="filterProperties"
13
+ .items="items"
14
+ @select="onSelectItem"
15
+ @filter="onFilter"
16
+ @mouseenter:result="onMouseEnterResult"
17
+ @mouseleave:result="onMouseLeaveResult"
18
+ :externalFilter="externalFilterHandler"
19
+ >
20
+ <h4 slot="filterstitle" style="margin: 14px 8px">{{ filtersTitle }}</h4>
21
+ <h4 slot="resultstitle" style="margin: 14px 8px">{{ resultsTitle }}</h4>
22
+ </eox-itemfilter>
23
+ </span>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { onUnmounted, ref, useTemplateRef } from "vue";
28
+ import { useSTAcStore } from "@/store/stac";
29
+ import {
30
+ createExternalFilter,
31
+ createFilterProperties,
32
+ createSubtitleProperty,
33
+ } from "./methods/filters";
34
+ import {
35
+ useSearchOnMapMove,
36
+ useRenderItemsFeatures,
37
+ useRenderOnFeatureHover,
38
+ } from "./methods/map";
39
+ import {
40
+ createOnFilterHandler,
41
+ createOnSelectHandler,
42
+ onMouseEnterResult,
43
+ onMouseLeaveResult,
44
+ } from "./methods/handlers";
45
+ import axios from "@/plugins/axios";
46
+ import { mdiViewDashboard } from "@mdi/js";
47
+ import EodashLayoutSwitcher from "^/EodashLayoutSwitcher.vue";
48
+
49
+ if (!customElements.get("eox-itemfilter")) {
50
+ await import("@eox/itemfilter");
51
+ }
52
+
53
+ // Props definition
54
+ const props = defineProps({
55
+ title: {
56
+ type: String,
57
+ default: "Explore Catalog",
58
+ },
59
+ layoutTarget: {
60
+ type: String,
61
+ default: "lite",
62
+ },
63
+ layoutIcon: {
64
+ type: String,
65
+ default: mdiViewDashboard,
66
+ },
67
+ filtersTitle: {
68
+ type: String,
69
+ default: "Filters:",
70
+ },
71
+ resultsTitle: {
72
+ type: String,
73
+ default: "Items:",
74
+ },
75
+ bboxFilter: {
76
+ type: Boolean,
77
+ default: true,
78
+ },
79
+ imageProperty: {
80
+ type: String,
81
+ default: "assets.thumbnail.href",
82
+ },
83
+ filters: {
84
+ /** @type {import("vue").PropType<import("./types").FiltersConfig>} */
85
+ type: Array,
86
+ default: () => [
87
+ {
88
+ property: "eo:cloud_cover",
89
+ type: "range",
90
+ title: "Cloud Cover (%)",
91
+ min: 0,
92
+ max: 100,
93
+ icon: `<svg style="height: 1rem; transform: translateY(-2px); fill: currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>weather-cloudy</title><path d="M6,19A5,5 0 0,1 1,14A5,5 0 0,1 6,9C7,6.65 9.3,5 12,5C15.43,5 18.24,7.66 18.5,11.03L19,11A4,4 0 0,1 23,15A4,4 0 0,1 19,19H6M19,13H17V12A5,5 0 0,0 12,7C9.5,7 7.45,8.82 7.06,11.19C6.73,11.07 6.37,11 6,11A3,3 0 0,0 3,14A3,3 0 0,0 6,17H19A2,2 0 0,0 21,15A2,2 0 0,0 19,13Z" /></svg>`,
94
+ unitLabel: "%",
95
+ },
96
+ ],
97
+ },
98
+ });
99
+
100
+ // Store and template refs
101
+ const store = useSTAcStore();
102
+ const itemfilterEl = useTemplateRef("itemfilter");
103
+
104
+ // Reactive state
105
+ /** @type {import("vue").Ref<import("@/types").GeoJsonFeature[]>} */
106
+ const currentItems = ref([]);
107
+
108
+ // Initial data fetch
109
+ await axios
110
+ .get(store.stacEndpoint + "/search?limit=100")
111
+ .then((res) => (currentItems.value = res.data.features));
112
+
113
+ const items = currentItems.value;
114
+
115
+ const filterProperties = createFilterProperties(props.filters);
116
+
117
+ const subTitleProperty = createSubtitleProperty(props.filters);
118
+
119
+ const externalFilterHandler = createExternalFilter(
120
+ props.filters,
121
+ props.bboxFilter,
122
+ );
123
+
124
+ // Event handlers
125
+ /**
126
+ * @param {CustomEvent} evt
127
+ */
128
+ const onFilter = createOnFilterHandler(currentItems);
129
+
130
+ /**
131
+ * @param {CustomEvent} evt
132
+ */
133
+ const onSelectItem = createOnSelectHandler(store);
134
+
135
+ // composables
136
+
137
+ // Render items features on the map
138
+ useRenderItemsFeatures(currentItems);
139
+ // Search on map move logic
140
+ useSearchOnMapMove(itemfilterEl, props.bboxFilter);
141
+
142
+ useRenderOnFeatureHover(itemfilterEl);
143
+
144
+ onUnmounted(() => {
145
+ store.selectedItem = null;
146
+ });
147
+ </script>
148
+ <style scoped lang="scss">
149
+ eox-itemfilter {
150
+ flex-basis: 20%;
151
+ height: 100%;
152
+ overflow: hidden !important;
153
+ padding: 1rem;
154
+ --eox-itemfilter-results-color: var(--v-theme-surface) !important;
155
+ }
156
+ .title {
157
+ // padding: 1em;
158
+ padding: 1em;
159
+ margin: 0.2em;
160
+ }
161
+ </style>
@@ -0,0 +1,216 @@
1
+ import { sanitizeBbox } from "@/eodashSTAC/helpers";
2
+ import { indicator, mapEl } from "@/store/states";
3
+ import { useSTAcStore } from "@/store/stac";
4
+
5
+ /**
6
+ *
7
+ * @param {import("../types").FiltersConfig} filtersConfig
8
+ */
9
+ export const createSubtitleProperty = (filtersConfig) => {
10
+ /**
11
+ * @param {Record<string, any>} item
12
+ */ // should be dynamic based on a prop
13
+ return (item) => {
14
+ let subtitle = "";
15
+ filtersConfig.forEach((filter) => {
16
+ const property = filter.property;
17
+ if ((!filter.icon && !filter.unitLabel) || !item.properties[property]) {
18
+ return;
19
+ }
20
+ if (filter.icon) {
21
+ subtitle += filter.icon;
22
+ }
23
+
24
+ let value = item.properties[property];
25
+ if (typeof value === "number" && !Number.isInteger(value)) {
26
+ value = value.toFixed(1);
27
+ }
28
+
29
+ subtitle += value;
30
+
31
+ if (filter.unitLabel) {
32
+ subtitle += filter.unitLabel;
33
+ }
34
+ });
35
+ return subtitle;
36
+ };
37
+ };
38
+
39
+ /**
40
+ * @param {Array<{
41
+ * property: string,
42
+ * type: "range" | "multiselect" | "select",
43
+ * title?: string,
44
+ * min?: number,
45
+ * max?: number,
46
+ * filterKeys?: string[],
47
+ * state?: Record<string, boolean>,
48
+ * placeholder?: string,
49
+ * }>} filtersConfig
50
+ */
51
+ // Transform simple filter configs into eox-itemfilter format
52
+ export const createFilterProperties = (filtersConfig) => {
53
+ const store = useSTAcStore();
54
+ const baseFilters = [
55
+ {
56
+ key: "collection",
57
+ title: "Collections",
58
+ type: "multiselect",
59
+ placeholder: "Select collections",
60
+ inline: false,
61
+ filterKeys: store.stac?.map((col) => col.id) || [],
62
+ ...(indicator.value && { state: { [indicator.value]: true } }),
63
+ },
64
+ // {
65
+ // key: "properties.datetime",
66
+ // title: "Date",
67
+ // type: "range",
68
+ // format: "date",
69
+ // }
70
+ ];
71
+
72
+ const dynamicFilters = filtersConfig
73
+ .map((filter) => {
74
+ const propertyKey = `properties.${filter.property}`;
75
+
76
+ if (filter.type === "range") {
77
+ return {
78
+ key: propertyKey,
79
+ title: filter.title || filter.property,
80
+ type: "range",
81
+ expanded: true,
82
+ filterKeys: [filter.min || 0, filter.max || 100],
83
+ state: {
84
+ min: filter.min ?? 0,
85
+ max: filter.max ?? 100,
86
+ },
87
+ };
88
+ } else if (filter.type === "multiselect") {
89
+ return {
90
+ key: propertyKey,
91
+ title: filter.title || filter.property,
92
+ type: "multiselect",
93
+ placeholder: filter.placeholder || `Select ${filter.property}`,
94
+ inline: false,
95
+ filterKeys: filter.filterKeys || [],
96
+ state: filter.state,
97
+ };
98
+ } else if (filter.type === "select") {
99
+ return {
100
+ key: propertyKey,
101
+ title: filter.title || filter.property,
102
+ type: "select",
103
+ placeholder:
104
+ filter.placeholder || `Select ${filter.title || filter.property}`,
105
+ filterKeys: filter.filterKeys || [],
106
+ state: filter.state,
107
+ };
108
+ }
109
+
110
+ return null;
111
+ })
112
+ .filter(Boolean);
113
+
114
+ return [...baseFilters, ...dynamicFilters];
115
+ };
116
+
117
+ /**
118
+ * Build STAC API filter string from dynamic filters
119
+ * @param {Record<string,any>} filters
120
+ * @param {import("../types").FiltersConfig} propsFilters
121
+ * @returns {string}
122
+ */
123
+ export const buildStacFilters = (filters, propsFilters) => {
124
+ /** @type {string[]} */
125
+ const stacFilters = [];
126
+
127
+ propsFilters.forEach((filterConfig) => {
128
+ const filterKey = `properties.${filterConfig.property}`;
129
+ const filterValue = filters[filterKey];
130
+
131
+ if (!filterValue) return;
132
+
133
+ if (filterConfig.type === "range" && filterValue.state) {
134
+ const { min, max } = filterValue.state;
135
+
136
+ // Add range filters based on configuration
137
+ if (min !== undefined && min > (filterConfig.min || 0)) {
138
+ stacFilters.push(`${filterConfig.property}>=${min}`);
139
+ }
140
+ if (max !== undefined && max < (filterConfig.max || 100)) {
141
+ stacFilters.push(`${filterConfig.property}<=${max}`);
142
+ }
143
+ } else if (
144
+ filterConfig.type === "multiselect" &&
145
+ filterValue.stringifiedState
146
+ ) {
147
+ // Handle multiselect filters
148
+ const selectedValues = filterValue.stringifiedState;
149
+ if (selectedValues.length > 0) {
150
+ stacFilters.push(`${filterConfig.property} IN (${selectedValues})`);
151
+ }
152
+ } else if (filterConfig.type === "select" && filterValue.stringifiedState) {
153
+ // Handle single select filters
154
+ const selectedValue = filterValue.stringifiedState;
155
+ if (selectedValue) {
156
+ stacFilters.push(`${filterConfig.property}='${selectedValue}'`);
157
+ }
158
+ }
159
+ });
160
+
161
+ return stacFilters.join(" AND ");
162
+ };
163
+
164
+ /**
165
+ * Build search URL with proper STAC API parameters
166
+ * @param {Record<string,any>} filters
167
+ * @param {Array<any>} propsFilters
168
+ * @param {boolean} bboxFilter
169
+ * @returns {string}
170
+ */
171
+ export const buildSearchUrl = (filters, propsFilters, bboxFilter) => {
172
+ const store = useSTAcStore();
173
+ const params = new URLSearchParams();
174
+
175
+ // Add collections
176
+ if (filters.collection?.stringifiedState) {
177
+ params.append(
178
+ "collections",
179
+ filters.collection.stringifiedState.replaceAll(" ", ""),
180
+ );
181
+ }
182
+
183
+ if (mapEl.value?.lonLatExtent && bboxFilter) {
184
+ params.append(
185
+ "bbox",
186
+ sanitizeBbox([...mapEl.value.lonLatExtent]).join(","),
187
+ );
188
+ }
189
+
190
+ // Add dynamic filters
191
+ const stacFilter = buildStacFilters(filters, propsFilters);
192
+ if (stacFilter) {
193
+ params.append("filter", stacFilter);
194
+ }
195
+
196
+ // Add limit
197
+ params.append("limit", "100");
198
+
199
+ return `${store.stacEndpoint}/search?${params.toString()}`;
200
+ };
201
+
202
+ /**
203
+ *
204
+ * @param {import("../types").FiltersConfig} propsFilters
205
+ * @param {boolean} bboxFilter
206
+ */
207
+ export const createExternalFilter = (propsFilters, bboxFilter) => {
208
+ /**
209
+ * @param {Array<any>} _items
210
+ * @param {Record<string,any>} filters
211
+ */
212
+ return (_items, filters) => ({
213
+ url: buildSearchUrl(filters, propsFilters, bboxFilter),
214
+ key: "features",
215
+ });
216
+ };
@@ -0,0 +1,50 @@
1
+ import { mapEl } from "@/store/states";
2
+ import { inAndOut } from "ol/easing";
3
+ import { renderItemsFeatures } from "./map";
4
+
5
+ /**
6
+ * @param {import("vue").Ref<import("@/types").GeoJsonFeature[]>} currentItems
7
+ */
8
+ export const createOnFilterHandler = (currentItems) => {
9
+ /** @param {CustomEvent} evt */
10
+ return (evt) => {
11
+ currentItems.value = evt.detail.results;
12
+ renderItemsFeatures(currentItems.value);
13
+ };
14
+ };
15
+ /**
16
+ *
17
+ * @param {ReturnType<typeof import("@/store/stac.js").useSTAcStore>} store
18
+ * @returns
19
+ */
20
+ export const createOnSelectHandler = (store) => {
21
+ /** @param {CustomEvent} evt */
22
+ return async (evt) => {
23
+ const item = /** @type {import("stac-ts").StacItem} */ (evt.detail);
24
+ if (!item) {
25
+ return;
26
+ }
27
+ if (store.selectedStac?.id === item.collection) {
28
+ store.selectedItem = item;
29
+ } else {
30
+ await store.loadSelectedSTAC(item.collection, false, item);
31
+ }
32
+
33
+ mapEl.value?.selectInteractions["stac-items"]?.highlightById([item.id], {
34
+ padding: [100, 100, 100, 100],
35
+ duration: 1200,
36
+ easing: inAndOut,
37
+ });
38
+ };
39
+ };
40
+
41
+ /**
42
+ *
43
+ * @param {CustomEvent} evt
44
+ */
45
+ export const onMouseEnterResult = (evt) => {
46
+ mapEl.value?.selectInteractions["stac-items"]?.highlightById([evt.detail.id]);
47
+ };
48
+ export const onMouseLeaveResult = () => {
49
+ mapEl.value?.selectInteractions["stac-items"]?.highlightById([]);
50
+ };
@@ -0,0 +1,144 @@
1
+ import { useOnLayersUpdate } from "@/composables";
2
+ import { mapEl } from "@/store/states";
3
+ import { onMounted, onUnmounted } from "vue";
4
+
5
+ /**
6
+ *
7
+ * @param {import("@/types").GeoJsonFeature[]} features
8
+ */
9
+ export function renderItemsFeatures(features) {
10
+ let analysisLayers =
11
+ /** @type {import("@eox/map/src/layers").EOxLayerTypeGroup} */ (
12
+ mapEl.value?.layers?.find((l) => l.properties?.id === "AnalysisGroup")
13
+ );
14
+ if (!mapEl.value || !features) {
15
+ return;
16
+ }
17
+ if (!analysisLayers) {
18
+ analysisLayers = {
19
+ type: "Group",
20
+ properties: {
21
+ id: "AnalysisGroup",
22
+ title: "Data Layers",
23
+ },
24
+ layers: [],
25
+ };
26
+ mapEl.value.layers = [analysisLayers, ...mapEl.value.layers.reverse()];
27
+ }
28
+
29
+ const stacItemsLayer = {
30
+ type: "Vector",
31
+ properties: {
32
+ id: "stac-items",
33
+ title: "STAC Items",
34
+ },
35
+ source: {
36
+ type: "Vector",
37
+ url:
38
+ "data:application/geo+json," +
39
+ encodeURIComponent(
40
+ JSON.stringify({ type: "FeatureCollection", features }),
41
+ ),
42
+ format: "GeoJSON",
43
+ },
44
+ style: {
45
+ "fill-color": "transparent",
46
+ "stroke-color": "#003170",
47
+ },
48
+ interactions: [
49
+ {
50
+ type: "select",
51
+ options: {
52
+ id: "stac-items",
53
+ condition: "pointermove",
54
+ style: {
55
+ "stroke-color": "white",
56
+ "stroke-width": 3,
57
+ },
58
+ },
59
+ },
60
+ ],
61
+ };
62
+ const exists = analysisLayers.layers.some(
63
+ (l) => l.properties?.id === "stac-items",
64
+ );
65
+ if (exists) {
66
+ //@ts-expect-error todo
67
+ mapEl.value.addOrUpdateLayer(stacItemsLayer);
68
+ return;
69
+ } else {
70
+ //@ts-expect-error todo
71
+ analysisLayers.layers.unshift(stacItemsLayer);
72
+ mapEl.value.layers = [...mapEl.value.layers].reverse();
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @param {import("vue").Ref<any>} itemFilter
78
+ * @param {boolean} bboxFilter
79
+ */
80
+ export const useSearchOnMapMove = (itemFilter, bboxFilter) => {
81
+ if (!bboxFilter) {
82
+ return;
83
+ }
84
+ /** @type {NodeJS.Timeout} */
85
+ let timeout;
86
+ const handler = () => {
87
+ clearTimeout(timeout);
88
+ timeout = setTimeout(() => {
89
+ itemFilter.value?.search();
90
+ }, 800); // 800ms debounce
91
+ };
92
+ onMounted(() => {
93
+ mapEl.value?.map.on("moveend", handler);
94
+ });
95
+ onUnmounted(() => {
96
+ mapEl.value?.map.un("moveend", handler);
97
+ });
98
+ };
99
+ /**
100
+ *
101
+ * @param {import("vue").Ref<import("@/types").GeoJsonFeature[]>} currentItems
102
+ */
103
+ export const useRenderItemsFeatures = (currentItems) => {
104
+ const renderOnUpdate = () =>
105
+ useOnLayersUpdate(() => {
106
+ // consider cases where this is not needed
107
+ renderItemsFeatures(currentItems.value);
108
+ });
109
+ onMounted(() => {
110
+ renderItemsFeatures(currentItems.value);
111
+ renderOnUpdate();
112
+ });
113
+ };
114
+ /**
115
+ *
116
+ * @param {import("vue").Ref<any>} itemfilterEl
117
+ */
118
+ export function useRenderOnFeatureHover(itemfilterEl) {
119
+ /**
120
+ *
121
+ * @param {CustomEvent} evt
122
+ */
123
+ const handler = (evt) => {
124
+ const itemId = evt.detail?.feature?.getId();
125
+ if (!itemId) {
126
+ return;
127
+ }
128
+ const item = itemfilterEl.value.items?.find(
129
+ //@ts-expect-error todo
130
+ (r) => r.id === itemId,
131
+ );
132
+ if (item) {
133
+ itemfilterEl.value.selectedResult = item;
134
+ }
135
+ };
136
+ onMounted(() => {
137
+ //@ts-expect-error todo
138
+ mapEl.value?.addEventListener("select", handler);
139
+ });
140
+ onUnmounted(() => {
141
+ //@ts-expect-error todo
142
+ mapEl.value?.removeEventListener("select", handler);
143
+ });
144
+ }
@@ -0,0 +1,15 @@
1
+ export interface FilterConfigItem {
2
+ property: string;
3
+ type: "range" | "multiselect" | "select";
4
+ title?: string;
5
+ min?: number;
6
+ max?: number;
7
+ filterKeys?: string[];
8
+ state?: Record<string, boolean>;
9
+ placeholder?: string;
10
+ /** svg icon */
11
+ icon?: string;
12
+ unitLabel?: string;
13
+ }
14
+
15
+ export type FiltersConfig = FilterConfigItem[];
@@ -5,22 +5,26 @@
5
5
  ref="eoxItemFilter"
6
6
  style="overflow: auto; --background-color: none"
7
7
  @select="onSelect"
8
- .items="store.stac?.filter((item) => item.rel === 'child')"
8
+ .items="items"
9
9
  >
10
10
  <h4 slot="filterstitle" style="margin: 14px 8px">{{ filtersTitle }}</h4>
11
-
12
11
  <h4 slot="resultstitle" style="margin: 14px 8px">{{ resultsTitle }}</h4>
13
12
  </eox-itemfilter>
14
13
  </template>
15
14
  <script setup>
16
15
  import { useSTAcStore } from "@/store/stac";
17
16
  import { isFirstLoad } from "@/utils/states";
18
- import "@eox/itemfilter";
19
17
  import { computed, ref } from "vue";
20
18
 
19
+ if (!customElements.get("eox-itemfilter")) {
20
+ await import("@eox/itemfilter");
21
+ }
22
+
21
23
  const store = useSTAcStore();
22
24
  const emit = defineEmits(["select"]);
23
-
25
+ const items = store.isApi
26
+ ? store.stac
27
+ : store.stac?.filter((item) => item.rel === "child");
24
28
  const props = defineProps({
25
29
  enableCompare: {
26
30
  type: Boolean,
@@ -87,31 +91,36 @@ const props = defineProps({
87
91
  },
88
92
  });
89
93
  /**
90
- * @param {import("stac-ts").StacLink} item
94
+ *
95
+ * @param {Function} loader Function to load the item
96
+ * @param {Function} reset Function to reset the selection
91
97
  */
92
- const selectIndicator = async (item) => {
93
- if (item) {
94
- if (isFirstLoad.value) {
95
- // prevent the map from jumping to the initial position
96
- isFirstLoad.value = false;
98
+ const createSelect = (loader, reset) => {
99
+ /**
100
+ * @param {import("stac-ts").StacLink | import("stac-ts").StacCollection} item
101
+ */
102
+ return async (item) => {
103
+ if (item) {
104
+ if (isFirstLoad.value) {
105
+ // prevent the map from jumping to the initial position
106
+ isFirstLoad.value = false;
107
+ }
108
+ const href = /** @type {string} */ (store.isApi ? item.id : item.href);
109
+ await loader(href);
110
+ emit("select", item);
111
+ } else {
112
+ reset();
97
113
  }
98
- await store.loadSelectedSTAC(item.href);
99
- emit("select", item);
100
- } else {
101
- store.selectedStac = null;
102
- }
103
- };
104
- /**
105
- * @param {import("stac-ts").StacLink} item
106
- */
107
- const selectCompareIndicator = (item) => {
108
- if (item) {
109
- store.loadSelectedCompareSTAC(item.href);
110
- emit("select", item);
111
- } else {
112
- store.resetSelectedCompareSTAC();
113
- }
114
+ };
114
115
  };
116
+ const selectIndicator = createSelect(
117
+ store.loadSelectedSTAC,
118
+ () => (store.selectedStac = null),
119
+ );
120
+ const selectCompareIndicator = createSelect(
121
+ store.loadSelectedCompareSTAC,
122
+ store.resetSelectedCompareSTAC,
123
+ );
115
124
  /** @param {any} evt*/
116
125
  const onSelect = async (evt) => {
117
126
  const item = /** @type {import('stac-ts').StacLink} */ evt.detail;
@@ -121,7 +130,6 @@ const onSelect = async (evt) => {
121
130
  selectIndicator(item);
122
131
  }
123
132
  };
124
-
125
133
  const config = computed(() => ({
126
134
  titleProperty: props.titleProperty,
127
135
  enableHighlighting: props.enableHighlighting,
@@ -138,7 +146,6 @@ const config = computed(() => ({
138
146
  /** @type {import("vue").Ref<HTMLElement & Record<string,any> | null>} */
139
147
  const eoxItemFilter = ref(null);
140
148
  </script>
141
-
142
149
  <style scoped>
143
150
  eox-itemfilter {
144
151
  --form-flex-direction: row;