geonetwork-ui 2.10.0-dev.300bb3d2d → 2.10.0-dev.3445256ab

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 (91) hide show
  1. package/fesm2022/geonetwork-ui.mjs +573 -123
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +119 -27
  4. package/index.d.ts.map +1 -1
  5. package/package.json +1 -1
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  21. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
  22. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  23. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
  24. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
  27. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -0
  28. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +54 -32
  29. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +46 -0
  30. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +11 -0
  31. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  32. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  33. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  34. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
  35. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -0
  36. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +10 -1
  37. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  38. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  39. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  40. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  41. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  42. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +38 -0
  43. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +33 -34
  44. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  45. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +37 -0
  52. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +1 -0
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  55. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +59 -3
  56. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  57. package/src/libs/feature/editor/src/lib/services/editor.service.ts +1 -1
  58. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  59. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  60. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  61. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  62. package/src/libs/ui/elements/src/index.ts +2 -0
  63. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +96 -0
  64. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  65. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +37 -0
  66. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  67. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  68. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  69. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +28 -67
  70. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  71. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  72. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  73. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  74. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  75. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +11 -10
  76. package/src/libs/util/app-config/src/lib/app-config.ts +49 -0
  77. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  78. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  79. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  80. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  81. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  82. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  83. package/tailwind.base.css +11 -2
  84. package/translations/de.json +10 -1
  85. package/translations/en.json +10 -1
  86. package/translations/es.json +10 -1
  87. package/translations/fr.json +10 -1
  88. package/translations/it.json +10 -1
  89. package/translations/nl.json +10 -1
  90. package/translations/pt.json +10 -1
  91. package/translations/sk.json +10 -1
@@ -8,7 +8,10 @@ import {
8
8
  } from '@angular/core'
