@eodash/eodash 5.2.0 → 5.3.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 (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 +254 -62
  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-BAstYnhU.js} +4 -5
  23. package/dist/client/{DynamicWebComponent-DCBMXskE.js → DynamicWebComponent-7v4_DFqP.js} +1 -1
  24. package/dist/client/{EodashDatePicker-DtngxU6s.js → EodashDatePicker-IVHLv9UN.js} +20 -22
  25. package/dist/client/{EodashItemFilter-ClQebJQt.js → EodashItemFilter-BPMpnXjo.js} +46 -31
  26. package/dist/client/EodashLayerControl-CSnQh2tb.js +1517 -0
  27. package/dist/client/{EodashLayoutSwitcher-DQ8SfVDd.js → EodashLayoutSwitcher-CPpGM8Pb.js} +4 -4
  28. package/dist/client/EodashMapBtns-C_jyUJ2x.js +301 -0
  29. package/dist/client/{EodashStacInfo-Dt1nF06x.js → EodashStacInfo-DjuWc0Iz.js} +1 -1
  30. package/dist/client/EodashTimeSlider-CDh9Lf02.js +53 -0
  31. package/dist/client/{EodashTools-DV5ykmWc.js → EodashTools-DSvDUUlL.js} +10 -7
  32. package/dist/client/{ExportState-B6zZQUmE.js → ExportState-BhjxS0jG.js} +145 -120
  33. package/dist/client/{Footer-DNhXs8k6.js → Footer-C3PPcdjv.js} +1 -1
  34. package/dist/client/{Header-BjhN5JY4.js → Header-E5NbT7HE.js} +2 -2
  35. package/dist/client/MobileLayout-DY7OHr1k.js +118 -0
  36. package/dist/client/{PopUp-CgpvNr3o.js → PopUp-CSPXdqKI.js} +79 -43
  37. package/dist/client/{ProcessList-vecpxThi.js → ProcessList-C3HV7G0b.js} +5 -6
  38. package/dist/client/{VImg-CETuikH2.js → VImg-FoXcOnWF.js} +6 -3
  39. package/dist/client/{VMain-Ci9DyaGU.js → VMain-Ck2g1QOG.js} +1 -1
  40. package/dist/client/{VTooltip-J4ac48X7.js → VTooltip-F_1Zcvhp.js} +2 -2
  41. package/dist/client/{WidgetsContainer-CCML4TyV.js → WidgetsContainer-Cq9uZEuN.js} +1 -1
  42. package/dist/client/asWebComponent-DZeEbWG0.js +8895 -0
  43. package/dist/client/{async-B7jIrM53.js → async-Dk79llLt.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-BbvoXHtj.js} +58 -45
  47. package/dist/client/{handling-BS24aG1q.js → handling-DxucYlYh.js} +12 -6
  48. package/dist/client/{helpers-wXK7Ywio.js → helpers-CI_7CUmn.js} +568 -281
  49. package/dist/client/index-BO5uGfUe.js +571 -0
  50. package/dist/client/{index-9KR-G20t.js → index-C13BiO9C.js} +2 -2
  51. package/dist/client/{index-4UCzZi8B.js → index-DcCcdbgR.js} +26 -13
  52. package/dist/client/{index-B2XpdgR6.js → index-KrGHjH-_.js} +63 -36
  53. package/dist/client/templates.js +82 -15
  54. package/dist/client/{transition-yBii4fu6.js → transition-Ctkv90El.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 +47 -16
  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
@@ -4,7 +4,7 @@ import { includesProcess } from "@/store/actions";
4
4
  /** @type {import("@/types").Template} */
5
5
  export default {
6
6
  loading: {
7
- id: Symbol(),
7
+ id: "loading",
8
8
  type: "web-component",
9
9
  widget: {
10
10
  // https://uiball.com/ldrs/
@@ -39,8 +39,8 @@ export default {
39
39
  btnsPosition: {
40
40
  x: "12/9/9",
41
41
  y: 1,
42
- gap: 16
43
- }
42
+ gap: 16,
43
+ },
44
44
  },
45
45
  },
46
46
  },
@@ -49,11 +49,11 @@ export default {
49
49
  id: "Tools",
50
50
  type: "internal",
51
51
  title: "Tools",
52
- layout: { x: 0, y: 0, w: "3/3/2", h: 1 },
52
+ layout: { x: 0, y: 0, w: "3/3/2", h: 2 },
53
53
  widget: {
54
54
  name: "EodashTools",
55
55
  properties: {
56
- layoutTarget: "light",
56
+ layoutTarget: "lite",
57
57
  layoutIcon: mdiViewDashboard,
58
58
  itemFilterConfig: {
59
59
  resultType: "cards",
@@ -0,0 +1,62 @@
1
+ // import { includesProcess } from "@/store/actions";
2
+ /** @type {import("@/types").Template} */
3
+ export default {
4
+ gap: 16,
5
+ loading: {
6
+ id: "loading",
7
+ type: "web-component",
8
+ widget: {
9
+ // https://uiball.com/ldrs/
10
+ link: "https://cdn.jsdelivr.net/npm/ldrs/dist/auto/mirage.js",
11
+ tagName: "l-mirage",
12
+ properties: {
13
+ class: "align-self-center justify-self-center",
14
+ size: "120",
15
+ speed: "2.5",
16
+ color: "#004170",
17
+ },
18
+ },
19
+ },
20
+ background: {
21
+ id: "background-map",
22
+ type: "internal",
23
+ widget: {
24
+ name: "EodashMap",
25
+ properties: {
26
+ enableCompare: true,
27
+ btns: {
28
+ enableZoom: true,
29
+ enableExportMap: true,
30
+ enableChangeProjection: true,
31
+ enableCompareIndicators: {
32
+ fallbackTemplate: "explore",
33
+ itemFilterConfig: {
34
+ imageProperty: "assets.thumbnail.href",
35
+ },
36
+ },
37
+ enableSearch: true,
38
+ },
39
+ },
40
+ },
41
+ },
42
+ widgets: [
43
+ {
44
+ id: "Layercontrol",
45
+ type: "internal",
46
+ title: "Layers",
47
+ layout: { x: "9/9/10", y: 0, w: "3/3/2", h: 12 },
48
+ widget: {
49
+ name: "EodashLayerControl",
50
+ },
51
+ },
52
+ {
53
+ id: "ItemCatalog",
54
+ title: "Catalog",
55
+ type: "internal",
56
+ layout: { x: 0, y: 0, w: "3/3/2", h: 12 },
57
+ widget: {
58
+ name: "EodashItemCatalog",
59
+ },
60
+ },
61
+ ],
62
+ };
@@ -1,6 +1,6 @@
1
1
  import { getBaseConfig } from "./baseConfig";
2
2
 
3
- export { default as light } from "./light";
3
+ export { default as lite } from "./lite";
4
4
  export { default as expert } from "./expert";
5
5
  export { default as compare } from "./compare";
6
6
  export { getBaseConfig };
@@ -42,7 +42,7 @@ export default {
42
42
  id: "tools-light",
43
43
  type: "internal",
44
44
  title: "Tools",
45
- layout: { x: 0, y: 0, w: "3/3/2", h: 1 },
45
+ layout: { x: 0, y: 0, w: "3/3/2", h: 2 },
46
46
  widget: {
47
47
  name: "EodashTools",
48
48
  properties: {
@@ -129,7 +129,6 @@ import { mdiRayStartArrow, mdiRayEndArrow } from "@mdi/js";
129
129
  import { eodashCollections, eodashCompareCollections } from "@/utils/states";
130
130
  import log from "loglevel";
131
131
  import { useTransparentPanel } from "@/composables";
132
- import { getDatetimeProperty } from "@/eodashSTAC/helpers";
133
132
  import { storeToRefs } from "pinia";
134
133
 
135
134
  const { lgAndDown } = useDisplay();
@@ -149,6 +148,13 @@ const currentDate = customRef((track, trigger) => ({
149
148
  trigger();
150
149
  log.debug("Datepicker setting currentDate", datetime.value);
151
150
  const date = new Date(num);
151
+
152
+ // Validate the date before setting
153
+ if (isNaN(date.getTime())) {
154
+ log.warn("Invalid date value provided to datepicker:", num);
155
+ return;
156
+ }
157
+
152
158
  datetime.value = date.toISOString();
153
159
  //@ts-expect-error supports move method https://vcalendar.io/datepicker/basics.html#basics
154
160
  datePickerEl.value?.move({
@@ -247,24 +253,15 @@ async function fetchCollectionsAttributes(eodashCollections) {
247
253
 
248
254
  return await Promise.all(
249
255
  eodashCollections.map((ec, idx) => {
250
- return ec.fetchCollection().then(() => {
251
- const dateProperty = getDatetimeProperty(ec.getItems());
252
- if (!dateProperty) {
253
- return [];
256
+ return ec.fetchCollection().then(async () => {
257
+ const dates = await ec.getDates();
258
+ if (!dates || !dates.length) {
259
+ log.debug(
260
+ `Collection ${ec.collectionStac?.id} has no dates, skipping datepicker attribute`,
261
+ );
262
+ return undefined;
254
263
  }
255
- const dates = [
256
- ...new Set(
257
- ec.getItems()?.reduce((valid, item) => {
258
- const parsed = Date.parse(
259
- /** @type {string} */ (item[dateProperty]),
260
- );
261
- if (parsed) {
262
- valid.push(new Date(parsed));
263
- }
264
- return valid;
265
- }, /** @type {Date[]} */ ([])),
266
- ),
267
- ];
264
+
268
265
  return {
269
266
  key: "id-" + idx.toString() + Math.random().toString(16).slice(2),
270
267
  dot: {
@@ -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
+ };