geonetwork-ui 2.10.0-dev.b5836aad7 → 2.10.0-dev.cbf02ead8

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 (54) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1069 -911
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +67 -27
  4. package/index.d.ts.map +1 -1
  5. package/package.json +2 -2
  6. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +14 -2
  7. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  8. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -0
  9. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +0 -1
  10. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +10 -1
  11. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  12. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  13. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +34 -34
  14. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +8 -15
  15. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +6 -4
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +3 -0
  22. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -0
  23. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +57 -3
  24. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  25. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  26. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  27. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  28. package/src/libs/ui/elements/src/index.ts +1 -0
  29. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +16 -0
  30. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +26 -0
  31. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  32. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  33. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  34. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +27 -66
  35. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  36. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  37. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  38. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +11 -10
  39. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  40. package/src/libs/util/app-config/src/lib/app-config.ts +36 -0
  41. package/src/libs/util/app-config/src/lib/model.ts +4 -0
  42. package/src/libs/util/app-config/src/lib/parse-utils.ts +23 -1
  43. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  44. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  45. package/src/libs/util/shared/src/lib/utils/user-display.ts +23 -0
  46. package/tailwind.base.css +11 -2
  47. package/translations/de.json +2 -0
  48. package/translations/en.json +2 -0
  49. package/translations/es.json +2 -0
  50. package/translations/fr.json +2 -0
  51. package/translations/it.json +2 -0
  52. package/translations/nl.json +2 -0
  53. package/translations/pt.json +2 -0
  54. package/translations/sk.json +2 -0
@@ -28,11 +28,12 @@
28
28
  <gn-ui-max-lines [maxLines]="7">
29
29
  <div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
30
30
  @for (keyword of metadata.keywords; track keyword) {
31
- <gn-ui-badge
32
- class="inline-block lowercase"
33
- (click)="onKeywordClick(keyword)"
34
- [clickable]="true"
35
- >{{ keyword.label }}</gn-ui-badge
31
+ <gn-ui-button
32
+ type="primary-light"
33
+ class="inline-block opacity-70"
34
+ extraClass="lowercase text-sm py-1 px-2"
35
+ (buttonClick)="onKeywordClick(keyword)"
36
+ >{{ keyword.label }}</gn-ui-button
36
37
  >
37
38
  }
38
39
  </div>
@@ -105,9 +106,29 @@
105
106
  </div>
106
107
  </gn-ui-expandable-panel>
107
108
  }
109
+ @if (metadata.contactsForResource?.length) {
110
+ <gn-ui-expandable-panel
111
+ [title]="'record.metadata.resource.contacts' | translate"
112
+ data-test="contacts-panel"
113
+ >
114
+ <div class="flex flex-col gap-1 pt-3 pb-4">
115
+ @for (group of contactGroups; track group.role) {
116
+ <div class="flex flex-col gap-1 rounded bg-gray-50 py-4 px-2">
117
+ <p class="text-xs font-normal text-black">
118
+ {{ group.roleLabel | translate }}
119
+ </p>
120
+ <div class="grid gap-0.5 grid-cols-1 md:grid-cols-2">
121
+ @for (contact of group.contacts; track contact.email) {
122
+ <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
123
+ }
124
+ </div>
125
+ </div>
126
+ }
127
+ </div>
128
+ </gn-ui-expandable-panel>
129
+ }
108
130
  @if (
109
131
  (metadata.kind === 'dataset' && metadata.lineage) ||
110
- resourceContact ||
111
132
  metadata.resourceCreated ||
112
133
  metadata.resourcePublished ||
113
134
  metadata.resourceUpdated ||
@@ -129,66 +150,6 @@
129
150
  </p>
130
151
  </div>
131
152
  }
132
- @if (resourceContact) {
133
- <div
134
- class="flex flex-row gap-6 mt-5 mb-8 resource-contact"
135
- data-test="details-panel-resource-contact"
136
- >
137
- @if (resourceContact.organization?.logoUrl?.href) {
138
- <div
139
- class="flex items-center justify-center border-solid border border-gray-300 rounded-md bg-white h-32 overflow-hidden"
140
- >
141
- <gn-ui-thumbnail
142
- class="relative h-full w-full"
143
- [thumbnailUrl]="resourceContact.organization.logoUrl.href"
144
- fit="contain"
145
- ></gn-ui-thumbnail>
146
- </div>
147
- }
148
- <div class="flex flex-col gap-1">
149
- <p class="text-sm font-medium" translate>record.metadata.producer</p>
150
- <div
151
- class="text-primary font-title text-21 mr-2 cursor-pointer hover:underline"
152
- data-cy="organization-name"
153
- >
154
- {{ resourceContact.organization?.name }}
155
- </div>
156
- @if (resourceContact.organization?.website) {
157
- <div>
158
- <a
159
- [href]="resourceContact.organization.website"
160
- target="_blank"
161
- class="contact-website text-primary text-sm cursor-pointer hover:underline transition-all"
162
- >{{ resourceContact.organization.website }}
163
- <ng-icon
164
- class="!w-[12px] !h-[12px] !text-[12px] opacity-75 shrink-0"
165
- name="matOpenInNew"
166
- ></ng-icon>
167
- </a>
168
- </div>
169
- }
170
- @if (resourceContact.email) {
171
- <div class="mt-4">
172
- <div class="flex">
173
- <ng-icon
174
- class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
175
- name="matMailOutline"
176
- ></ng-icon>
177
- @if (resourceContact.email) {
178
- <a
179
- [href]="'mailto:' + resourceContact.email"
180
- class="text-sm hover:underline ml-2"
181
- target="_blank"
182
- data-cy="contact-email"
183
- >{{ resourceContact?.email }}</a
184
- >
185
- }
186
- </div>
187
- </div>
188
- }
189
- </div>
190
- </div>
191
- }
192
153
  <div
