geonetwork-ui 2.10.0-dev.e97ea6950 → 2.10.0-dev.fb2893f44

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 (129) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1622 -6598
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +196 -87
  4. package/index.d.ts.map +1 -1
  5. package/material-styles.css +483 -0
  6. package/package.json +25 -35
  7. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  20. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +2 -2
  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/iso19115-3/iso19115-3.converter.ts +7 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
  30. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  31. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  32. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +68 -34
  33. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  34. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  35. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  36. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  37. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +11 -5
  38. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  39. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  40. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  41. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
  42. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  43. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -0
  44. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +0 -1
  45. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +10 -1
  46. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  47. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  49. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  50. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  51. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  52. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +32 -34
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  55. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  56. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +8 -7
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +6 -6
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.css +0 -0
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.html +5 -0
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.ts +89 -0
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +1 -0
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  67. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -1
  68. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +77 -5
  69. package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
  70. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  71. package/src/libs/feature/editor/src/lib/services/editor.service.ts +1 -1
  72. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  73. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  74. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  75. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  76. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  77. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  78. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  79. package/src/libs/ui/dataviz/src/index.ts +0 -1
  80. package/src/libs/ui/elements/src/index.ts +2 -0
  81. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +96 -0
  82. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  83. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  84. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  85. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  86. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  87. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  88. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  89. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  90. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  91. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  92. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  93. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +28 -67
  94. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  95. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  96. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  97. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  98. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  99. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  100. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  101. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  102. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  103. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  104. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  105. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  106. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  107. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  108. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  109. package/src/libs/util/i18n/src/index.ts +0 -1
  110. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  111. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  112. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  113. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  114. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  115. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  116. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  117. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  118. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  119. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  120. package/style.css +0 -1
  121. package/tailwind.base.css +19 -2
  122. package/translations/de.json +29 -20
  123. package/translations/en.json +29 -20
  124. package/translations/es.json +29 -20
  125. package/translations/fr.json +29 -20
  126. package/translations/it.json +29 -20
  127. package/translations/nl.json +29 -20
  128. package/translations/pt.json +29 -20
  129. package/translations/sk.json +29 -20
@@ -69,6 +69,13 @@ export interface MetadataQualityConfig {
69
69
  ENABLED: boolean
70
70
  }
71
71
 
72
+ export type NewRecordStandard = 'iso19139' | 'iso19115-3'
73
+
74
+ export interface EditorConfig {
75
+ NEW_RECORD_DEFAULT_LANGUAGE?: string
76
+ NEW_RECORD_STANDARD?: NewRecordStandard
77
+ }
78
+
72
79
  export type CustomTranslations = { [translationKey: string]: string }
