geonetwork-ui 2.10.0-dev.fc6515c0b → 2.11.0-dev.2478042b2

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 (185) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2326 -6755
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +309 -219
  4. package/index.d.ts.map +1 -1
  5. package/package.json +32 -42
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +20 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +4 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +23 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +8 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +2 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +2 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +2 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +2 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +4 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +23 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +2 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +4 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +9 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +2 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +49 -18
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +11 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +21 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +52 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +24 -1
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +77 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  32. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +79 -0
  33. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  34. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +25 -5
  35. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +106 -56
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +37 -2
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +61 -5
  40. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  41. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  42. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  43. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  44. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  45. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +4 -3
  46. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +62 -0
  47. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  48. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  49. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  50. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  51. package/src/libs/feature/editor/src/index.ts +1 -0
  52. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  53. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  54. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  55. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  56. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  57. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +68 -0
  58. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +41 -0
  59. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  60. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  61. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  62. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  79. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +34 -16
  80. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +17 -0
  81. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  82. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  83. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  84. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  85. package/src/libs/feature/editor/src/lib/fields.config.ts +359 -337
  86. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -3
  87. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  88. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  89. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  90. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  91. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  92. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  93. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  94. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +219 -8
  95. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  96. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  97. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  98. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  99. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  100. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  101. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  102. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  103. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  104. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  105. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  106. package/src/libs/ui/dataviz/src/index.ts +0 -1
  107. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  108. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  109. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  110. package/src/libs/ui/elements/src/index.ts +1 -0
  111. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  112. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
  113. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  114. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  115. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  116. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  117. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  118. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  119. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  120. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  121. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  122. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +5 -8
  123. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  124. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  125. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  126. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  127. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  128. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  129. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  130. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  131. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  132. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  133. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  134. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  135. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  136. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  137. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  138. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  139. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  140. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  141. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  142. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  143. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  144. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  145. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  146. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  147. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  148. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  149. package/src/libs/util/app-config/src/lib/app-config.ts +35 -10
  150. package/src/libs/util/app-config/src/lib/model.ts +3 -0
  151. package/src/libs/util/app-config/src/lib/parse-utils.ts +27 -0
  152. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  153. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  154. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  155. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  156. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  157. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -39
  158. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  159. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  160. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  161. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  162. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  163. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  164. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +183 -124
  165. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  166. package/src/libs/util/i18n/src/index.ts +0 -1
  167. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  168. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  169. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  170. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  171. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  172. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  173. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  174. package/style.css +0 -1
  175. package/tailwind.base.css +15 -0
  176. package/translations/de.json +124 -51
  177. package/translations/en.json +129 -56
  178. package/translations/es.json +97 -24
  179. package/translations/fr.json +126 -53
  180. package/translations/it.json +125 -52
  181. package/translations/nl.json +96 -23
  182. package/translations/pt.json +97 -24
  183. package/translations/sk.json +98 -25
  184. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
  185. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -1,54 +1,10 @@
1
1
  import { BaseFileReader } from './base-file'