193
154
  class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
194
155
  >
@@ -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'
@@ -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,5 @@
1
1
  <gn-ui-internal-link-card
2
2
  [linkHref]="linkHref"
3
- [linkTarget]="linkTarget"
4
3
  [record]="record"
5
4
  [favoriteTemplate]="favoriteTemplate"
6
5
  [metadataQualityDisplay]="metadataQualityDisplay"
@@ -1,6 +1,7 @@
1
1
  import * as TOML from '@ltd/j-toml'
2
2
  import {
3
3
  checkMetadataLanguage,
4
+ checkNewRecordDefaultLanguage,
4
5
  parseConfigSection,
5
6
  parseMultiConfigSection,
6
7
  parseTranslationsConfigSection,
@@ -8,6 +9,7 @@ import {
8
9
  import {
9
10
  CustomTranslations,
10
11
  CustomTranslationsAllLanguages,
12
+ EditorConfig,
11
13
  GlobalConfig,
12
14
  LayerConfig,
13
15
  MapConfig,
@@ -51,6 +53,12 @@ export function getOptionalSearchConfig(): SearchConfig | null {
51
53
  return searchConfig
52
54
  }
53
55
 
56
+ let editorConfig: EditorConfig = null
57
+
58
+ export function getOptionalEditorConfig(): EditorConfig | null {
59
+ return editorConfig
60
+ }
61
+
54
62
  let metadataQualityConfig: MetadataQualityConfig = null
55
63
  export function getMetadataQualityConfig(): MetadataQualityConfig {
56
64
  return (
@@ -285,6 +293,33 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
285
293
  SORTABLE: parsedMetadataQualitySection.sortable,
286
294
  } as MetadataQualityConfig)
287
295
 
296
+ let parsedEditingSection = parseConfigSection(
297
+ parsed,
298
+ 'editing',
299
+ [],
300
+ ['new_record_default_language'],
301
+ warnings,
302
+ errors
303
+ )
304
+ if (
305
+ parsedEditingSection !== null &&
306
+ parsedEditingSection.new_record_default_language !== undefined
307
+ ) {
308
+ parsedEditingSection = checkNewRecordDefaultLanguage(
309
+ parsedEditingSection,
310
+ warnings
311
+ )
312
+ }
313
+ editorConfig =
314
+ parsedEditingSection === null
315
+ ? null
316
+ : ({
317
+ NEW_RECORD_DEFAULT_LANGUAGE:
318
+ parsedEditingSection.new_record_default_language as
319
+ | string
320
+ | undefined,
321
+ } as EditorConfig)
322
+
288
323
  customTranslations = parseTranslationsConfigSection(
289
324
  parsed,
290
325
  'translations'
@@ -309,6 +344,7 @@ export function isConfigLoaded() {
309
344
  export function _reset() {
310
345
  globalConfig = null
311
346
  themeConfig = null
347
+ editorConfig = null
312
348
  customTranslations = null
313
349
  }
314
350
 
@@ -69,6 +69,10 @@ export interface MetadataQualityConfig {
69
69
  ENABLED: boolean
70
70
  }
71
71
 
72
+ export interface EditorConfig {
73
+ NEW_RECORD_DEFAULT_LANGUAGE?: string
74
+ }
75
+
72
76
  export type CustomTranslations = { [translationKey: string]: string }
73
77
  export type CustomTranslationsAllLanguages = {
74
78
  [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,25 @@ 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
+ }
@@ -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,23 @@
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 toIndividual(user: UserModel): Individual {
16
+ return {
17
+ firstName: user.name,
18
+ lastName: user.surname,
19
+ email: user.email,
20
+ role: 'unspecified',
21
+ organization: user.organisation ? { name: user.organisation } : undefined,
22
+ }
23
+ }
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
  }
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "Rechtliche Einschränkung",
160
160
  "editor.record.form.field.license": "Lizenz",
161
161
  "editor.record.form.field.onlineLinkResources": "Beigefügte Ressourcen",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Link",
162
163
  "editor.record.form.field.onlineResource.cancel": "Abbrechen",
163
164
  "editor.record.form.field.onlineResource.confirm": "Bestätigen",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "Vorschau des Datensatzes bearbeiten",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "Aktualisierungsfrequenz nicht angegeben",
521
522
  "record.metadata.quality.updateFrequency.success": "Aktualisierungsfrequenz angegeben",
522
523
  "record.metadata.related": "Entdecken Sie den Katalog",
524
+ "record.metadata.resource.contacts": "Kontakte zur Ressource",
523
525
  "record.metadata.resourceCreated": "Erstellt",
524
526
  "record.metadata.resourcePublished": "Veröffentlicht",
525
527
  "record.metadata.resourceUpdated": "Zuletzt aktualisiert",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "Legal constraint",
160
160
  "editor.record.form.field.license": "License",
161
161
  "editor.record.form.field.onlineLinkResources": "Attached resources",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Link",
162
163
  "editor.record.form.field.onlineResource.cancel": "Cancel",
163
164
  "editor.record.form.field.onlineResource.confirm": "Confirm",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "Modify the dataset preview",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "Update frequency is not specified",
521
522
  "record.metadata.quality.updateFrequency.success": "Update frequency is specified",
522
523
  "record.metadata.related": "Explore the catalog",
524
+ "record.metadata.resource.contacts": "Contacts for the resource",
523
525
  "record.metadata.resourceCreated": "Created",
524
526
  "record.metadata.resourcePublished": "Published",
525
527
  "record.metadata.resourceUpdated": "Last updated",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "",
160
160
  "editor.record.form.field.license": "",
161
161
  "editor.record.form.field.onlineLinkResources": "",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "",
162
163
  "editor.record.form.field.onlineResource.cancel": "",
163
164
  "editor.record.form.field.onlineResource.confirm": "",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "",
521
522
  "record.metadata.quality.updateFrequency.success": "",
522
523
  "record.metadata.related": "",
524
+ "record.metadata.resource.contacts": "",
523
525
  "record.metadata.resourceCreated": "",
524
526
  "record.metadata.resourcePublished": "",
525
527
  "record.metadata.resourceUpdated": "",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "Contrainte légale",
160
160
  "editor.record.form.field.license": "Licence",
161
161
  "editor.record.form.field.onlineLinkResources": "Ressources attachées",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Lien",
162
163
  "editor.record.form.field.onlineResource.cancel": "Annuler",
163
164
  "editor.record.form.field.onlineResource.confirm": "Valider",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "Modifier l'aperçu du jeu de données",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "La fréquence de mise à jour n'est pas renseignée",
521
522
  "record.metadata.quality.updateFrequency.success": "La fréquence de mise à jour est renseignée",
522
523
  "record.metadata.related": "Explorez le catalogue",
524
+ "record.metadata.resource.contacts": "Contacts liés à la donnée",
523
525
  "record.metadata.resourceCreated": "Créé",
524
526
  "record.metadata.resourcePublished": "Publié",
525
527
  "record.metadata.resourceUpdated": "Mis à jour",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "Vincolo legale",
160
160
  "editor.record.form.field.license": "Licenza",
161
161
  "editor.record.form.field.onlineLinkResources": "Risorse allegate",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "Link",
162
163
  "editor.record.form.field.onlineResource.cancel": "Annulla",
163
164
  "editor.record.form.field.onlineResource.confirm": "Convalida",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "Modifica anteprima dataset",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "La frequenza di aggiornamento non è specificata",
521
522
  "record.metadata.quality.updateFrequency.success": "La frequenza di aggiornamento è specificata",
522
523
  "record.metadata.related": "Vedi anche",
524
+ "record.metadata.resource.contacts": "Contatti per la risorsa",
523
525
  "record.metadata.resourceCreated": "Creato",
524
526
  "record.metadata.resourcePublished": "Pubblicato",
525
527
  "record.metadata.resourceUpdated": "Ultimo aggiornamento",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "",
160
160
  "editor.record.form.field.license": "",
161
161
  "editor.record.form.field.onlineLinkResources": "",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "",
162
163
  "editor.record.form.field.onlineResource.cancel": "",
163
164
  "editor.record.form.field.onlineResource.confirm": "",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "",
521
522
  "record.metadata.quality.updateFrequency.success": "",
522
523
  "record.metadata.related": "",
524
+ "record.metadata.resource.contacts": "",
523
525
  "record.metadata.resourceCreated": "",
524
526
  "record.metadata.resourcePublished": "",
525
527
  "record.metadata.resourceUpdated": "",
@@ -159,6 +159,7 @@
159
159
  "editor.record.form.field.legalConstraints": "",
160
160
  "editor.record.form.field.license": "",
161
161
  "editor.record.form.field.onlineLinkResources": "",
162
+ "editor.record.form.field.onlineLinkageResource.defaultName": "",
162
163
  "editor.record.form.field.onlineResource.cancel": "",
163
164
  "editor.record.form.field.onlineResource.confirm": "",
164
165
  "editor.record.form.field.onlineResource.dialogTitle": "",
@@ -520,6 +521,7 @@
520
521
  "record.metadata.quality.updateFrequency.failed": "",
521
522
  "record.metadata.quality.updateFrequency.success": "",
522
523
  "record.metadata.related": "",
524
+ "record.metadata.resource.contacts": "",
523
525
  "record.metadata.resourceCreated": "",
524
526
  "record.metadata.resourcePublished": "",
525
527
  "record.metadata.resourceUpdated": "",