73
80
  export type CustomTranslationsAllLanguages = {
74
81
  [lang: string]: CustomTranslations
@@ -1,4 +1,4 @@
1
- import { toLang2 } from '../../../../../libs/util/i18n/src'
1
+ import { LANG_2_TO_3_MAPPER, toLang2 } from '../../../../../libs/util/i18n/src'
2
2
 
3
3
  const flatten = (
4
4
  base: string,
@@ -137,3 +137,52 @@ export function checkMetadataLanguage(
137
137
  }
138
138
  return parsedConfigSection
139
139
  }
140
+
141
+ export function checkNewRecordDefaultLanguage(
142
+ parsedConfigSection: any,
143
+ outWarnings: string[]
144
+ ) {
145
+ const lang2 = toLang2(
146
+ parsedConfigSection.new_record_default_language.toLowerCase()
147
+ )
148
+ if (!(lang2 in LANG_2_TO_3_MAPPER)) {
149
+ outWarnings.push(
150
+ `In the [editing] section: new_record_default_language = "${parsedConfigSection.new_record_default_language}" is not a recognized ISO 639 language code`
151
+ )
152
+ return {
153
+ ...parsedConfigSection,
154
+ new_record_default_language: undefined,
155
+ }
156
+ }
157
+ return {
158
+ ...parsedConfigSection,
159
+ new_record_default_language: lang2,
160
+ }
161
+ }
162
+
163
+ export function checkNewRecordStandard(
164
+ parsedConfigSection: any,
165
+ outWarnings: string[]
166
+ ) {
167
+ const standard = parsedConfigSection.new_record_standard
168
+ const normalizedStandard =
169
+ typeof standard === 'string' ? standard.trim().toLowerCase() : null
170
+
171
+ if (
172
+ normalizedStandard === 'iso19139' ||
173
+ normalizedStandard === 'iso19115-3'
174
+ ) {
175
+ return {
176
+ ...parsedConfigSection,
177
+ new_record_standard: normalizedStandard,
178
+ }
179
+ }
180
+
181
+ outWarnings.push(
182
+ `In the [editing] section: new_record_standard = "${standard}" is not a supported metadata standard`
183
+ )
184
+ return {
185
+ ...parsedConfigSection,
186
+ new_record_standard: undefined,
187
+ }
188
+ }
@@ -1,4 +1,3 @@
1
- import { BaseReader } from './base'
2
1
  import { DataItem, DatasetInfo, PropertyInfo } from '../model'
3
2
  import { getJsonDataItemsProxy, jsonToGeojsonFeature } from '../utils'
4
3
  import { generateSqlQuery } from '../sql-utils'
@@ -58,7 +57,7 @@ export class BaseFileReader extends BaseCacheReader {
58
57
  this.groupedBy,
59
58
  this.aggregations
60
59
  )
61
- const result = await import('alasql').then((module) =>
60
+ const result: any[] = await import('alasql').then((module) =>
62
61
  module.default(query, [jsonItems])
63
62
  )
64
63
  return result.map(jsonToGeojsonFeature)
@@ -1,10 +1,13 @@
1
1
  import { EndpointError, WfsEndpoint, WfsVersion } from '@camptocamp/ogc-client'
2
2
  import { DataItem, DatasetInfo, FetchError, PropertyInfo } from '../model'
3
- import { fetchDataAsText } from '../utils'
3
+ import {
4
+ fetchDataAsText,
5
+ getJsonDataItemsProxy,
6
+ jsonToGeojsonFeature,
7
+ } from '../utils'
4
8
  import { GmlReader, parseGml } from './gml'
5
9
  import { GeojsonReader, parseGeojson } from './geojson'
6
10
  import { BaseCacheReader } from './base-cache'
7
- import { getJsonDataItemsProxy, jsonToGeojsonFeature } from '../utils'
8
11
  import { generateSqlQuery } from '../sql-utils'
9
12
 
10
13
  export async function getWfsEndpoint(wfsUrl: string): Promise<WfsEndpoint> {
@@ -167,7 +170,7 @@ export class WfsReader extends BaseCacheReader {
167
170
  this.groupedBy,
168
171
  this.aggregations
169
172
  )
170
- const result = await import('alasql').then((module) =>
173
+ const result: any[] = await import('alasql').then((module) =>
171
174
  module.default(query, [jsonItems])
172
175
  )
173
176
  return result.map(jsonToGeojsonFeature)
@@ -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)
@@ -1,7 +1,8 @@
1
1
  import {
2
- APP_INITIALIZER,
3
2
  EnvironmentProviders,
3
+ inject,
4
4
  makeEnvironmentProviders,
5
+ provideAppInitializer,
5
6
  Provider,
6
7
  } from '@angular/core'
7
8
  import {
@@ -22,17 +23,16 @@ import {
22
23
  } from '@angular/common/http'
23
24
  import { I18nInterceptor } from './i18n.interceptor'
24
25
 
25
- function i18nInitializerFromLocalStorageFactory(translate: TranslateService) {
26
- return () => {
27
- translate.setDefaultLang(DEFAULT_LANG)
28
- let storageLang = null
29
- try {
30
- storageLang = localStorage.getItem(LANGUAGE_STORAGE_KEY)
31
- } catch (error) {
32
- console.warn(error)
33
- }
34
- translate.use(storageLang || translate.getBrowserLang() || DEFAULT_LANG)
26
+ function i18nInitializerFromLocalStorageFactory() {
27
+ const translate = inject(TranslateService)
28
+ translate.setFallbackLang(DEFAULT_LANG)
29
+ let storageLang = null
30
+ try {
31
+ storageLang = localStorage.getItem(LANGUAGE_STORAGE_KEY)
32
+ } catch (error) {
33
+ console.warn(error)
35
34
  }
35
+ translate.use(storageLang || translate.getBrowserLang() || DEFAULT_LANG)
36
36
  }
37
37
 
38
38
  export function provideI18n(
@@ -56,12 +56,9 @@ export function provideI18n(
56
56
  provideTranslateService(usedConfig),
57
57
  ]
58
58
  if (useLocalStorage) {
59
- providers.push({
60
- provide: APP_INITIALIZER,
61
- useFactory: i18nInitializerFromLocalStorageFactory,
62
- deps: [TranslateService],
63
- multi: true,
64
- })
59
+ providers.push(
60
+ provideAppInitializer(i18nInitializerFromLocalStorageFactory)
61
+ )
65
62
  }
66
63
 
67
64
  return makeEnvironmentProviders(providers)
@@ -0,0 +1,48 @@
1
+ import { inject, Injectable, InjectionToken } from '@angular/core'
2
+ import {
3
+ provideTranslateService,
4
+ TranslateCompiler,
5
+ TranslateLoader,
6
+ TranslationObject,
7
+ } from '@ngx-translate/core'
8
+ import { Observable, of } from 'rxjs'
9
+ import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'
10
+
11
+ /**
12
+ * This translate loader is used for testing purposes, it allows to provide translations directly in the test file without having to create JSON files
13
+ * Taken from https://github.com/mwootendev/ngx-translate-plugins/issues/87
14
+ */
15
+
16
+ const TestTranslations = new InjectionToken<Record<string, TranslationObject>>(
17
+ 'test-translations'
18
+ )
19
+
20
+ @Injectable()
21
+ class TestTranslateLoader implements TranslateLoader {
22
+ translations = inject(TestTranslations)
23
+ getTranslation(lang: string): Observable<TranslationObject> {
24
+ return of(this.translations[lang] as TranslationObject)
25
+ }
26
+ }
27
+
28
+ export function provideTranslateTestingService(
29
+ translations: Record<string, TranslationObject>
30
+ ) {
31
+ return provideTranslateService({
32
+ loader: [
33
+ {
34
+ provide: TestTranslations,
35
+ useValue: translations,
36
+ },
37
+ {
38
+ provide: TranslateLoader,
39
+ useClass: TestTranslateLoader,
40
+ },
41
+ ],
42
+ fallbackLang: 'en',
43
+ compiler: {
44
+ provide: TranslateCompiler,
45
+ useClass: TranslateMessageFormatCompiler,
46
+ },
47
+ })
48
+ }
@@ -266,7 +266,7 @@ export function checkFileFormat(
266
266
  new RegExp(`[./]${format}`, 'i').test(link.name.toLowerCase())) ||
267
267
  ('url' in link &&
268
268
  new RegExp(`[./]${format}`, 'i').test(link.url.toString())) ||
269
- ('name' in link && link.name.toLowerCase().includes(format))
269
+ ('name' in link && new RegExp(`\\b${format}\\b`, 'i').test(link.name))
270
270
  )
271
271
  }
272
272
 
@@ -3,27 +3,41 @@ import {
3
3
  RecordKind,
4
4
  } from '../../../../../../libs/common/domain/src/lib/model/record'
5
5
 
6
+ type TValidatorEntry = {
7
+ validator: (metadata: Partial<CatalogRecord>) => boolean
8
+ alias?: string
9
+ }
10
+
6
11
  type TValidatorMapper = {
7
- [key: string]: (metadata: Partial<CatalogRecord>) => boolean
12
+ [key: string]: TValidatorEntry
8
13
  }
9
14
 
10
15
  const ValidatorMapper: TValidatorMapper = {
11
- title: (record) => !!record?.title,
12
- abstract: (record) => !!record?.abstract,
13
- keywords: (record) => (record?.keywords?.length ?? 0) > 0,
14
- legalConstraints: (record) =>
15
- !!(
16
- record?.legalConstraints?.length &&
17
- record.legalConstraints.some((c) => c?.text?.trim().length > 0)
18
- ),
19
- contacts: (record) =>
20
- !!record?.contacts?.[0]?.email &&
21
- record.contacts[0].email !== 'missing@missing.com',
22
- updateFrequency: (record) =>
23
- !!record?.updateFrequency && record.updateFrequency !== 'unknown',
24
- topics: (record) => (record?.topics?.length ?? 0) > 0,
25
- organisation: (record) => !!record?.contacts?.[0]?.organization?.name,
26
- source: (record) => !!record?.extras?.sourcesIdentifiers,
16
+ title: { validator: (record) => !!record?.title },
17
+ abstract: { validator: (record) => !!record?.abstract },
18
+ keywords: { validator: (record) => (record?.keywords?.length ?? 0) > 0 },
19
+ legalConstraints: {
20
+ validator: (record) =>
21
+ !!(
22
+ record?.legalConstraints?.length &&
23
+ record.legalConstraints.some((c) => c?.text?.trim().length > 0)
24
+ ),
25
+ },
26
+ contacts: {
27
+ validator: (record) =>
28
+ !!record?.contacts?.[0]?.email &&
29
+ record.contacts[0].email !== 'missing@missing.com',
30
+ },
31
+ updateFrequency: {
32
+ validator: (record) =>
33
+ !!record?.updateFrequency && record.updateFrequency !== 'unknown',
34
+ },
35
+ topics: { validator: (record) => (record?.topics?.length ?? 0) > 0 },
36
+ organisation: {
37
+ validator: (record) => !!record?.contacts?.[0]?.organization?.name,
38
+ alias: 'contacts',
39
+ },
40
+ source: { validator: (record) => !!record?.extras?.sourcesIdentifiers },
27
41
  } as const
28
42
 
29
43
  export type ValidatorMapperKeys = keyof typeof ValidatorMapper & string
@@ -67,6 +81,7 @@ export function getQualityValidators(
67
81
 
68
82
  return filteredProps.map((name) => ({
69
83
  name,
70
- validator: () => ValidatorMapper[name](record),
84
+ validator: () => ValidatorMapper[name].validator(record),
85
+ alias: ValidatorMapper[name].alias,
71
86
  }))
72
87
  }
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import { TranslateService } from '@ngx-translate/core'
3
3
  import { type Locale } from 'date-fns/locale'
4
4
  import { formatDistance } from 'date-fns/formatDistance'
@@ -30,13 +30,13 @@ export class DateService {
30
30
  locale: string
31
31
  dateObj: Date
32
32
  } {
33
- const locale = this.translateService.currentLang || DEFAULT_LANGUAGE
33
+ const locale = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE
34
34
  const dateObj = this.getDateObject(date)
35
35
  return { locale, dateObj }
36
36
  }
37
37
 
38
38
  private async getDateLocale(): Promise<Locale> {
39
- const lang = this.translateService.currentLang || DEFAULT_LANGUAGE
39
+ const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE
40
40
  const locales = await this.dateLocales
41
41
  return locales[lang]
42
42
  }
@@ -1,4 +1,11 @@
1
- import type { Feature, FeatureCollection, Geometry, Position } from 'geojson'
1
+ import type {
2
+ Feature,
3
+ FeatureCollection,
4
+ Geometry,
5
+ Polygon,
6
+ Position,
7
+ } from 'geojson'
8
+ import type { DatasetSpatialExtent } from '../../../../../../libs/common/domain/src/lib/model/record'
2
9
 
3
10
  /**
4
11
  * @returns The geometry if available, otherwise null.
@@ -94,3 +101,53 @@ export function getGeometryBoundingBox(geometry: Geometry): BoundingBox {
94
101
  return coordinatesReducer(emptyExtent, geometry.coordinates)
95
102
  }
96
103
  }
104
+
105
+ /**
106
+ * Builds the closed GeoJSON Polygon matching the given bounding box.
107
+ */
108
+ export function bboxToPolygon(bbox: BoundingBox): Polygon {
109
+ const [minX, minY, maxX, maxY] = bbox
110
+ return {
111
+ type: 'Polygon',
112
+ coordinates: [
113
+ [
114
+ [minX, minY],
115
+ [minX, maxY],
116
+ [maxX, maxY],
117
+ [maxX, minY],
118
+ [minX, minY],
119
+ ],
120
+ ],
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Returns the geometry carried by a spatial extent, falling back to the polygon
126
+ * derived from its bounding box. Returns null when the extent has neither.
127
+ */
128
+ export function spatialExtentToGeometry(
129
+ extent: DatasetSpatialExtent
130
+ ): Geometry | null {
131
+ if (extent.geometry) return extent.geometry
132
+ if (extent.bbox) return bboxToPolygon(extent.bbox)
133
+ return null
134
+ }
135
+
136
+ /**
137
+ * Converts a list of dataset spatial extents into a GeoJSON FeatureCollection,
138
+ * e.g. to be used as the data source of a map layer.
139
+ */
140
+ export function spatialExtentsToFeatureCollection(
141
+ extents: DatasetSpatialExtent[]
142
+ ): FeatureCollection {
143
+ return {
144
+ type: 'FeatureCollection',
145
+ features: extents.reduce<Feature[]>((features, extent) => {
146
+ const geometry = spatialExtentToGeometry(extent)
147
+ if (geometry) {
148
+ features.push({ type: 'Feature', properties: {}, geometry })
149
+ }
150
+ return features
151
+ }, []),
152
+ }
153
+ }
@@ -13,3 +13,4 @@ export * from './sort-by'
13
13
  export * from './strip-html'
14
14
  export * from './temporal-extent-union'
15
15
  export * from './url'
16
+ export * from './user-display'
@@ -0,0 +1,32 @@
1
+ import { UserModel } from '../../../../../../libs/common/domain/src/lib/model/user'
2
+ import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+
4
+ export function getIndividualDisplayName(individual: Individual): string {
5
+ const nameParts = [individual.firstName, individual.lastName]
6
+ .filter(Boolean)
7
+ .join(' ')
8
+ const orgPart = individual.organization?.name
9
+ ? ` (${individual.organization.name})`
10
+ : ''
11
+ if (nameParts) return `${nameParts}${orgPart}`
12
+ return individual.organization?.name ?? individual.email ?? ''
13
+ }
14
+
15
+ export function getAddressLines(address: string | undefined): string[] {
16
+ return address
17
+ ? address
18
+ .split(',')
19
+ .map((part) => part.trim())
20
+ .filter(Boolean)
21
+ : []
22
+ }
23
+
24
+ export function toIndividual(user: UserModel): Individual {
25
+ return {
26
+ firstName: user.name,
27
+ lastName: user.surname,
28
+ email: user.email,
29
+ role: 'unspecified',
30
+ organization: user.organisation ? { name: user.organisation } : undefined,
31
+ }
32
+ }
package/style.css CHANGED
@@ -1,4 +1,3 @@
1
1
  @import 'tippy.js/dist/tippy.css';
2
2
  @import 'ol/ol.css';
3
- @import '@angular/material/prebuilt-themes/indigo-pink.css';
4
3
  @import './tailwind.base.css';
package/tailwind.base.css CHANGED
@@ -1,3 +1,5 @@
1
+ @import './material-styles.css';
2
+
1
3
  @tailwind base;
2
4
  @tailwind components;
3
5
  @tailwind utilities;
@@ -74,7 +76,10 @@
74
76
  --bg-hover: var(--gn-ui-button-bg-hover, var(--color-background));
75
77
  --font-size: var(--gn-ui-button-font-size, 1em);
76
78
  --color: var(--gn-ui-button-color, var(--color-main));
77
- @apply flex flex-row items-center justify-center
79
+ --disabled-opacity: var(--gn-ui-button-disabled-opacity, 0.5);
80
+ --justify: var(--gn-ui-button-justify, center);
81
+ justify-content: var(--justify);
82
+ @apply flex flex-row items-center
78
83
  text-[length:--font-size] leading-none
79
84
  text-[color:--color]
80
85
  p-[--padding] rounded-[--rounded] w-[--width] h-[--height] overflow-hidden
@@ -82,7 +87,7 @@
82
87
  hover:bg-[--bg-hover] focus:bg-[--bg-hover] active:bg-[--background]
83
88
  outline-none
84
89
  relative
85
- disabled:opacity-50 disabled:pointer-events-none
90
+ disabled:opacity-[--disabled-opacity] disabled:pointer-events-none
86
91
  focus:z-10 active:z-10 hover:z-10;
87
92
  }
88
93
  /* makes sure icons will not make the buttons grow vertically */
@@ -116,6 +121,12 @@
116
121
  border-gray-100 focus:ring-4 focus:ring-gray-50;
117
122
  }
118
123
 
124
+ .gn-ui-btn-gray-light {
125
+ @apply gn-ui-btn text-black
126
+ bg-gray-50 hover:bg-gray-100 focus:bg-gray-100 active:bg-gray-200
127
+ border-gray-50 focus:ring-4 focus:ring-gray-100;
128
+ }
129
+
119
130
  .gn-ui-btn-black {
120
131
  @apply gn-ui-btn text-white
121
132
  bg-black hover:bg-gray-950 focus:bg-gray-950 active:bg-gray-900
@@ -134,6 +145,12 @@
134
145
  bg-white border-white focus:ring-4 focus:ring-gray-300;
135
146
  }
136
147
 
148
+ .gn-ui-btn-primary-light {
149
+ @apply gn-ui-btn text-primary-darkest
150
+ bg-primary-white hover:bg-primary focus:bg-primary hover:text-white focus:text-white active:bg-primary-darker
151
+ border-transparent;
152
+ }
153
+
137
154
  .gn-ui-card-s {
138
155
  @apply min-h-[64px] w-full xs:min-w-[260px] xs:max-w-[280px] sm:min-w-[300px] sm:max-w-[500px] md:min-w-[230px];
139
156
  }
@@ -150,6 +150,14 @@
150
150
  "editor.record.form.draft.updateAlert": "Seit der Erstellung dieses Entwurfs wurde dieser Datensatz am { date } von { user } geändert. Durch das Veröffentlichen Ihrer Version können dessen Änderungen gelöscht werden. Um dies zu vermeiden, können Sie Ihre Änderungen rückgängig machen oder Ihre Version in voller Kenntnis der Sachlage veröffentlichen.",
151
151
  "editor.record.form.field.abstract": "Kurzbeschreibung",
152
152
  "editor.record.form.field.constraintsShortcuts": "",
153
+ "editor.record.form.field.contactDetails.email": "",
154
+ "editor.record.form.field.contactDetails.email.placeholder": "",
155
+ "editor.record.form.field.contactDetails.firstName": "",
156
+ "editor.record.form.field.contactDetails.firstName.placeholder": "",
157
+ "editor.record.form.field.contactDetails.lastName": "",
158
+ "editor.record.form.field.contactDetails.lastName.placeholder": "",
159
+ "editor.record.form.field.contactDetails.organization": "",
160
+ "editor.record.form.field.contactDetails.organization.placeholder": "",
153
161
  "editor.record.form.field.contacts": "Ansprechpartner - Email",
154
162
  "editor.record.form.field.contacts.noContact": "Bitte geben Sie mindestens einen Ansprechpartner an.",
155
163
  "editor.record.form.field.contacts.placeholder": "Kontakt auswählen",
@@ -159,6 +167,7 @@
159
167
  "editor.record.form.field.legalConstraints": "Rechtliche Einschränkung",
160
168
  "editor.record.form.field.license": "Lizenz",
161
169
  "editor.record.form.field.onlineLinkResources": "Beigefügte Ressourcen",
170
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Link",
162
171
  "editor.record.form.field.onlineResource.cancel": "Abbrechen",
163
172
  "editor.record.form.field.onlineResource.confirm": "Bestätigen",
164
173
  "editor.record.form.field.onlineResource.dialogTitle": "Vorschau des Datensatzes bearbeiten",
@@ -234,25 +243,25 @@
234
243
  "editor.record.form.temporalExtents.addRange": "Zeitraum",
235
244
  "editor.record.form.temporalExtents.date": "Datum",
236
245
  "editor.record.form.temporalExtents.range": "Datumsbereich",
237
- "editor.record.form.topics.inspire.biota": "Biologie",
238
- "editor.record.form.topics.inspire.boundaries": "Grenzen",
239
- "editor.record.form.topics.inspire.climatology": "Klimatologie/Meteorologie/Atmosphäre",
240
- "editor.record.form.topics.inspire.economy": "Wirtschaft",
241
- "editor.record.form.topics.inspire.elevation": "Höhenangaben",
242
- "editor.record.form.topics.inspire.environnement": "Umwelt",
243
- "editor.record.form.topics.inspire.farming": "Landwirtschaft",
244
- "editor.record.form.topics.inspire.geoscientific": "Geowissenschaften",
245
- "editor.record.form.topics.inspire.health": "Gesundheitswesen",
246
- "editor.record.form.topics.inspire.imagery": "Bilddaten/Basiskarten/Landbedeckung",
247
- "editor.record.form.topics.inspire.intelligence": "Aufklärung/Militär",
248
- "editor.record.form.topics.inspire.location": "Ortsangaben",
249
- "editor.record.form.topics.inspire.oceans": "Meere",
250
- "editor.record.form.topics.inspire.planning": "Planungsunterlagen/Kataster",
251
- "editor.record.form.topics.inspire.society": "Gesellschaft",
252
- "editor.record.form.topics.inspire.structure": "Bauwerke",
253
- "editor.record.form.topics.inspire.transportation": "Verkehrswesen",
254
- "editor.record.form.topics.inspire.utilities": "Ver- und Entsorgung/Nachrichtenwesen",
255
- "editor.record.form.topics.inspire.waters": "Binnengewässer",
246
+ "editor.record.form.topics.iso.biota": "Biologie",
247
+ "editor.record.form.topics.iso.boundaries": "Grenzen",
248
+ "editor.record.form.topics.iso.climatologyMeteorologyAtmosphere": "Klimatologie/Meteorologie/Atmosphäre",
249
+ "editor.record.form.topics.iso.economy": "Wirtschaft",
250
+ "editor.record.form.topics.iso.elevation": "Höhenangaben",
251
+ "editor.record.form.topics.iso.environment": "Umwelt",
252
+ "editor.record.form.topics.iso.farming": "Landwirtschaft",
253
+ "editor.record.form.topics.iso.geoscientificInformation": "Geowissenschaften",
254
+ "editor.record.form.topics.iso.health": "Gesundheitswesen",
255
+ "editor.record.form.topics.iso.imageryBaseMapsEarthCover": "Bilddaten/Basiskarten/Landbedeckung",
256
+ "editor.record.form.topics.iso.intelligenceMilitary": "Aufklärung/Militär",
257
+ "editor.record.form.topics.iso.location": "Ortsangaben",
258
+ "editor.record.form.topics.iso.oceans": "Meere",
259
+ "editor.record.form.topics.iso.planningCadastre": "Planungsunterlagen/Kataster",
260
+ "editor.record.form.topics.iso.society": "Gesellschaft",
261
+ "editor.record.form.topics.iso.structure": "Bauwerke",
262
+ "editor.record.form.topics.iso.transportation": "Verkehrswesen",
263
+ "editor.record.form.topics.iso.utilitiesCommunication": "Ver- und Entsorgung/Nachrichtenwesen",
264
+ "editor.record.form.topics.iso.inlandWaters": "Binnengewässer",
256
265
  "editor.record.form.topics.placeholder": "Ein oder mehrere Themen auswählen",
257
266
  "editor.record.form.updateFrequency.planned": "Dieses Dataset wird regelmäßig aktualisiert.",
258
267
  "editor.record.importFromExternalFile.failure.body": "Beim Importieren des Datensatzes ist ein Fehler aufgetreten:",
@@ -494,7 +503,6 @@
494
503
  "record.metadata.preview.config.idle": "Standardvorschau festlegen",
495
504
  "record.metadata.preview.config.saved": "Gespeichert!",
496
505
  "record.metadata.preview.config.saving": "Speichern...",
497
- "record.metadata.producer": "Datenproduzent",
498
506
  "record.metadata.publication": "Veröffentlichungsdatum",
499
507
  "record.metadata.publications": "{count, plural, =0{Veröffentlichungsdatum} one{Veröffentlichungsdatum} other{Veröffentlichungen}}",
500
508
  "record.metadata.quality": "Metadatenqualität",
@@ -520,6 +528,7 @@
520
528
  "record.metadata.quality.updateFrequency.failed": "Aktualisierungsfrequenz nicht angegeben",
521
529
  "record.metadata.quality.updateFrequency.success": "Aktualisierungsfrequenz angegeben",
522
530
  "record.metadata.related": "Entdecken Sie den Katalog",
531
+ "record.metadata.resource.contacts": "Kontakte zur Ressource",
523
532
  "record.metadata.resourceCreated": "Erstellt",
524
533
  "record.metadata.resourcePublished": "Veröffentlicht",
525
534
  "record.metadata.resourceUpdated": "Zuletzt aktualisiert",