@eodash/eodash 5.0.0-alpha.2 → 5.0.0-alpha.2.10

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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -3
  3. package/core/client/App.vue +26 -0
  4. package/core/client/SuspensedDashboard.ce.vue +105 -0
  5. package/core/client/asWebComponent.d.ts +23 -0
  6. package/core/client/asWebComponent.js +12 -0
  7. package/core/client/components/DashboardLayout.vue +77 -0
  8. package/core/client/components/DynamicWebComponent.vue +68 -0
  9. package/core/client/components/ErrorAlert.vue +36 -0
  10. package/core/{components → client/components}/Footer.vue +34 -15
  11. package/core/client/components/Header.vue +19 -0
  12. package/core/client/components/IframeWrapper.vue +11 -0
  13. package/core/client/components/Loading.vue +37 -0
  14. package/core/client/components/MobileLayout.vue +109 -0
  15. package/core/client/composables/DefineEodash.js +67 -0
  16. package/core/client/composables/DefineWidgets.js +161 -0
  17. package/core/client/composables/index.js +136 -0
  18. package/core/client/eodash.js +136 -0
  19. package/core/client/main.js +2 -0
  20. package/core/client/plugins/index.js +20 -0
  21. package/core/client/plugins/vuetify.js +35 -0
  22. package/core/client/render.js +12 -0
  23. package/core/client/store/Actions.js +62 -0
  24. package/core/client/store/States.js +21 -0
  25. package/core/client/store/index.js +20 -0
  26. package/core/client/store/stac.js +81 -0
  27. package/core/client/types.d.ts +287 -0
  28. package/core/client/utils/eodashSTAC.js +337 -0
  29. package/core/client/utils/helpers.js +172 -0
  30. package/core/client/utils/index.js +51 -0
  31. package/core/{utils → client/utils}/keys.js +2 -2
  32. package/core/client/views/Dashboard.vue +116 -0
  33. package/core/client/vite-env.d.ts +39 -0
  34. package/dist/client/DashboardLayout-C_3q8Y10.js +156 -0
  35. package/dist/{DynamicWebComponent-BDOQsEcj.js → client/DynamicWebComponent-IiTTLBt-.js} +11 -11
  36. package/dist/client/EodashDatePicker-DgC1lccp.js +252 -0
  37. package/dist/{eox-itemfilter-DcQkRD2l.js → client/EodashItemFilter-DoWZGe6r.js} +1515 -1381
  38. package/dist/client/EodashMap-C96D3LLv.js +114352 -0
  39. package/dist/client/EodashMapBtns-DDtzV94u.js +50 -0
  40. package/dist/client/ExportState-CduS2mFs.js +558 -0
  41. package/dist/client/Footer-DHQcutCv.js +115 -0
  42. package/dist/client/Header-DC0y7rLK.js +350 -0
  43. package/dist/{IframeWrapper-DYHQQRUh.js → client/IframeWrapper-BK-10FbS.js} +4 -4
  44. package/dist/client/MobileLayout-BpLoMY4i.js +945 -0
  45. package/dist/client/PopUp-CNMCWL0z.js +300 -0
  46. package/dist/client/VImg-DT7eb8V-.js +291 -0
  47. package/dist/client/VMain-OenWyy46.js +39 -0
  48. package/dist/client/VOverlay-x3UXpKhr.js +972 -0
  49. package/dist/{WidgetsContainer-D_PLP1qf.js → client/WidgetsContainer-qMUwtN0W.js} +26 -19
  50. package/dist/client/asWebComponent-DQrnLsI2.js +20404 -0
  51. package/dist/{basedecoder-Qm25PwVp-CHo5Pomv.js → client/basedecoder-DHcBySSe-BmCFNFnw.js} +5 -6
  52. package/dist/{decoder-kAoyGIq9-HAkjSTCt.js → client/decoder-Cth6J7EK-CtUoHA6r.js} +1 -1
  53. package/dist/client/deflate-BXt-9JA_-CWfClgpK.js +10 -0
  54. package/dist/client/eo-dash.js +6 -0
  55. package/dist/client/eox-jsonform-DCErgjhw.js +17515 -0
  56. package/dist/client/eox-layercontrol-D0Ht7Mag.js +21298 -0
  57. package/dist/client/eox-stacinfo-l7ALSV90.js +13969 -0
  58. package/dist/client/forwardRefs-CpNjL95t.js +185 -0
  59. package/dist/client/helpers-KCSgmfim.js +1417 -0
  60. package/dist/{index-8ch8xKfa.js → client/index-DopVrUzM.js} +21 -20
  61. package/dist/{jpeg-DNfUpLwy-Fjan-04T.js → client/jpeg-BAgeD1d3-oeHbFPUL.js} +5 -6
  62. package/dist/{lerc-C9VL9kri-DyVxBvsf.js → client/lerc-BgbQqdFI-D986ErVw.js} +37 -39
  63. package/dist/{lzw-BOMhmEDy-Dboc93VO.js → client/lzw-LAGDNbSC-DkP96qO9.js} +1 -1
  64. package/dist/{packbits-DaUD6MLm-Bu1PoTGa.js → client/packbits-BlDR4Kj5-C66n1-zr.js} +1 -1
  65. package/dist/{pako.esm-C3kYPGGQ-BMki8cQY.js → client/pako.esm-CB1uQYY0-DB0PYm1P.js} +6 -12
  66. package/dist/{raw-CcGKjn8q-DFOt-i8n.js → client/raw-CMGvRjfu-BRi6E4i1.js} +1 -1
  67. package/dist/{ssrBoot-Cm5pWM14.js → client/ssrBoot-DkjdOWvj.js} +1 -1
  68. package/dist/client/style.css +5 -0
  69. package/dist/client/transition-BtJHh7JK.js +34 -0
  70. package/dist/{webfontloader-CyOFAuFB.js → client/webfontloader-qotgY98I.js} +56 -92
  71. package/dist/{webimage-D2c098k3-DLj1LQxB.js → client/webimage-BM_pbLN3-L2cGWK5l.js} +1 -1
  72. package/dist/node/cli.js +20 -0
  73. package/dist/node/main.js +2 -0
  74. package/dist/node/types.d.ts +47 -0
  75. package/package.json +54 -51
  76. package/widgets/EodashDatePicker.vue +145 -40
  77. package/widgets/EodashItemFilter.vue +73 -38
  78. package/widgets/EodashMap.vue +210 -37
  79. package/widgets/EodashMapBtns.vue +50 -0
  80. package/widgets/ExportState.vue +112 -0
  81. package/widgets/PopUp.vue +40 -0
  82. package/widgets/WidgetsContainer.vue +47 -27
  83. package/bin/app.js +0 -22
  84. package/bin/cli.js +0 -62
  85. package/bin/main.js +0 -9
  86. package/bin/serverConfig.js +0 -165
  87. package/bin/types.d.ts +0 -52
  88. package/bin/utils.js +0 -103
  89. package/core/App.vue +0 -19
  90. package/core/SuspensedDashboard.ce.vue +0 -59
  91. package/core/asWebComponent.d.ts +0 -18
  92. package/core/asWebComponent.js +0 -12
  93. package/core/components/DashboardLayout.vue +0 -54
  94. package/core/components/DynamicWebComponent.vue +0 -68
  95. package/core/components/Header.vue +0 -14
  96. package/core/components/IframeWrapper.vue +0 -11
  97. package/core/components/Loading.vue +0 -27
  98. package/core/components/MobileLayout.vue +0 -49
  99. package/core/composables/DefineEodash.js +0 -57
  100. package/core/composables/DefineWidgets.js +0 -129
  101. package/core/composables/index.js +0 -219
  102. package/core/eodash.js +0 -100
  103. package/core/main.js +0 -2
  104. package/core/plugins/index.js +0 -18
  105. package/core/plugins/vuetify.js +0 -30
  106. package/core/render.js +0 -13
  107. package/core/store/Actions.js +0 -0
  108. package/core/store/States.js +0 -21
  109. package/core/store/index.js +0 -17
  110. package/core/store/stac.js +0 -63
  111. package/core/types.d.ts +0 -355
  112. package/core/utils/eodashSTAC.js +0 -164
  113. package/core/utils/helpers.js +0 -40
  114. package/core/utils/index.js +0 -28
  115. package/core/views/Dashboard.vue +0 -59
  116. package/core/vite-env.d.ts +0 -31
  117. package/dist/DashboardLayout-YLfAa8wH.js +0 -168
  118. package/dist/EodashDatePicker-DlehqSgz.js +0 -1645
  119. package/dist/EodashItemFilter-TSp7XRGy.js +0 -51
  120. package/dist/EodashMap-DInb9cfS.js +0 -53405
  121. package/dist/Footer-CIzHgJbK.js +0 -118
  122. package/dist/Header-C5PSB8Uc.js +0 -609
  123. package/dist/MobileLayout-DZmqrpub.js +0 -523
  124. package/dist/VBtn-CFPhsIX8.js +0 -1106
  125. package/dist/VMain-deI9jyIf.js +0 -35
  126. package/dist/asWebComponent-BKe6Spni.js +0 -11323
  127. package/dist/color-D72nEBIR.js +0 -115
  128. package/dist/deflate-Be2Arps5-hDqMz3RA.js +0 -10
  129. package/dist/dimensions-BZkmIizV.js +0 -53
  130. package/dist/eo-dash.js +0 -6
  131. package/dist/eox-map-BJ9SIixs.js +0 -36205
  132. package/dist/eox-stacinfo-B-YrT7Ug.js +0 -13698
  133. package/dist/forwardRefs-Bxeu9Obx.js +0 -142
  134. package/dist/index-DIt8GjSR.js +0 -65
  135. package/dist/style.css +0 -5
  136. /package/dist/{.gitkeep → client/.gitkeep} +0 -0
  137. /package/dist/{_commonjsHelpers-DaMA6jEr.js → client/_commonjsHelpers-DaMA6jEr.js} +0 -0