2
- import { DataItem, PropertyInfo } from '../model'
3
- import { fetchDataAsText, processItemProperties } from '../utils'
4
- import { GeoJSON, WFS } from 'ol/format.js'
5
- import { WfsVersion } from '@camptocamp/ogc-client'
6
-
7
- export function parseGml(
8
- text: string,
9
- namespace: string,
10
- version: string
11
- ): {
12
- items: DataItem[]
13
- properties: PropertyInfo[]
14
- } {
15
- const splittedNamespace = namespace.split(':')
16
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`)
17
- const match = regex.exec(text)
18
-
19
- if (match && match.length >= 2) {
20
- const wf = new WFS({
21
- featureNS: match[1],
22
- featureType: splittedNamespace[1],
23
- version: version,
24
- })
25
-
26
- let features
27
- try {
28
- features = wf.readFeatures(text)
29
- } catch (e) {
30
- throw Error("Couldn't parse WFS with GML features")
31
- }
32
-
33
- const geojsonItem = new GeoJSON().writeFeaturesObject(features)
34
- return processItemProperties(geojsonItem.features, true)
35
- }
36
- throw Error("Couldn't retrieve namespace url")
37
- }
38
2
 
39
3
  export class GmlReader extends BaseFileReader {
40
- constructor(
41
- protected url: string,
42
- protected namespace: string,
43
- protected version: WfsVersion,
44
- protected cacheActive = true
45
- ) {
46
- super(url)
47
- }
48
-
49
- protected getData() {
50
- return fetchDataAsText(this.url, this.cacheActive).then((text) =>
51
- parseGml(text, this.namespace, this.version)
52
- )
4
+ async getLoadQuery() {
5
+ return `
6
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
7
+ allowed_drivers = ['GML']
8
+ );`
53
9
  }
54
10
  }
@@ -1,28 +1,10 @@
1
- import { DataItem, PropertyInfo } from '../model'
2
- import {
3
- fetchDataAsText,
4
- jsonToGeojsonFeature,
5
- processItemProperties,
6
- } from '../utils'
7
1
  import { BaseFileReader } from './base-file'
8
2
 
9
- /**
10
- * This parser only supports arrays of simple flat objects with properties
11
- * @param text
12
- */
13
- export function parseJson(text: string): {
14
- items: DataItem[]
15
- properties: PropertyInfo[]
16
- } {
17
- const parsed = JSON.parse(text) as any[]
18
- if (!Array.isArray(parsed)) {
19
- throw new Error('Could not parse JSON, expected an array at root level')
20
- }
21
- return processItemProperties(parsed.map(jsonToGeojsonFeature))
22
- }
23
-
24
3
  export class JsonReader extends BaseFileReader {
25
- getData() {
26
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson)
4
+ async getLoadQuery() {
5
+ return `
6
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
7
+ maximum_object_size = 536870912 -- 500MB
8
+ );`
27
9
  }
28
10
  }
@@ -1,11 +1,58 @@
1
- import { EndpointError, WfsEndpoint, WfsVersion } from '@camptocamp/ogc-client'
1
+ import {
2
+ EndpointError,
3
+ WfsEndpoint,
4
+ WfsFeatureTypeFull,
5
+ } from '@camptocamp/ogc-client'
2
6
  import { DataItem, DatasetInfo, FetchError, PropertyInfo } from '../model'
3
7
  import { fetchDataAsText } from '../utils'
4
- import { GmlReader, parseGml } from './gml'
5
- import { GeojsonReader, parseGeojson } from './geojson'
6
- import { BaseCacheReader } from './base-cache'
7
- import { getJsonDataItemsProxy, jsonToGeojsonFeature } from '../utils'
8
- import { generateSqlQuery } from '../sql-utils'
8
+ import WFS from 'ol/format/WFS'
9
+ import GeoJSON from 'ol/format/GeoJSON'
10
+ import Feature from 'ol/Feature'
11
+ import { GeojsonReader } from './geojson'
12
+ import { GmlReader } from './gml'
13
+ import { BaseReader } from './base'
14
+
15
+ const formatGeojson = new GeoJSON()
16
+
17
+ export function parseGeojson(text: string): DataItem[] {
18
+ const parsed = JSON.parse(text)
19
+ const features =
20
+ parsed.type === 'FeatureCollection' ? parsed.features : parsed
21
+ if (!Array.isArray(features)) {
22
+ throw new Error(
23
+ 'Could not parse GeoJSON, expected a features collection or an array of features at root level'
24
+ )
25
+ }
26
+ return features
27
+ }
28
+
29
+ export function parseGml(
30
+ text: string,
31
+ featureType: string,
32
+ version: string
33
+ ): DataItem[] {
34
+ const parts = featureType.split(':')
35
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`)
36
+ const match = regex.exec(text)
37
+ if (match && match.length >= 2) {
38
+ const wfs = new WFS({
39
+ featureNS: match[1],
40
+ featureType: parts[1],
41
+ version: version,
42
+ })
43
+ let features: Feature[]
44
+ try {
45
+ features = wfs.readFeatures(text)
46
+ } catch (e: unknown) {
47
+ throw Error(
48
+ `Couldn't parse WFS with GML features: ${(e as Error).message}`
49
+ )
50
+ }
51
+ const geojsonItem = formatGeojson.writeFeaturesObject(features)
52
+ return geojsonItem.features
53
+ }
54
+ throw Error("Couldn't retrieve namespace url")
55
+ }
9
56
 