9
9
  import {
10
10
  CatalogRecord,
11
+ Individual,
11
12
  Keyword,
13
+ Role,
14
+ RoleLabels,
12
15
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
16
  import { DateService, getTemporalRangeUnion } from '../../../../../../libs/util/shared/src'
14
17
  import { MarkdownParserComponent } from '../markdown-parser/markdown-parser.component'
@@ -19,17 +22,18 @@ import {
19
22
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
20
23
  import {
21
24
  BadgeComponent,
25
+ ButtonComponent,
22
26
  CopyTextButtonComponent,
23
27
  } from '../../../../../../libs/ui/inputs/src'
24
28
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
25
29
  import { NgIcon, provideIcons } from '@ng-icons/core'
26
30
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
27
31
  import { matMailOutline } from '@ng-icons/material-icons/outline'
28
- import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
29
32
  import { GnUiLinkifyDirective } from './linkify.directive'
30
33
  import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/src'
31
34
 
32
35
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
36
+ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
33
37
 
34
38
  @Component({
35
39
  selector: 'gn-ui-metadata-info',
@@ -42,15 +46,16 @@ import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
42
46
  TranslatePipe,
43
47
  MarkdownParserComponent,
44
48
  ExpandablePanelComponent,
49
+ ButtonComponent,
45
50
  BadgeComponent,
46
51
  ContentGhostComponent,
47
- ThumbnailComponent,
48
52
  MaxLinesComponent,
49
53
  CopyTextButtonComponent,
50
54
  NgIcon,
51
55
  GnUiLinkifyDirective,
52
56
  GnUiHumanizeDateDirective,
53
57
  SpatialExtentComponent,
58
+ ContactPillComponent,
54
59
  ],
55
60
  viewProviders: [
56
61
  provideIcons({
@@ -132,18 +137,33 @@ export class MetadataInfoComponent {
132
137
  return getTemporalRangeUnion(temporalExtents, this.dateService)
133
138
  }
134
139
 
135
- get shownOrganization() {
136
- return this.metadata.ownerOrganization
137
- }
138
-
139
- get resourceContact() {
140
- return this.metadata.contactsForResource?.[0]
141
- }
142
-
143
140
  fieldReady(propName: string) {
144
141
  return !this.incomplete || propName in this.metadata
145
142
  }
146
143
 
144
+ get contactGroups(): {
145
+ role: Role
146
+ roleLabel: string
147
+ contacts: Individual[]
148
+ }[] {
149
+ const groups: { role: Role; roleLabel: string; contacts: Individual[] }[] =
150
+ []
151
+ const indexByRole = new Map<Role, number>()
152
+ for (const contact of this.metadata.contactsForResource ?? []) {
153
+ if (indexByRole.has(contact.role)) {
154
+ groups[indexByRole.get(contact.role)].contacts.push(contact)
155
+ } else {
156
+ indexByRole.set(contact.role, groups.length)
157
+ groups.push({
158
+ role: contact.role,
159
+ roleLabel: RoleLabels.get(contact.role),
160
+ contacts: [contact],
161
+ })
162
+ }
163
+ }
164
+ return groups
165
+ }
166
+
147
167
  onKeywordClick(keyword: Keyword) {
148
168
  this.keyword.emit(keyword)
149
169
  }
@@ -26,6 +26,7 @@ export class ButtonComponent {
26
26
  | 'light'
27
27
  | 'gray'
28
28
  | 'black'
29
+ | 'primary-light'
29
30
  ) {
30
31
  // btn-classes are written in full to be picked up by tailwind
31
32
  switch (value) {
@@ -47,6 +48,9 @@ export class ButtonComponent {
47
48
  case 'black':
48
49
  this.btnClass = 'gn-ui-btn-black'
49
50
  break
51
+ case 'primary-light':
52
+ this.btnClass = 'gn-ui-btn-primary-light'
53
+ break
50
54
  case 'default':
51
55
  default:
52
56
  this.btnClass = 'gn-ui-btn-default'
@@ -6,7 +6,7 @@
6
6
  type="url"
7
7
  [value]="inputValue"
8
8
  (input)="handleInput($event)"
9
- (keydown.enter)="handleUpload(input)"
9
+ (keydown.enter)="handleUpload(input, $event)"
10
10
  [placeholder]="placeholder"
11
11
  [attr.aria-label]="placeholder"
12
12
  [disabled]="disabled"
@@ -25,7 +25,7 @@
25
25
  extraClass="absolute inset-y-[var(--side-padding)] right-[var(--side-padding)]"
26
26
  type="primary"
27
27
  [disabled]="disabled || input.value === '' || !isValidUrl(input.value)"
28
- (buttonClick)="handleUpload(input)"
28
+ (buttonClick)="handleUpload(input, $event)"
29
29
  >
30
30
  <ng-content>
31
31
  <ng-icon name="iconoirArrowUp"></ng-icon>
@@ -81,7 +81,8 @@ export class UrlInputComponent implements OnChanges {
81
81
  this.valueChange.next(value)
82
82
  }
83
83
 
84
- handleUpload(element: HTMLInputElement) {
84
+ handleUpload(element: HTMLInputElement, event: Event) {
85
+ event.stopPropagation()
85
86
  const value = element.value
86
87
  if (!value || !this.isValidUrl(value)) return
87
88
  this.uploadClick.next(value)
@@ -204,11 +204,12 @@ export class MapContainerComponent implements AfterViewInit, OnChanges {
204
204
 
205
205
  async ngOnChanges(changes: SimpleChanges) {
206
206
  if ('context' in changes && !changes['context'].isFirstChange()) {
207
+ const olMap = await this.openlayersMap
207
208
  const diff = computeMapContextDiff(
208
209
  this.processContext(changes['context'].currentValue),
209
210
  this.processContext(changes['context'].previousValue)
210
211
  )
211
- await applyContextDiffToMap(this.olMap, diff)
212
+ await applyContextDiffToMap(olMap, diff)
212
213
 
213
214
  if (this._resolvedExtentChange && diff.viewChanges) {
214
215
  this._resolvedExtentChange.emit(this.calculateCurrentMapExtent())
@@ -1,4 +1,4 @@
1
- import { Component, Input } from '@angular/core'
1
+ import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'
2
2
  import { CommonModule } from '@angular/common'
3
3
  import { Geometry } from 'geojson'
4
4
  import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON.js'
@@ -10,8 +10,8 @@ import {
10
10
  MapContextLayer,
11
11
  } from '@geospatial-sdk/core'
12
12
  import { MapContainerComponent } from '../map-container/map-container.component'
13
- import { BehaviorSubject, Observable } from 'rxjs'
14
- import { switchMap } from 'rxjs/operators'
13
+ import { BehaviorSubject, from, Observable, of } from 'rxjs'
14
+ import { map, switchMap, tap } from 'rxjs/operators'
15
15
  import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/src/lib/model/record'
16
16
 
17
17
  @Component({
@@ -22,14 +22,16 @@ import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/sr
22
22
  styleUrl: './spatial-extent.component.css',
23
23
  })
24
24
  export class SpatialExtentComponent {
25
+ private _cdr = inject(ChangeDetectorRef)
26
+
25
27
  @Input() set spatialExtents(value: DatasetSpatialExtent[]) {
26
28
  this.spatialExtents$.next(value)
27
29
  }
28
30
  spatialExtents$ = new BehaviorSubject<DatasetSpatialExtent[]>([])
29
31
  mapContext$: Observable<MapContext> = this.spatialExtents$.pipe(
30
- switchMap(async (extents) => {
32
+ switchMap((extents) => {
31
33
  if (extents.length === 0) {
32
- return null // null extent means default view
34
+ return of(null)
33
35
  }
34
36
  const featureCollection: GeoJSONFeatureCollection = {
35
37
  type: 'FeatureCollection',
@@ -61,11 +63,10 @@ export class SpatialExtentComponent {
61
63
  'fill-color': 'rgba(153, 153, 153, 0.3)',
62
64
  },
63
65
  }
64
- const view = await createViewFromLayer(layer)
65
- return {
66
- view,
67
- layers: [layer],
68
- }
66
+ return from(createViewFromLayer(layer)).pipe(
67
+ map((view) => ({ view, layers: [layer] }) as MapContext),
68
+ tap(() => this._cdr.markForCheck())
69
+ )
69
70
  })
70
71
  )
71
72
 
@@ -1,6 +1,8 @@
1
1
  import * as TOML from '@ltd/j-toml'
2
2
  import {
3
3
  checkMetadataLanguage,
4
+ checkNewRecordDefaultLanguage,
5
+ checkNewRecordStandard,
4
6
  parseConfigSection,
5
7
  parseMultiConfigSection,
6
8
  parseTranslationsConfigSection,
@@ -8,6 +10,7 @@ import {
8
10
  import {
9
11
  CustomTranslations,
10
12
  CustomTranslationsAllLanguages,
13
+ EditorConfig,
11
14
  GlobalConfig,
12
15
  LayerConfig,
13
16
  MapConfig,
@@ -51,6 +54,12 @@ export function getOptionalSearchConfig(): SearchConfig | null {
51
54
  return searchConfig
52
55
  }
53
56
 
57
+ let editorConfig: EditorConfig = null
58
+
59
+ export function getOptionalEditorConfig(): EditorConfig | null {
60
+ return editorConfig
61
+ }
62
+
54
63
  let metadataQualityConfig: MetadataQualityConfig = null
55
64
  export function getMetadataQualityConfig(): MetadataQualityConfig {
56
65
  return (
@@ -285,6 +294,45 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
285
294
  SORTABLE: parsedMetadataQualitySection.sortable,
286
295
  } as MetadataQualityConfig)
287
296
 
297
+ let parsedEditingSection = parseConfigSection(
298
+ parsed,
299
+ 'editing',
300
+ [],
301
+ ['new_record_default_language', 'new_record_standard'],
302
+ warnings,
303
+ errors
304
+ )
305
+ if (
306
+ parsedEditingSection !== null &&
307
+ parsedEditingSection.new_record_default_language !== undefined
308
+ ) {
309
+ parsedEditingSection = checkNewRecordDefaultLanguage(
310
+ parsedEditingSection,
311
+ warnings
312
+ )
313
+ }
314
+ if (
315
+ parsedEditingSection !== null &&
316
+ parsedEditingSection.new_record_standard !== undefined
317
+ ) {
318
+ parsedEditingSection = checkNewRecordStandard(
319
+ parsedEditingSection,
320
+ warnings
321
+ )
322
+ }
323
+ editorConfig =
324
+ parsedEditingSection === null
325
+ ? null
326
+ : ({
327
+ NEW_RECORD_DEFAULT_LANGUAGE:
328
+ parsedEditingSection.new_record_default_language as
329
+ | string
330
+ | undefined,
331
+ NEW_RECORD_STANDARD: parsedEditingSection.new_record_standard as
332
+ | EditorConfig['NEW_RECORD_STANDARD']
333
+ | undefined,
334
+ } as EditorConfig)
335
+
288
336
  customTranslations = parseTranslationsConfigSection(
289
337
  parsed,
290
338
  'translations'
@@ -309,6 +357,7 @@ export function isConfigLoaded() {
309
357
  export function _reset() {
310
358
  globalConfig = null
311
359
  themeConfig = null
360
+ editorConfig = null
312
361
  customTranslations = null
313
362
  }
314
363
 
@@ -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
+ }
@@ -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
  }
@@ -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/tailwind.base.css CHANGED
@@ -74,7 +74,10 @@
74
74
  --bg-hover: var(--gn-ui-button-bg-hover, var(--color-background));
75
75
  --font-size: var(--gn-ui-button-font-size, 1em);
76
76
  --color: var(--gn-ui-button-color, var(--color-main));
77
- @apply flex flex-row items-center justify-center
77
+ --disabled-opacity: var(--gn-ui-button-disabled-opacity, 0.5);
78
+ --justify: var(--gn-ui-button-justify, center);
79
+ justify-content: var(--justify);
80
+ @apply flex flex-row items-center
78
81
  text-[length:--font-size] leading-none
79
82
  text-[color:--color]
80
83
  p-[--padding] rounded-[--rounded] w-[--width] h-[--height] overflow-hidden
@@ -82,7 +85,7 @@
82
85
  hover:bg-[--bg-hover] focus:bg-[--bg-hover] active:bg-[--background]
83
86
  outline-none
84
87
  relative
85
- disabled:opacity-50 disabled:pointer-events-none
88
+ disabled:opacity-[--disabled-opacity] disabled:pointer-events-none
86
89
  focus:z-10 active:z-10 hover:z-10;
87
90
  }
88
91
  /* makes sure icons will not make the buttons grow vertically */
@@ -134,6 +137,12 @@
134
137
  bg-white border-white focus:ring-4 focus:ring-gray-300;
135
138
  }
136
139
 
140
+ .gn-ui-btn-primary-light {
141
+ @apply gn-ui-btn text-primary-darkest
142
+ bg-primary-white hover:bg-primary focus:bg-primary hover:text-white focus:text-white active:bg-primary-darker
143
+ border-transparent;
144
+ }
145
+
137
146
  .gn-ui-card-s {
138
147
  @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
148
  }
@@ -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",
@@ -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",
@@ -150,6 +150,14 @@
150
150
  "editor.record.form.draft.updateAlert": "Since you created this draft, the dataset has been updated on { date } by { user }. Publishing your draft might erase their edits. To avoid this, you need to either cancel your changes or knowingly publish your own version.",
151
151
  "editor.record.form.field.abstract": "Abstract",
152
152
  "editor.record.form.field.constraintsShortcuts": "",
153
+ "editor.record.form.field.contactDetails.email": "Contact email",
154
+ "editor.record.form.field.contactDetails.email.placeholder": "example@domainname.com",
155
+ "editor.record.form.field.contactDetails.firstName": "Contact first name",
156
+ "editor.record.form.field.contactDetails.firstName.placeholder": "First name",
157
+ "editor.record.form.field.contactDetails.lastName": "Contact last name",
158
+ "editor.record.form.field.contactDetails.lastName.placeholder": "Last name",
159
+ "editor.record.form.field.contactDetails.organization": "Contact organization",
160
+ "editor.record.form.field.contactDetails.organization.placeholder": "Company",
153
161
  "editor.record.form.field.contacts": "Point of contact - Email",
154
162
  "editor.record.form.field.contacts.noContact": "Please provide at least one point of contact.",
155
163
  "editor.record.form.field.contacts.placeholder": "Choose a contact",
@@ -159,6 +167,7 @@
159
167
  "editor.record.form.field.legalConstraints": "Legal constraint",
160
168
  "editor.record.form.field.license": "License",
161
169
  "editor.record.form.field.onlineLinkResources": "Attached resources",
170
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Link",
162
171
  "editor.record.form.field.onlineResource.cancel": "Cancel",
163
172
  "editor.record.form.field.onlineResource.confirm": "Confirm",
164
173
  "editor.record.form.field.onlineResource.dialogTitle": "Modify the dataset preview",
@@ -494,7 +503,6 @@
494
503
  "record.metadata.preview.config.idle": "Set as default preview",
495
504
  "record.metadata.preview.config.saved": "Saved !",
496
505
  "record.metadata.preview.config.saving": "Saving...",
497
- "record.metadata.producer": "Data producer",
498
506
  "record.metadata.publication": "Date of publication",
499
507
  "record.metadata.publications": "{count, plural, =0{publication} one{publication} other{publications}}",
500
508
  "record.metadata.quality": "Metadata Quality",
@@ -520,6 +528,7 @@
520
528
  "record.metadata.quality.updateFrequency.failed": "Update frequency is not specified",
521
529
  "record.metadata.quality.updateFrequency.success": "Update frequency is specified",
522
530
  "record.metadata.related": "Explore the catalog",
531
+ "record.metadata.resource.contacts": "Contacts for the resource",
523
532
  "record.metadata.resourceCreated": "Created",
524
533
  "record.metadata.resourcePublished": "Published",
525
534
  "record.metadata.resourceUpdated": "Last updated",
@@ -150,6 +150,14 @@
150
150
  "editor.record.form.draft.updateAlert": "",
151
151
  "editor.record.form.field.abstract": "",
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": "Punto de contacto - Correo ",
154
162
  "editor.record.form.field.contacts.noContact": "",
155
163
  "editor.record.form.field.contacts.placeholder": "",
@@ -159,6 +167,7 @@
159
167
  "editor.record.form.field.legalConstraints": "",
160
168
  "editor.record.form.field.license": "",
161
169
  "editor.record.form.field.onlineLinkResources": "",
170
+ "editor.record.form.field.onlineLinkageResource.defaultName": "",
162
171
  "editor.record.form.field.onlineResource.cancel": "",
163
172
  "editor.record.form.field.onlineResource.confirm": "",
164
173
  "editor.record.form.field.onlineResource.dialogTitle": "",
@@ -494,7 +503,6 @@
494
503
  "record.metadata.preview.config.idle": "",
495
504
  "record.metadata.preview.config.saved": "",
496
505
  "record.metadata.preview.config.saving": "",
497
- "record.metadata.producer": "",
498
506
  "record.metadata.publication": "",
499
507
  "record.metadata.publications": "{count, plural, =0{} one{} other{}}",
500
508
  "record.metadata.quality": "",
@@ -520,6 +528,7 @@
520
528
  "record.metadata.quality.updateFrequency.failed": "",
521
529
  "record.metadata.quality.updateFrequency.success": "",
522
530
  "record.metadata.related": "",
531
+ "record.metadata.resource.contacts": "",
523
532
  "record.metadata.resourceCreated": "",
524
533
  "record.metadata.resourcePublished": "",
525
534
  "record.metadata.resourceUpdated": "",