@@ -0,0 +1,337 @@
1
+ import { Collection, Item } from "stac-js";
2
+ import { toAbsolute } from "stac-js/src/http.js";
3
+ import {
4
+ createLayersFromDataAssets,
5
+ extractLayerConfig,
6
+ generateFeatures,
7
+ setMapProjFromCol,
8
+ } from "./helpers";
9
+ import axios from "axios";
10
+ import { registerProjection } from "@/store/Actions";
11
+
12
+ export class EodashCollection {
13
+ /** @type {string} */
14
+ #collectionUrl = "";
15
+ /** @type {import("stac-ts").StacCollection | undefined} */
16
+ #collectionStac;
17
+ /**
18
+ * @type {import("stac-ts").StacLink
19
+ * | import("stac-ts").StacItem
20
+ * | undefined}
21
+ */
22
+ selectedItem;
23
+
24
+ /** @param {string} collectionUrl */
25
+ constructor(collectionUrl) {
26
+ this.#collectionUrl = collectionUrl;
27
+ }
28
+ /**
29
+ * @async
30
+ * @param {import('stac-ts').StacLink | Date} [itemLinkOrDate]
31
+ * @returns
32
+ */
33
+ createLayersJson = async (itemLinkOrDate) => {
34
+ /** @type {import("stac-ts").StacLink | undefined} */
35
+ let stacItem,
36
+ /** @type {import("stac-ts").StacCollection | undefined} */
37
+ stac;
38
+ // TODO get auxiliary layers from collection
39
+ /** @type {object[]} */
40
+ let layersJson = [];
41
+
42
+ // Load collectionstac if not yet initialized
43
+ if (!this.#collectionStac) {
44
+ const response = await axios.get(this.#collectionUrl);
45
+ stac = await response.data;
46
+ this.#collectionStac = new Collection(stac);
47
+ }
48
+
49
+ // set availabe map projection
50
+ setMapProjFromCol(this.#collectionStac);
51
+
52
+ if (stac && stac.endpointtype === "GeoDB") {
53
+ // Special handling of point based data
54
+ const allFeatures = generateFeatures(stac.links);
55
+ layersJson.unshift({
56
+ type: "Vector",
57
+ properties: {
58
+ id: stac.id,
59
+ title: stac.title || stac.id,
60
+ },
61
+ source: {
62
+ type: "Vector",
63
+ url: "data:," + encodeURIComponent(JSON.stringify(allFeatures)),
64
+ format: "GeoJSON",
65
+ },
66
+ style: {
67
+ "circle-radius": 5,
68
+ "circle-fill-color": "#00417077",
69
+ "circle-stroke-color": "#004170",
70
+ "fill-color": "#00417077",
71
+ "stroke-color": "#004170",
72
+ },
73
+ });
74
+
75
+ return layersJson;
76
+ } else {
77
+ if (itemLinkOrDate instanceof Date) {
78
+ // if collectionStac not yet initialized we do it here
79
+ stacItem = this.getItems()?.sort((a, b) => {
80
+ const distanceA = Math.abs(
81
+ new Date(/** @type {number} */ (a.datetime)).getTime() -
82
+ itemLinkOrDate.getTime(),
83
+ );
84
+ const distanceB = Math.abs(
85
+ new Date(/** @type {number} */ (b.datetime)).getTime() -
86
+ itemLinkOrDate.getTime(),
87
+ );
88
+ return distanceA - distanceB;
89
+ })[0];
90
+ this.selectedItem = stacItem;
91
+ } else {
92
+ stacItem = itemLinkOrDate;
93
+ }
94
+
95
+ const stacItemUrl = stacItem
96
+ ? toAbsolute(stacItem.href, this.#collectionUrl)
97
+ : this.#collectionUrl;
98
+ stac = await axios.get(stacItemUrl).then((resp) => resp.data);
99
+
100
+ if (!stacItem) {
101
+ // no specific item was requested; render last item
102
+ this.#collectionStac = new Collection(stac);
103
+ const items = this.getItems();
104
+ this.selectedItem = items?.[items.length - 1];
105
+ if (this.selectedItem) {
106
+ layersJson = await this.createLayersJson(this.selectedItem);
107
+ } else {
108
+ if (import.meta.env.DEV) {
109
+ console.warn(
110
+ "[eodash] the selected collection does not include any items",
111
+ );
112
+ }
113
+ }
114
+ return [];
115
+ } else {
116
+ // specific item was requested
117
+ const item = new Item(stac);
118
+ this.selectedItem = item;
119
+ const title = this.#collectionStac.title || this.#collectionStac.id;
120
+ layersJson.unshift(
121
+ ...(await this.buildJsonArray(item, stacItemUrl, title)),
122
+ );
123
+ return layersJson;
124
+ }
125
+ }
126
+ };
127
+
128
+ async getExtent() {
129
+ if (!this.#collectionStac) {
130
+ const response = await axios.get(this.#collectionUrl);
131
+ const stac = await response.data;
132
+ this.#collectionStac = new Collection(stac);
133
+ }
134
+ return this.#collectionStac?.extent;
135
+ }
136
+ /**
137
+ * @param {object} properties
138
+ * @param {[string]} roles
139
+ * */
140
+ extractRoles(properties, roles) {
141
+ roles?.forEach((role) => {
142
+ if (role === "visible") {
143
+ // @ts-expect-error visible does not need to exist in properties
144
+ properties.visible = true;
145
+ }
146
+ if (role === "overlay" || role === "baselayer") {
147
+ // @ts-expect-error group not expected to exist in properties
148
+ properties.group = role;
149
+ }
150
+ });
151
+ }
152
+ /**
153
+ * @param {import("stac-ts").StacItem} item
154
+ * @param {string} itemUrl
155
+ * @param {string} title
156
+ * */
157
+ async buildJsonArray(item, itemUrl, title) {
158
+ const jsonArray = [];
159
+ // TODO: this currently assumes only one layer will be extracted
160
+ // from an item, although it think this is currently true
161
+ // potentially this could return multiple layers
162
+ // TODO: implement other types, such as COG
163
+
164
+ // I propose following approach, we "manually" create configurations
165
+ // for the rendering options we know and expect.
166
+ // If we don't find any we fallback to using the STAC ol item that
167
+ // will try to extract anything it supports but for which we have
168
+ // less control.
169
+ let dataAssets = /** @type {Record<string,import('stac-ts').StacAsset>} */ ({});
170
+ if (item.assets) {
171
+ dataAssets = Object.keys(item.assets).reduce((data, ast) => {
172
+ if (item.assets[ast].roles?.includes("data")) {
173
+ data[ast] = item.assets[ast];
174
+ }
175
+ return data;
176
+ }, /** @type {Record<string,import('stac-ts').StacAsset>} */ ({}));
177
+ }
178
+
179
+ const { layerConfig, style } = extractLayerConfig(
180
+ await this.fetchStyle(item, itemUrl),
181
+ );
182
+ const wmsArray = item.links.filter((l) => l.rel === "wms");
183
+ const xyzArray = item.links.filter((l) => l.rel === "xyz");
184
+ const fallbackToStac = item.links.find((l) => l.rel === "wmts");
185
+
186
+ // TODO: add capability to find projection in item
187
+ await registerProjection(
188
+ /** @type {number | undefined} */ (item?.["proj:epsg"]),
189
+ );
190
+
191
+ if (wmsArray.length > 0) {
192
+ wmsArray.forEach((link) => {
193
+ let json = {
194
+ type: "Tile",
195
+ properties: {
196
+ id: link.id || item.id,
197
+ title: title || link.title || item.id,
198
+ },
199
+ source: {
200
+ // TODO: if no projection information is provided we should
201
+ // assume one, else for WMS requests it will try to get
202
+ // the map projection that might not be supported
203
+ // projection: projDef ? projDef : "EPSG:4326",
204
+ type: "TileWMS",
205
+ url: link.href,
206
+ params: {
207
+ LAYERS: link["wms:layers"],
208
+ TILED: true,
209
+ },
210
+ },
211
+ };
212
+ this.extractRoles(
213
+ json.properties,
214
+ /** @type {[string]} */ (link.roles),
215
+ );
216
+ if ("wms:dimensions" in link) {
217
+ // Expand all dimensions into the params attribute
218
+ json.source.params = Object.assign(
219
+ json.source.params,
220
+ link["wms:dimensions"],
221
+ );
222
+ }
223
+ jsonArray.push(json);
224
+ });
225
+ } else if (xyzArray.length > 0) {
226
+ xyzArray.forEach((link) => {
227
+ let json = {
228
+ type: "Tile",
229
+ properties: {
230
+ id: link.id || item.id,
231
+ title: title || link.title || item.id,
232
+ roles: link.roles,
233
+ },
234
+ source: {
235
+ type: "XYZ",
236
+ url: link.href,
237
+ },
238
+ };
239
+ this.extractRoles(
240
+ json.properties,
241
+ /** @type {[string]} */ (link.roles),
242
+ );
243
+ jsonArray.push(json);
244
+ });
245
+ } else if (fallbackToStac) {
246
+ jsonArray.push({
247
+ type: "STAC",
248
+ displayWebMapLink: true,
249
+ displayFootprint: false,
250
+ data: item,
251
+ properties: {
252
+ id: item.id,
253
+ title: title || item.id,
254
+ },
255
+ });
256
+ } else if (Object.keys(dataAssets).length) {
257
+ jsonArray.push(
258
+ ...(await createLayersFromDataAssets(
259
+ this.#collectionStac?.title || item.id,
260
+ this.#collectionStac?.title || item.id,
261
+ dataAssets,
262
+ style,
263
+ layerConfig,
264
+ )),
265
+ );
266
+ } else if (item.geometry) {
267
+ // fall back to rendering the feature
268
+ jsonArray.push({
269
+ type: "Vector",
270
+ source: {
271
+ type: "Vector",
272
+ url: "data:," + encodeURIComponent(JSON.stringify(item.geometry)),
273
+ format: "GeoJSON",
274
+ },
275
+ properties: {
276
+ id: item.id,
277
+ title: this.#collectionStac?.title || item.id,
278
+ layerConfig: {
279
+ ...layerConfig,
280
+ style,
281
+ },
282
+ },
283
+ });
284
+ }
285
+
286
+ return jsonArray;
287
+ }
288
+
289
+ getItems() {
290
+ return (
291
+ this.#collectionStac?.links
292
+ .filter((i) => i.rel === "item")
293
+ // sort by `datetime`, where oldest is first in array
294
+ .sort((a, b) =>
295
+ /** @type {number} */ (a.datetime) <
296
+ /** @type {number} */ (b.datetime)
297
+ ? -1
298
+ : 1,
299
+ )
300
+ );
301
+ }
302
+
303
+ /**
304
+ * @param {import("stac-ts").StacItem} item
305
+ * @param {string} itemUrl
306
+ **/
307
+ async fetchStyle(item, itemUrl) {
308
+ const styleLink = item.links.find((link) => link.rel.includes("style"));
309
+ if (styleLink) {
310
+ let url = "";
311
+ if (styleLink.href.startsWith("http")) {
312
+ url = styleLink.href;
313
+ } else {
314
+ url = toAbsolute(styleLink.href, itemUrl);
315
+ }
316
+
317
+ /** @type {import("ol/layer/WebGLTile").Style & {jsonform?:object}} */
318
+ const styleJson = await axios.get(url).then((resp) => resp.data);
319
+ return styleJson;
320
+ }
321
+ }
322
+
323
+ getDates() {
324
+ return (
325
+ this.#collectionStac?.links
326
+ .filter((i) => i.rel === "item")
327
+ // sort by `datetime`, where oldest is first in array
328
+ .sort((a, b) =>
329
+ /** @type {number} */ (a.datetime) <
330
+ /** @type {number} */ (b.datetime)
331
+ ? -1
332
+ : 1,
333
+ )
334
+ .map((i) => new Date(/** @type {number} */ (i.datetime)))
335
+ );
336
+ }
337
+ }
@@ -0,0 +1,172 @@
1
+ import { changeMapProjection, registerProjection } from "@/store/Actions";
2
+ import { availableMapProjection } from "@/store/States";
3
+ import { toAbsolute } from "stac-js/src/http.js";
4
+
5
+ /** @param {import("stac-ts").StacLink[]} links */
6
+ export function generateFeatures(links) {
7
+ /**
8
+ * @type {{
9
+ * type: string;
10
+ * geometry: {
11
+ * type: string;
12
+ * coordinates: [number, number];
13
+ * };
14
+ * }[]}
15
+ */
16
+ const features = [];
17
+ links.forEach((element) => {
18
+ if (element.rel === "item" && "latlng" in element) {
19
+ const [lat, lon] = /** @type {string} */ (element.latlng)
20
+ .split(",")
21
+ .map((it) => Number(it));
22
+ features.push({
23
+ type: "Feature",
24
+ geometry: {
25
+ type: "Point",
26
+ coordinates: [lon, lat],
27
+ },
28
+ });
29
+ }
30
+ });
31
+ const geojsonObject = {
32
+ type: "FeatureCollection",
33
+ crs: {
34
+ type: "name",
35
+ properties: {
36
+ name: "EPSG:4326",
37
+ },
38
+ },
39
+ features,
40
+ };
41
+ return geojsonObject;
42
+ }
43
+
44
+ /** @param { import("ol/layer/WebGLTile").Style & { jsonform?: object } } [style] */
45
+ export function extractLayerConfig(style) {
46
+ /** @type {Record<string,unknown>} */
47
+ let layerConfig = {};
48
+ if (style?.jsonform) {
49
+ layerConfig = { schema: style.jsonform, type: "style" };
50
+ delete style?.jsonform;
51
+ }
52
+ return { layerConfig, style };
53
+ }
54
+ /**
55
+ * @param {string} id
56
+ * @param {string} title
57
+ * @param {Record<string,import("stac-ts").StacAsset>} assets
58
+ * @param {import("ol/layer/WebGLTile").Style} [style]
59
+ * @param {Record<string, unknown>} [layerConfig]
60
+ **/
61
+ export async function createLayersFromDataAssets(
62
+ id,
63
+ title,
64
+ assets,
65
+ style,
66
+ layerConfig,
67
+ ) {
68
+ let jsonArray = [];
69
+ let geoTIFFSources = [];
70
+ for (const ast in assets) {
71
+ // register projection if exists
72
+ await registerProjection(
73
+ /** @type {number | undefined} */ (assets[ast]?.["proj:epsg"]),
74
+ );
75
+
76
+ if (assets[ast]?.type === "application/geo+json") {
77
+ jsonArray.push({
78
+ type: "Vector",
79
+ source: {
80
+ type: "Vector",
81
+ url: assets[ast].href,
82
+ format: "GeoJSON",
83
+ },
84
+ properties: {
85
+ id,
86
+ title,
87
+ layerConfig: {
88
+ ...layerConfig,
89
+ style,
90
+ },
91
+ },
92
+ });
93
+ } else if (assets[ast]?.type === "image/tiff") {
94
+ geoTIFFSources.push({ url: assets[ast].href });
95
+ }
96
+ }
97
+ if (geoTIFFSources.length) {
98
+ jsonArray.push({
99
+ type: "WebGLTile",
100
+ source: {
101
+ type: "GeoTIFF",
102
+ normalize: style?.variables ? false : true,
103
+ sources: geoTIFFSources,
104
+ },
105
+ properties: {
106
+ id,
107
+ title,
108
+ layerConfig,
109
+ },
110
+ style,
111
+ });
112
+ }
113
+ return jsonArray;
114
+ }
115
+
116
+ /**
117
+ * checks if there's a projection on the Collection and
118
+ * updates {@link availableMapProjection}
119
+ * @param {import('stac-ts').StacCollection} STAcCollection
120
+ */
121
+ export const setMapProjFromCol = (STAcCollection) => {
122
+ // if a projection exists on the collection level
123
+ if (STAcCollection?.["proj:epsg"]) {
124
+ if (
125
+ availableMapProjection.value &&
126
+ availableMapProjection.value !== STAcCollection?.["proj:epsg"]
127
+ ) {
128
+ changeMapProjection(
129
+ /** @type {number} */
130
+ (STAcCollection["proj:epsg"]),
131
+ );
132
+ }
133
+ // set it for `EodashMapBtns`
134
+ availableMapProjection.value = /** @type {string} */ (
135
+ STAcCollection["proj:epsg"]
136
+ );
137
+ } else {
138
+ // reset to default projection
139
+ changeMapProjection((availableMapProjection.value = ""));
140
+ }
141
+ };
142
+
143
+ /**
144
+ * Function to extract collection urls from an indicator
145
+ * @param {import("stac-ts").StacCatalog
146
+ * | import("stac-ts").StacCollection
147
+ * | import("stac-ts").StacItem
148
+ * | null
149
+ * } stacObject
150
+ * @param {string} basepath
151
+ * @returns {string[]}
152
+ */
153
+ export function extractCollectionUrls(stacObject, basepath) {
154
+ const collectionUrls = [];
155
+ // Support for two structure types, flat and indicator, simplified here:
156
+ // Flat assumes Catalog-Collection-Item
157
+ // Indicator assumes Catalog-Collection-Collection-Item
158
+ // TODO: this is not the most stable test approach,
159
+ // we should discuss potential other approaches
160
+
161
+ if (stacObject?.links && stacObject?.links[1].rel === "item") {
162
+ collectionUrls.push(basepath);
163
+ } else if (stacObject?.links[1].rel === "child") {
164
+ // TODO: Iterate through all children to create collections
165
+ stacObject.links.forEach((link) => {
166
+ if (link.rel === "child") {
167
+ collectionUrls.push(toAbsolute(link.href, basepath));
168
+ }
169
+ });
170
+ }
171
+ return collectionUrls;
172
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Loads font in the app using `webfontloader`
3
+ *
4
+ * @param {string} [family]
5
+ * @param {string} [link]
6
+ * @param {boolean} [isWebComponent]
7
+ * @returns {Promise<string>} - Font family name
8
+ * @see {@link "https://github.com/typekit/webfontloader"}
9
+ */
10
+ export const loadFont = async (
11
+ family = "",
12
+ link = "",
13
+ isWebComponent = false,
14
+ ) => {
15
+ if (family && link) {
16
+ const WebFontLoader = (await import("webfontloader")).default;
17
+ WebFontLoader.load({
18
+ classes: false,
19
+ custom: {
20
+ // Use FVD notation to include families https://github.com/typekit/fvd
21
+ families: [family],
22
+ // Path to stylesheet that defines font-face
23
+ urls: [link],
24
+ },
25
+ fontactive(familyName, _fvd) {
26
+ const stylesheet = new CSSStyleSheet();
27
+ const styles = isWebComponent
28
+ ? `eo-dash {font-family: ${familyName};}`
29
+ : `* {font-family: ${familyName};}`;
30
+ stylesheet.replaceSync(styles);
31
+ document.adoptedStyleSheets.push(stylesheet);
32
+ },
33
+ fontinactive(familyName, _fvd) {
34
+ throw new Error(`error loading font: ${familyName}`);
35
+ },
36
+ });
37
+ }
38
+ return family;
39
+ };
40
+
41
+ /**
42
+ * @param {string} text
43
+ * @param {import("vue").Ref<boolean>} showIcon
44
+ **/
45
+ export const copyToClipBoard = async (text, showIcon) => {
46
+ await navigator.clipboard.writeText(text);
47
+ showIcon.value = true;
48
+ setTimeout(() => {
49
+ showIcon.value = false;
50
+ }, 2000);
51
+ };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * `eodash` injection key.
3
- * @type {Symbol}
2
+ * `eodash` injection key.
3
+ *
4
4
  * @see {@link "@/plugins/index.js"}
5
5
  */
6
6
  export const eodashKey = Symbol("eodash");
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <HeaderComponent ref="headerRef" v-if="!eodash.brand.noLayout" />
3
+ <ErrorAlert v-model="error" />
4
+ <Suspense>
5
+ <TemplateComponent
6
+ @vue:mounted="onTemplateMount?.(hiddenElements)"
7
+ class="template"
8
+ />
9
+ <template #fallback>
10
+ <div class="loading-container">
11
+ <Loading />
12
+ </div>
13
+ </template>
14
+ </Suspense>
15
+ <FooterComponent ref="footerRef" v-if="!eodash.brand.noLayout" />
16
+ </template>
17
+
18
+ <script setup>
19
+ import { useEodashRuntime } from "@/composables/DefineEodash";
20
+ import { useURLSearchParametersSync, useUpdateTheme } from "@/composables";
21
+ import { useSTAcStore } from "@/store/stac";
22
+ import { defineAsyncComponent, onErrorCaptured, onMounted, ref } from "vue";
23
+ import { useDisplay, useLayout } from "vuetify/lib/framework.mjs";
24
+ import { loadFont } from "@/utils";
25
+ import Loading from "@/components/Loading.vue";
26
+ import ErrorAlert from "@/components/ErrorAlert.vue";
27
+
28
+ const props = defineProps({
29
+ config: {
30
+ type: String,
31
+ },
32
+ onTemplateMount: {
33
+ type: Function,
34
+ },
35
+ });
36
+ const eodash = await useEodashRuntime(props.config);
37
+
38
+ useURLSearchParametersSync();
39
+
40
+ const theme = useUpdateTheme("dashboardTheme", eodash.brand?.theme);
41
+ theme.global.name.value = "dashboardTheme";
42
+
43
+ await loadFont(
44
+ eodash.brand?.font?.family,
45
+ eodash.brand?.font?.link,
46
+ !!props.onTemplateMount,
47
+ );
48
+
49
+ const { loadSTAC } = useSTAcStore();
50
+ await loadSTAC();
51
+
52
+ const { smAndDown } = useDisplay();
53
+ const TemplateComponent = smAndDown.value
54
+ ? defineAsyncComponent(() => import(`@/components/MobileLayout.vue`))
55
+ : defineAsyncComponent(() => import(`@/components/DashboardLayout.vue`));
56
+
57
+ const HeaderComponent = defineAsyncComponent(
58
+ () => import(`@/components/Header.vue`),
59
+ );
60
+ const FooterComponent = defineAsyncComponent(
61
+ () => import(`@/components/Footer.vue`),
62
+ );
63
+ const { mainRect } = useLayout();
64
+ const templateHeight = eodash.brand.noLayout
65
+ ? props.onTemplateMount
66
+ ? "100%"
67
+ : "90dvh"
68
+ : `calc(100dvh - ${mainRect.value["top"] + mainRect.value["bottom"]}px)`;
69
+
70
+ const headerRef = ref(null);
71
+ /**
72
+ * @type {import("vue").Ref<InstanceType<
73
+ * typeof import("@/components/Footer.vue").default
74
+ * > | null>}
75
+ */
76
+ const footerRef = ref(null);
77
+
78
+ const hiddenElements = [headerRef, footerRef];
79
+
80
+ onMounted(() => {
81
+ if (props.onTemplateMount && !eodash.brand.noLayout) {
82
+ hiddenElements.forEach((element) => {
83
+ /** @type {HTMLElement} */
84
+ (
85
+ /** @type {import("vue").ComponentPublicInstance} */
86
+ (element.value).$el
87
+ ).style.opacity = "0";
88
+ });
89
+ }
90
+ });
91
+
92
+ const error = ref("");
93
+ onErrorCaptured((e, comp, info) => {
94
+ error.value = `
95
+ ${e}.
96
+ component: ${comp?.$.type.name}.
97
+ info: ${info}.
98
+ `;
99
+ });
100
+ </script>
101
+ <style>
102
+ html {
103
+ overflow: hidden !important;
104
+ }
105
+
106
+ .template {
107
+ height: v-bind("templateHeight");
108
+ }
109
+
110
+ .loading-container {
111
+ height: 100dvh;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ }
116
+ </style>