10
57
  export async function getWfsEndpoint(wfsUrl: string): Promise<WfsEndpoint> {
11
58
  try {
@@ -33,151 +80,163 @@ export async function getWfsEndpoint(wfsUrl: string): Promise<WfsEndpoint> {
33
80
  }
34
81
  }
35
82
 
36
- export class WfsReader extends BaseCacheReader {
37
- endpoint: WfsEndpoint
38
- featureTypeName: string
39
- version: WfsVersion
40
-
41
- constructor(
42
- url: string,
43
- wfsEndpoint: WfsEndpoint,
44
- featureTypeName: string,
45
- cacheActive?: boolean
46
- ) {
47
- super(url, cacheActive)
48
- this.endpoint = wfsEndpoint
49
- this.featureTypeName = featureTypeName
50
- this.version = this.endpoint.getVersion()
83
+ export class WfsReader extends BaseReader {
84
+ endpoint: Promise<WfsEndpoint>
85
+ featureType: Promise<WfsFeatureTypeFull>
86
+
87
+ /**
88
+ * This dataset reader will read the whole data as geojson or gml instead of
89
+ * using the WFS protocol; it is used in the following situations:
90
+ * - the endpoint does not support pagination
91
+ * - aggregations or grouping are set on the dataset (i.e. for chart view)
92
+ */
93
+ backupReader_: Promise<BaseReader>
94
+
95
+ constructor(url: string, featureTypeName: string) {
96
+ super(url)
97
+ this.endpoint = getWfsEndpoint(url)
98
+
99
+ this.featureType = this.endpoint
100
+ .then((endpoint) => {
101
+ const featureTypes = endpoint.getFeatureTypes()
102
+ return endpoint.getFeatureTypeFull(
103
+ featureTypes.length === 1 && !featureTypeName
104
+ ? featureTypes[0].name
105
+ : featureTypeName
106
+ )
107
+ })
108
+ .then((featureType) => {
109
+ if (!featureType) {
110
+ throw new Error('wfs.featuretype.notfound')
111
+ }
112
+ return featureType
113
+ })
51
114
  }
52
115
 
53
- get properties(): Promise<PropertyInfo[]> {
54
- return this.endpoint
55
- .getFeatureTypeFull(this.featureTypeName)
56
- .then((featureType) =>
57
- Object.keys(featureType.properties).map((prop) => {
58
- const originalType = featureType.properties[prop]
59
- const type =
60
- originalType === 'float' || originalType === 'integer'
61
- ? 'number'
62
- : (originalType as PropertyInfo['type']) // FIXME: ogc-client typing is incorrect, should be a string union
63
- return {
64
- name: prop,
65
- label: prop,
66
- type,
116
+ get backupReader(): Promise<BaseReader> {
117
+ if (this.backupReader_) {
118
+ return this.backupReader_
119
+ }
120
+
121
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(
122
+ ([endpoint, featureType]) => {
123
+ let reader: BaseReader
124
+ if (endpoint.supportsJson(featureType.name)) {
125
+ reader = new GeojsonReader(
126
+ endpoint.getFeatureUrl(featureType.name, {
127
+ asJson: true,
128
+ outputCrs: 'EPSG:4326',
129
+ })
130
+ )
131
+ } else {
132
+ if (
133
+ featureType.outputFormats.find((f) =>
134
+ f.toLowerCase().includes('gml')
135
+ ) &&
136
+ (featureType.defaultCrs === 'EPSG:4326' ||
137
+ featureType.otherCrs?.includes('EPSG:4326'))
138
+ ) {
139
+ reader = new GmlReader(
140
+ endpoint.getFeatureUrl(featureType.name, {
141
+ outputFormat: featureType.outputFormats.find((f) =>
142
+ f.toLowerCase().includes('gml')
143
+ ),
144
+ outputCrs: 'EPSG:4326',
145
+ })
146
+ )
67
147
  }
68
- })
69
- )
148
+ }
149
+ reader.enableCache(this.cacheEnabled)
150
+ reader.load()
151
+ return reader
152
+ }
153
+ )
154
+ return this.backupReader_
155
+ }
156
+
157
+ get properties(): Promise<PropertyInfo[]> {
158
+ return this.featureType.then((featureType) =>
159
+ Object.keys(featureType.properties).map((prop) => {
160
+ const originalType = featureType.properties[prop]
161
+ let type: PropertyInfo['type']
162
+ if (originalType === 'float' || originalType === 'integer') {
163
+ type = 'number'
164
+ } else if (originalType === 'boolean') {
165
+ type = 'string' // we don't handle booleans yet in the data fetcher
166
+ } else {
167
+ type = originalType
168
+ }
169
+ return {
170
+ name: prop,
171
+ label: prop,
172
+ type,
173
+ }
174
+ })
175
+ )
70
176
  }
71
177
 
72
178
  get info(): Promise<DatasetInfo> {
73
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then(
179
+ return this.featureType.then(
74
180
  (result) =>
75
181
  ({
76
182
  itemsCount: result.objectCount,
183
+ hasGeometry: !!result.geometryName,
77
184
  }) as DatasetInfo
78
185
  )
79
186
  }
80
187
 
81
- static async createReader(wfsUrlEndpoint: string, featureTypeName?: string) {
82
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint)
83
- const featureTypes = wfsEndpoint.getFeatureTypes()
84
- const featureType = wfsEndpoint.getFeatureTypeSummary(
85
- featureTypes.length === 1 && !featureTypeName
86
- ? featureTypes[0].name
87
- : featureTypeName
88
- )
89
- if (!featureType) {
90
- throw new Error('wfs.featuretype.notfound')
91
- }
188
+ load() {
189
+ // Nothing to load for Wfs
190
+ }
92
191
 
93
- if (wfsEndpoint.supportsStartIndex()) {
94
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name)
95
- } else if (wfsEndpoint.supportsJson(featureType.name)) {
96
- return new GeojsonReader(
97
- wfsEndpoint.getFeatureUrl(featureType.name, {
98
- asJson: true,
99
- outputCrs: 'EPSG:4326',
100
- })
101
- )
102
- } else {
103
- if (
104
- featureType.outputFormats.find((f) =>
105
- f.toLowerCase().includes('gml')
106
- ) &&
107
- (featureType.defaultCrs === 'EPSG:4326' ||
108
- featureType.otherCrs?.includes('EPSG:4326'))
109
- ) {
110
- return new GmlReader(
111
- wfsEndpoint.getFeatureUrl(featureType.name, {
112
- outputFormat: featureType.outputFormats.find((f) =>
113
- f.toLowerCase().includes('gml')
114
- ),
115
- outputCrs: 'EPSG:4326',
116
- }),
117
- featureType.name,
118
- wfsEndpoint.getVersion()
119
- )
192
+ async read(): Promise<DataItem[]> {
193
+ const endpoint = await this.endpoint
194
+ const featureType = await this.featureType
195
+
196
+ // if we can't use the WFS protocol we fall back to the backup reader
197
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
198
+ const backupReader = await this.backupReader
199
+ backupReader.selectAll()
200
+ if (this.selected) {
201
+ backupReader.select(...this.selected)
120
202
  }
121
- throw new Error('wfs.geojsongml.notsupported')
203
+ if (this.aggregations) {
204
+ backupReader.aggregate(...this.aggregations)
205
+ }
206
+ if (this.groupedBy) {
207
+ backupReader.groupBy(...this.groupedBy)
208
+ }
209
+ if (this.sort) {
210
+ backupReader.orderBy(...this.sort)
211
+ }
212
+ if (this.startIndex !== null && this.count !== null) {
213
+ backupReader.limit(this.startIndex, this.count)
214
+ }
215
+ return backupReader.read()
122
216
  }
123
- }
124
217
 
125
- public async getData(aggregation?, groupedBy?) {
126
- if (aggregation || groupedBy) {
127
- return { items: await this.getQueryData() }
128
- }
129
- const asJson = this.endpoint.supportsJson(this.featureTypeName)
218
+ const asJson = endpoint.supportsJson(featureType.name)
130
219
  const attributes = this.selected ?? undefined
131
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
220
+ let sortBy = null
221
+ if (this.sort) {
222
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]]
223
+ sortBy = Array.isArray(this.sort[0])
224
+ ? this.sort.map(mapSort)
225
+ : mapSort(this.sort)
226
+ }
227
+ const url = endpoint.getFeatureUrl(featureType.name, {
132
228
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
133
229
  ...(this.count !== null && { maxFeatures: this.count }),
134
230
  asJson,
135
231
  outputCrs: 'EPSG:4326',
136
232
  attributes,
137
- // sortBy: this.sort // TODO: no sort in ogc-client?
233
+ sortBy,
138
234
  })
139
235
 
140
- if (Array.isArray(this.sort) && this.sort.length > 0) {
141
- const finalUrl = new URL(url)
142
- const sorts = this.sort
143
- .map(
144
- (fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`
145
- )
146
- .join(',')
147
- // Direct update on string url to prevent encoding of +A and +D
148
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`
149
- }
150
-
151
- return fetchDataAsText(url, this.cacheActive).then((text) =>
236
+ return fetchDataAsText(url, this.cacheEnabled).then((text) =>
152
237
  asJson
153
238
  ? parseGeojson(text)
154
- : parseGml(text, this.featureTypeName, this.version)
155
- )
156
- }
157
-
158
- public async getQueryData() {
159
- const items = (await this.getData()).items
160
- const jsonItems = getJsonDataItemsProxy(items)
161
- const query = generateSqlQuery(
162
- this.selected,
163
- this.filter,
164
- this.sort,
165
- this.startIndex,
166
- this.count,
167
- this.groupedBy,
168
- this.aggregations
169
- )
170
- const result = await import('alasql').then((module) =>
171
- module.default(query, [jsonItems])
239
+ : parseGml(text, featureType.name, endpoint.getVersion())
172
240
  )
173
- return result.map(jsonToGeojsonFeature)
174
- }
175
-
176
- load() {
177
- // Nothing to load for Wfs
178
- }
179
-
180
- async read(): Promise<DataItem[]> {
181
- return (await this.getData(this.aggregations, this.groupedBy)).items
182
241
  }
183
242
  }
@@ -2,14 +2,11 @@ import {
2
2
  DataItem,
3
3
  DatasetHeaders,
4
4
  FetchError,
5
- PropertyInfo,
6
5
  SupportedType,
7
6
  SupportedTypes,
8
7
  } from './model'
9
8
  import { sharedFetch, useCache } from '@camptocamp/ogc-client'
10
9
  import { parseHeaders } from './headers'
11
- import { parse as parseDate } from 'date-fns/parse'
12
- import { parseISO as parseIsoDate } from 'date-fns/parseISO'
13
10
 
14
11
  export async function inferDatasetType(
15
12
  url: string,
@@ -95,146 +92,6 @@ export function fetchDataAsArrayBuffer(
95
92
  })
96
93
  }
97
94
 
98
- export function tryParseDate(input: unknown): Date | null {
99
- if (typeof input !== 'string') return null
100
-
101
- function tryIso(value: string) {
102
- const parsed = parseIsoDate(value)
103
- return isNaN(parsed.getDate()) ? null : parsed
104
- }
105
- function tryFormat(value: string, format: string) {
106
- const parsed = parseDate(value, format, new Date())
107
- return isNaN(parsed.getDate()) ? null : parsed
108
- }
109
- return (
110
- tryIso(input) ||
111
- tryFormat(input, 'dd/MM/yyyy') ||
112
- tryFormat(input, 'dd.MM.yyyy') ||
113
- tryFormat(input, 'MM/dd/yyyy') ||
114
- null
115
- )
116
- }
117
-
118
- export function tryParseNumber(input: string): number | null {
119
- if (isNaN(input as any)) return null
120
- const parsed = parseFloat(input)
121
- return isNaN(parsed) ? null : parsed
122
- }
123
-
124
- export function jsonToGeojsonFeature(object: { [key: string]: any }): DataItem {
125
- const { id, properties } = Object.keys(object)
126
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
127
- .reduce(
128
- (prev, curr) =>
129
- curr.toLowerCase().endsWith('id')
130
- ? {
131
- ...prev,
132
- id: object[curr],
133
- }
134
- : {
135
- ...prev,
136
- properties: { ...prev.properties, [curr]: object[curr] },
137
- },
138
- { id: undefined, properties: {} }
139
- )
140
- return {
141
- type: 'Feature',
142
- geometry: null,
143
- properties,
144
- ...(id !== undefined && { id }),
145
- }
146
- }
147
-
148
- function mutateProperties(
149
- items: DataItem[],
150
- mutators: { [fieldName: string]: (value: unknown) => unknown }
151
- ): DataItem[] {
152
- const mutatorKeys = Object.keys(mutators)
153
- for (let i = 0, ii = items.length; i < ii; i++) {
154
- const item = items[i]
155
- for (const mutatorField of mutatorKeys) {
156
- if (!(mutatorField in item.properties)) continue
157
- item.properties[mutatorField] = mutators[mutatorField](
158
- item.properties[mutatorField]
159
- )
160
- }
161
- }
162
- return items
163
- }
164
-
165
- const SAMPLE_SIZE = 20
166
-
167
- /**
168
- * This will infer field types from a list of data items and cast the values accordingly
169
- * @param items
170
- * @param inferTypes
171
- */
172
- export function processItemProperties(
173
- items: DataItem[],
174
- inferTypes = false
175
- ): {
176
- items: DataItem[]
177
- properties: PropertyInfo[]
178
- } {
179
- const foundFields: { [name: string]: PropertyInfo } = {}
180
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
181
- const item = items[i]
182
- const fields = Object.keys(item.properties)
183
- for (const field of fields) {
184
- if (!(field in foundFields)) {
185
- foundFields[field] = {
186
- label: field,
187
- name: field,
188
- type: null,
189
- }
190
- }
191
- const value = item.properties[field]
192
- const info = foundFields[field]
193
- if (value === undefined || value === '' || value === null) continue
194
-
195
- if (!inferTypes) {
196
- if (info.type === null && typeof value === 'number') {
197
- info.type = 'number'
198
- } else if (info.type === 'number' && typeof value !== 'number') {
199
- info.type = 'string'
200
- }
201
- continue
202
- }
203
-
204
- const parsedNumber = tryParseNumber(value)
205
- if (info.type === null && parsedNumber !== null) {
206
- info.type = 'number'
207
- continue
208
- } else if (info.type === 'number' && parsedNumber === null) {
209
- info.type = 'string'
210
- continue
211
- }
212
- const parsedDate = tryParseDate(value)
213
- if (info.type === null && parsedDate !== null) {
214
- info.type = 'date'
215
- } else if (info.type === 'date' && parsedDate === null) {
216
- info.type = 'string'
217
- }
218
- }
219
- }
220
-
221
- const properties: PropertyInfo[] = []
222
- const mutators = {}
223
- for (const field in foundFields) {
224
- const info = foundFields[field]
225
- if (info.type === 'number') {
226
- mutators[field] = tryParseNumber
227
- } else if (info.type === 'date') {
228
- mutators[field] = tryParseDate
229
- }
230
- properties.push({ ...info, type: info.type || 'string' })
231
- }
232
- if (inferTypes) {
233
- mutateProperties(items, mutators)
234
- }
235
- return { items, properties }
236
- }
237
-
238
95
  /**
239
96
  * This creates a Proxy that allows reading and writing to the data item properties
240
97
  * as if it was a simple array of JSON objects
@@ -2,6 +2,5 @@ export * from './lib/i18n.providers'
2
2
  export * from './lib/i18n.constants'
3
3
  export * from './lib/i18n.interceptor'
4
4
  export * from './lib/file.translate.loader'
5
- export * from './lib/embedded.translate.loader'
6
5
  export * from './lib/translate.loader.utils'
7
6
  export * from './lib/language-codes'
@@ -1,6 +1,4 @@
1
- import { HttpClient } from '@angular/common/http'
2
1
  import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'
3
- import { FileTranslateLoader } from './file.translate.loader'
4
2
  import {
5
3
  TranslateCompiler,
6
4
  TranslateDefaultParser,
@@ -9,26 +7,34 @@ import {
9
7
  TranslateParser,
10
8
  } from '@ngx-translate/core'
11
9
  import { Injectable } from '@angular/core'
10
+ import { TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader'
11
+ import { HttpClient } from '@angular/common/http'
12
+ import { FileTranslateLoader } from './file.translate.loader'
12
13
 
13
14
  export const DEFAULT_LANG = 'en'
14
15
 
15
16
  // Caution: changing this can break language selection from third parties!
16
17
  export const LANGUAGE_STORAGE_KEY = 'geonetwork-ui-language'
17
18
 
18
- export function HttpLoaderFactory(http: HttpClient) {
19
- return new FileTranslateLoader(http, './assets/i18n/')
20
- }
21
-
22
19
  export const TRANSLATE_DEFAULT_CONFIG: TranslateModuleConfig = {
23
20
  compiler: {
24
21
  provide: TranslateCompiler,
25
22
  useClass: TranslateMessageFormatCompiler,
26
23
  },
27
- loader: {
28
- provide: TranslateLoader,
29
- useFactory: HttpLoaderFactory,
30
- deps: [HttpClient],
31
- },
24
+ loader: [
25
+ {
26
+ provide: TRANSLATE_HTTP_LOADER_CONFIG,
27
+ useValue: {
28
+ prefix: './assets/i18n/',
29
+ suffix: '.json',
30
+ },
31
+ },
32
+ {
33
+ provide: TranslateLoader,
34
+ useClass: FileTranslateLoader,
35
+ deps: [HttpClient, TRANSLATE_HTTP_LOADER_CONFIG],
36
+ },
37
+ ],
32
38
  }
33
39
 
34
40
  @Injectable()
@@ -6,7 +6,7 @@ import {
6
6
  } from '@angular/common/http'
7
7
  import { DEFAULT_LANG } from './i18n.constants'
8
8
  import { Observable } from 'rxjs'
9
- import { Injectable, Injector, inject } from '@angular/core'
9
+ import { inject, Injectable, Injector } from '@angular/core'
10
10
  import { TranslateService } from '@ngx-translate/core'
11
11
 
12
12
  @Injectable()
@@ -21,7 +21,7 @@ export class I18nInterceptor implements HttpInterceptor {
21
21
  const translate = this.injector.get(TranslateService)
22
22
  request = request.clone({
23
23
  setHeaders: {
24
- 'Accept-Language': translate.currentLang || DEFAULT_LANG,
24
+ 'Accept-Language': translate.getCurrentLang() || DEFAULT_LANG,
25
25
  },
26
26
  })
27
27
  return next.handle(request)