geonetwork-ui 2.10.0-dev.f143bfdfa → 2.10.0-dev.f1c00d5fe

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 (142) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1331 -6228
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +186 -170
  4. package/index.d.ts.map +1 -1
  5. package/package.json +23 -33
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +11 -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 +22 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +1 -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 +22 -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 +1 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +6 -5
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +4 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +13 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +45 -1
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -1
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +40 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +47 -1
  31. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -4
  32. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  33. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +12 -8
  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 +11 -22
  37. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -5
  38. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  39. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  40. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +57 -0
  41. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  42. package/src/libs/feature/editor/src/index.ts +1 -0
  43. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  44. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  45. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -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.html +7 -6
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +10 -8
  49. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  50. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  51. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  52. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  55. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  56. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +27 -16
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  67. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  68. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  69. package/src/libs/feature/editor/src/lib/fields.config.ts +354 -337
  70. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  71. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  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/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  75. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  76. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  77. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  78. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +221 -8
  79. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  80. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  81. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  82. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  83. package/src/libs/ui/dataviz/src/index.ts +0 -1
  84. package/src/libs/ui/elements/src/index.ts +1 -0
  85. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  86. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  87. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  88. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  89. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  90. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  91. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  92. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  93. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  94. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  95. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +4 -7
  96. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  97. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  98. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  99. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  100. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  101. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  102. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  103. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  104. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  105. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  106. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  107. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  108. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  109. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  110. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  111. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  112. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  113. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  114. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  115. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  116. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  117. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  118. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  119. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  120. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  121. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  122. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  123. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  124. package/src/libs/util/i18n/src/index.ts +0 -1
  125. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  126. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  127. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  128. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  129. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  130. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  131. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  132. package/style.css +0 -1
  133. package/tailwind.base.css +9 -0
  134. package/translations/de.json +102 -49
  135. package/translations/en.json +112 -59
  136. package/translations/es.json +76 -23
  137. package/translations/fr.json +109 -56
  138. package/translations/it.json +103 -50
  139. package/translations/nl.json +75 -22
  140. package/translations/pt.json +76 -23
  141. package/translations/sk.json +76 -23
  142. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -3,7 +3,7 @@ import {
3
3
  HttpErrorResponse,
4
4
  HttpHeaders,
5
5
  } from '@angular/common/http'
6
- import { Injectable, InjectionToken, inject } from '@angular/core'
6
+ import { inject, Injectable, InjectionToken } from '@angular/core'
7
7
  import {
8
8
  assertValidXml,
9
9
  BaseConverter,
@@ -327,8 +327,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
327
327
  return this.settingsService.allowEditHarvested$.pipe(
328
328
  map((allowEditHarvested) => {
329
329
  return (
330
- record.extras['edit'] &&
331
- (!record.extras['isHarvested'] || allowEditHarvested)
330
+ (this.platformService.supportsAuthentication() &&
331
+ record.extras?.['edit'] &&
332
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
333
+ false
332
334
  )
333
335
  })
334
336
  )
@@ -336,10 +338,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
336
338
 
337
339
  private canEdit(record: CatalogRecord, allowEditHarvested: boolean): boolean {
338
340
  return (
339
- this.platformService.supportsAuthentication() &&
340
- record.kind === 'dataset' &&
341
- record.extras['edit'] &&
342
- (!record.extras['isHarvested'] || allowEditHarvested)
341
+ (this.platformService.supportsAuthentication() &&
342
+ record.extras?.['edit'] &&
343
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
344
+ false
343
345
  )
344
346
  }
345
347
 
@@ -361,8 +363,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
361
363
  }
362
364
 
363
365
  openRecordForEdition(
364
- uniqueIdentifier: string
366
+ uniqueIdentifier: string,
367
+ disableDraft?: boolean
365
368
  ): Observable<[CatalogRecord, string, boolean] | null> {
369
+ this.disableDraft = disableDraft ?? this.disableDraft
366
370
  const draft$ = this.disableDraft
367
371
  ? of(null)
368
372
  : of(this.getRecordFromLocalStorage(uniqueIdentifier))
@@ -52,7 +52,7 @@ export class OrganizationsFromGroupsService
52
52
  organisationsCount$ = this.organisations$.pipe(map((orgs) => orgs.length))
53
53
 
54
54
  private get lang3() {
55
- return toLang3(this.translateService.currentLang)
55
+ return toLang3(this.translateService.getCurrentLang())
56
56
  }
57
57
 
58
58
  constructor(
@@ -128,7 +128,7 @@ export class OrganizationsFromMetadataService
128
128
  ) {}
129
129
 
130
130
  private get langIndex() {
131
- return `lang${toLang3(this.translateService.currentLang)}`
131
+ return `lang${toLang3(this.translateService.getCurrentLang())}`
132
132
  }
133
133
 
134
134
  equalsNormalizedStrings(
@@ -1,5 +1,5 @@
1
1
  import { HttpClient, HttpEventType } from '@angular/common/http'
2
- import { Injectable, InjectionToken, inject } from '@angular/core'
2
+ import { inject, Injectable, InjectionToken } from '@angular/core'
3
3
  import {
4
4
  KeywordApiResponse,
5
5
  ThesaurusApiResponse,
@@ -100,7 +100,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
100
100
  private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
101
101
 
102
102
  private get lang3() {
103
- return toLang3(this.translateService.currentLang)
103
+ return toLang3(this.translateService.getCurrentLang())
104
104
  }
105
105
 
106
106
  constructor() {
@@ -177,26 +177,15 @@ export class Gn4PlatformService implements PlatformServiceInterface {
177
177
  const editorIds = meResponse.groupsWithEditor ?? []
178
178
  const memberIds = meResponse.groupsWithRegisteredUser ?? []
179
179
  const adminIds = meResponse.groupsWithUserAdmin ?? []
180
- const groupsById = new Map(groups.map((g) => [g.id, g]))
181
- return [
182
- ...new Set([
183
- ...reviewerIds,
184
- ...editorIds,
185
- ...groups.map((g) => g.id),
186
- ]),
187
- ]
188
- .filter((id) => groupsById.has(id))
189
- .map((id) => {
190
- const group = groupsById.get(id)
191
- return {
192
- groupId: group.id,
193
- groupName: group.name,
194
- isMember: memberIds.includes(id),
195
- canEdit: editorIds.includes(id),
196
- canApprove: reviewerIds.includes(id),
197
- canAdministrate: adminIds.includes(id),
198
- }
199
- })
180
+
181
+ return groups.map((group) => ({
182
+ groupId: group.id,
183
+ groupName: group.name,
184
+ isMember: memberIds.includes(group.id),
185
+ canEdit: editorIds.includes(group.id),
186
+ canApprove: reviewerIds.includes(group.id),
187
+ canAdministrate: adminIds.includes(group.id),
188
+ }))
200
189
  })
201
190
  )
202
191
  }
@@ -104,15 +104,12 @@ export interface Keyword {
104
104
  type: KeywordType
105
105
  thesaurus?: ThesaurusModel
106
106
  bbox?: [number, number, number, number]
107
+ // thesaurus hierarchy path incl. the keyword as last element; unset for root-level keywords
108
+ hierarchyPath?: string[]
107
109
 
108
110
  translations?: KeywordTranslations
109
111
  }
110
112
 
111
- export interface INSPIRE_topic {
112
- value: string
113
- label: string
114
- }
115
-
116
113
  export interface ResourceIdentifier {
117
114
  code: string
118
115
  codeSpace?: string
@@ -263,11 +260,20 @@ export interface SourceRecord {
263
260
  href?: string
264
261
  }
265
262
 
263
+ /**
264
+ * Represents another record associated with this one (e.g. a parent/sibling dataset).
265
+ */
266
+ export interface AssociatedRecord {
267
+ uuid: string
268
+ associationType: string
269
+ }
270
+
266
271
  export interface DatasetRecord extends BaseRecord {
267
272
  kind: 'dataset'
268
273
  status: RecordStatus
269
274
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
270
275
  sourceRecords: Array<SourceRecord>
276
+ associatedRecords: Array<AssociatedRecord>
271
277
  onlineResources: Array<DatasetOnlineResource>
272
278
  spatialExtents: Array<DatasetSpatialExtent>
273
279
  temporalExtents: Array<DatasetTemporalExtent>
@@ -296,6 +302,7 @@ export interface ReuseRecord extends BaseRecord {
296
302
  kind: 'reuse'
297
303
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
298
304
  sourceRecords: Array<SourceRecord>
305
+ associatedRecords: Array<AssociatedRecord>
299
306
  onlineResources: Array<DatasetOnlineResource>
300
307
  reuseType: ReuseType
301
308
  spatialExtents: Array<DatasetSpatialExtent>
@@ -1,7 +1,8 @@
1
1
  export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
2
2
 
3
3
  export interface ThesaurusModel {
4
- id: string
4
+ // absent when the source provides no identifier for the thesaurus
5
+ id?: string
5
6
  name?: string
6
7
  url?: URL
7
8
  thesaurusKey?: string
@@ -39,7 +39,8 @@ export abstract class RecordsRepositoryInterface {
39
39
  * @returns Observable<[CatalogRecord, string, boolean] | null>
40
40
  */
41
41
  abstract openRecordForEdition(
42
- uniqueIdentifier: string
42
+ uniqueIdentifier: string,
43
+ disableDraft?: boolean
43
44
  ): Observable<[CatalogRecord, string, boolean] | null>
44
45
 
45
46
  /**
@@ -3,6 +3,7 @@ import {
3
3
  DatasetRecord,
4
4
  DatasetSpatialExtent,
5
5
  Keyword,
6
+ ReuseRecord,
6
7
  ServiceRecord,
7
8
  } from '../../../../../libs/common/domain/src/lib/model/record'
8
9
 
@@ -135,6 +136,7 @@ Cette section contient des *caractères internationaux* (ainsi que des "caractè
135
136
 
136
137
  As such, **it is not very interesting at all.**`,
137
138
  sourceRecords: [],
139
+ associatedRecords: [],
138
140
  licenses: [
139
141
  {
140
142
  text: 'Licence ODbL mai 2013 (basée sur ODbL 1.0)',
@@ -312,6 +314,7 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
312
314
  lineage: `Document d’urbanisme numérisé conformément aux prescriptions nationales du CNIG par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.
313
315
  Ce lot de données produit en 2019, a été numérisé à partir du PCI Vecteur de 2019 et contrôlé par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.`,
314
316
  sourceRecords: [],
317
+ associatedRecords: [],
315
318
  legalConstraints: [],
316
319
  securityConstraints: [],
317
320
  otherConstraints: [],
@@ -364,6 +367,7 @@ export const simpleDatasetRecordFixture = (): DatasetRecord => ({
364
367
  spatialExtents: [],
365
368
  temporalExtents: [],
366
369
  sourceRecords: [],
370
+ associatedRecords: [],
367
371
  onlineResources: [
368
372
  {
369
373
  type: 'download',
@@ -417,6 +421,7 @@ export const simpleDatasetRecordWithFcatsFixture = (): DatasetRecord => ({
417
421
  spatialExtents: [],
418
422
  temporalExtents: [],
419
423
  sourceRecords: [],
424
+ associatedRecords: [],
420
425
  onlineResources: [],
421
426
  updateFrequency: { per: 'month', updatedTimes: 3 },
422
427
  translations: {},
@@ -983,6 +988,7 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
983
988
  abstract: 'English Abstract',
984
989
  lineage: 'English Lineage',
985
990
  sourceRecords: [],
991
+ associatedRecords: [],
986
992
  translations: {
987
993
  title: { fr: 'Titre Français', de: 'Titel DE' },
988
994
  abstract: { fr: 'Résumé Français', de: 'Beschreibung DE' },
@@ -1027,3 +1033,54 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
1027
1033
  topics: [],
1028
1034
  recordUpdated: null,
1029
1035
  })
1036
+
1037
+ export const simpleReuseRecordFixture = (): ReuseRecord => ({
1038
+ abstract: `Sample reuse record`,
1039
+ kind: 'reuse',
1040
+ reuseType: 'application',
1041
+ recordUpdated: new Date('2023-03-17T07:38:08.875Z'),
1042
+ recordPublished: null,
1043
+ ownerOrganization: null,
1044
+ licenses: [],
1045
+ legalConstraints: [],
1046
+ securityConstraints: [],
1047
+ otherConstraints: [],
1048
+ contacts: [],
1049
+ contactsForResource: [],
1050
+ keywords: [],
1051
+ topics: [],
1052
+ spatialExtents: [],
1053
+ temporalExtents: [],
1054
+ lineage: 'bla',
1055
+ sourceRecords: [],
1056
+ associatedRecords: [],
1057
+ overviews: [],
1058
+ defaultLanguage: null,
1059
+ otherLanguages: [],
1060
+ title: 'Sites de gestion des déchets miniers - Service de visualisation WMS',
1061
+ onlineResources: [
1062
+ {
1063
+ name: 'Rapport de disponibilité du service WMS',
1064
+ description:
1065
+ 'Ce service de visualisation WMS permet de consulter la série de couches de données "Sites de gestion des déchets miniers - Série".',
1066
+ mimeType: 'text/html',
1067
+ type: 'link',
1068
+ url: new URL(
1069
+ 'https://geoservices.wallonie.be/rapportDisponibilite/wms/sites_de_gestion_des_dechets_miniers_srie.html'
1070
+ ),
1071
+ accessRestricted: false,
1072
+ },
1073
+ ],
1074
+ uniqueIdentifier: '00b22798-ec8e-4500-89e8-90eeeda45919',
1075
+ landingPage: new URL(
1076
+ 'http://localhost:4200/geonetwork/srv/fre/catalog.search#/metadata/00b22798-ec8e-4500-89e8-90eeeda45919'
1077
+ ),
1078
+ extras: {
1079
+ isPublishedToAll: true,
1080
+ id: '723',
1081
+ isHarvested: true,
1082
+ ownerInfo: 'admin|admin|admin|Administrator',
1083
+ edit: true,
1084
+ },
1085
+ recordCreated: new Date('2023-03-17T07:38:08.875Z'),
1086
+ })
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import { SourcesApiService } from '../../../../../../libs/data-access/gn4/src'
3
3
  import { Observable } from 'rxjs'
4
4
  import { filter, map, shareReplay } from 'rxjs/operators'
@@ -21,7 +21,10 @@ export class SourcesService {
21
21
  return this.sources$.pipe(
22
22
  map((sources) => sources.filter((source) => source.uuid === uuid)[0]),
23
23
  filter((source) => !!source),
24
- map((source) => source.label[toLang3(this.translateService.currentLang)])
24
+ map(
25
+ (source) =>
26
+ source.label[toLang3(this.translateService.getCurrentLang())]
27
+ )
25
28
  )
26
29
  }
27
30
  }
@@ -3,6 +3,7 @@ export * from './lib/+state/editor.facade'
3
3
  export * from './lib/+state/editor.models'
4
4
  export * from './lib/+state/editor.reducer'
5
5
  export * from './lib/+state/editor.selectors'
6
+ export * from './lib/components/contact-details/contact-details-form.component'
6
7
  export * from './lib/components/import-record/import-record.component'
7
8
  export * from './lib/components/metadata-quality-panel/metadata-quality-panel.component'
8
9
  export * from './lib/components/multilingual-panel/multilingual-panel.component'
@@ -29,7 +29,10 @@ export const markRecordAsChanged = createAction(
29
29
  '[Editor] Mark record as changed'
30
30
  )
31
31
 
32
- export const saveRecord = createAction('[Editor] Save record')
32
+ export const saveRecord = createAction(
33
+ '[Editor] Save record',
34
+ props<{ publish: boolean }>()
35
+ )
33
36
  export const saveRecordSuccess = createAction('[Editor] Save record success')
34
37
  export const saveRecordFailure = createAction(
35
38
  '[Editor] Save record failure',
@@ -28,30 +28,37 @@ export class EditorEffects {
28
28
  this.store.select(selectRecordSource),
29
29
  this.store.select(selectEditorConfig)
30
30
  ),
31
- switchMap(([, record, recordSource, fieldsConfig]) =>
32
- this.editorService.saveRecord(record, recordSource, fieldsConfig).pipe(
33
- switchMap(([savedRecord, savedRecordSource]) => {
34
- const actions: Action[] = [EditorActions.saveRecordSuccess()]
31
+ switchMap(([{ publish }, record, recordSource, fieldsConfig]) =>
32
+ this.editorService
33
+ .saveRecord(record, recordSource, fieldsConfig, publish)
34
+ .pipe(
35
+ switchMap(([savedRecord, savedRecordSource]) => {
36
+ const actions: Action[] = [EditorActions.saveRecordSuccess()]
37
+
38
+ if (publish) {
39
+ // saving without publishing doesn't unpublish
40
+ actions.push(EditorActions.isPublished({ isPublished: true }))
41
+ }
35
42
 
36
- if (!record?.uniqueIdentifier) {
37
- actions.push(
38
- EditorActions.openRecord({
39
- record: savedRecord,
40
- recordSource: savedRecordSource,
43
+ if (!record?.uniqueIdentifier) {
44
+ actions.push(
45
+ EditorActions.openRecord({
46
+ record: savedRecord,
47
+ recordSource: savedRecordSource,
48
+ })
49
+ )
50
+ }
51
+
52
+ return of(...actions)
53
+ }),
54
+ catchError((error) =>
55
+ of(
56
+ EditorActions.saveRecordFailure({
57
+ error,
41
58
  })
42
59
  )
43
- }
44
-
45
- return of(...actions)
46
- }),
47
- catchError((error) =>
48
- of(
49
- EditorActions.saveRecordFailure({
50
- error,
51
- })
52
60
  )
53
61
  )
54
- )
55
62
  )
56
63
  )
57
64
  )
@@ -54,7 +54,11 @@ export class EditorFacade {
54
54
  }
55
55
 
56
56
  saveRecord() {
57
- this.store.dispatch(EditorActions.saveRecord())
57
+ this.store.dispatch(EditorActions.saveRecord({ publish: false }))
58
+ }
59
+
60
+ saveAndPublishRecord() {
61
+ this.store.dispatch(EditorActions.saveRecord({ publish: true }))
58
62
  }
59
63
 
60
64
  undoRecordDraft() {
@@ -8,7 +8,6 @@ import {
8
8
 
9
9
  import { FormFieldRichComponent } from '../record-form/form-field/form-field-rich/form-field-rich.component'
10
10
  import { ButtonComponent, UrlInputComponent } from '../../../../../../../libs/ui/inputs/src'
11
- import { MatIconModule } from '@angular/material/icon'
12
11
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
13
12
  import { Constraint } from '../../../../../../../libs/common/domain/src/lib/model/record'
14
13
  import {
@@ -26,7 +25,6 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
26
25
  FormFieldRichComponent,
27
26
  UrlInputComponent,
28
27
  ButtonComponent,
29
- MatIconModule,
30
28
  TranslateDirective,
31
29
  TranslatePipe,
32
30
  NgIconComponent,
@@ -7,8 +7,8 @@
7
7
  <gn-ui-text-input
8
8
  class="block w-full"
9
9
  extraClass="w-full"
10
- [(value)]="contact.lastName"
11
- (valueChange)="emitContactChange()"
10
+ [value]="contact.lastName ?? ''"
11
+ (valueChange)="handleChange({ lastName: $event })"
12
12
  [placeholder]="
13
13
  'editor.record.form.field.contactDetails.lastName.placeholder'
14
14
  | translate
@@ -23,8 +23,8 @@
23
23
  <gn-ui-text-input
24
24
  class="block w-full"
25
25
  extraClass="w-full"
26
- [(value)]="contact.firstName"
27
- (valueChange)="emitContactChange()"
26
+ [value]="contact.firstName ?? ''"
27
+ (valueChange)="handleChange({ firstName: $event })"
28
28
  [placeholder]="
29
29
  'editor.record.form.field.contactDetails.firstName.placeholder'
30
30
  | translate
@@ -40,8 +40,9 @@
40
40
  <gn-ui-text-input
41
41
  class="block w-full"
42
42
  extraClass="w-full"
43
- [value]="contact.organization?.email ?? ''"
44
- (valueChange)="handleOrganizationChange({ email: $event })"
43
+ [type]="'email'"
44
+ [value]="contact.email ?? ''"
45
+ (valueChange)="handleChange({ email: $event })"
45
46
  [placeholder]="
46
47
  'editor.record.form.field.contactDetails.email.placeholder' | translate
47
48
  "
@@ -24,16 +24,18 @@ export class ContactDetailsFormComponent {
24
24
  @Input() contact: Individual
25
25
  @Output() contactChange = new EventEmitter<Individual>()
26
26
 
27
- emitContactChange() {
28
- this.contactChange.emit(this.contact)
27
+ // emit a fresh contact on each change; never mutate the @Input
28
+ handleChange(change: Partial<Individual>) {
29
+ this.contactChange.emit({ ...this.contact, ...change })
29
30
  }
30
31
 
31
32
  handleOrganizationChange(change: Partial<Organization>) {
32
- this.contact.organization = {
33
- ...(this.contact.organization ?? ({} as Organization)),
34
- ...change,
35
- } as Organization
36
-
37
- this.emitContactChange()
33
+ this.contactChange.emit({
34
+ ...this.contact,
35
+ organization: {
36
+ ...(this.contact.organization ?? ({} as Organization)),
37
+ ...change,
38
+ } as Organization,
39
+ })
38
40
  }
39
41
  }
@@ -11,20 +11,11 @@
11
11
  ></gn-ui-autocomplete>
12
12
  <div class="flex gap-2 flex-wrap">
13
13
  @for (keyword of keywords; track keyword) {
14
- <gn-ui-badge
15
- [removable]="true"
16
- (badgeRemoveClicked)="removeKeyword(keyword)"
17
- >{{ keyword.label }}
18
- @if (isPlaceWithoutExtent(keyword)) {
19
- <ng-icon
20
- [title]="
21
- 'editor.record.form.keywords.place.withoutExtent' | translate
22
- "
23
- class="badge-warning"
24
- name="matWarningAmberOutline"
25
- ></ng-icon>
26
- }
27
- </gn-ui-badge>
14
+ <gn-ui-keyword-badge
15
+ [keyword]="keyword"
16
+ [editable]="true"
17
+ (keywordRemove)="removeKeyword($event)"
18
+ ></gn-ui-keyword-badge>
28
19
  }
29
20
  </div>
30
21
  </div>
@@ -7,40 +7,20 @@ import {
7
7
  inject,
8
8
  } from '@angular/core'
9
9
  import { PlatformServiceInterface } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
10
- import { AutocompleteComponent, BadgeComponent } from '../../../../../../../libs/ui/inputs/src'
10
+ import { AutocompleteComponent } from '../../../../../../../libs/ui/inputs/src'
11
+ import { KeywordBadgeComponent } from '../../../../../../../libs/ui/elements/src'
11
12
  import { map } from 'rxjs'
12
13
  import { Keyword } from '../../../../../../../libs/common/domain/src/lib/model/record'
13
14
  import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
14
- import { TranslatePipe } from '@ngx-translate/core'
15
- import {
16
- NgIconComponent,
17
- provideIcons,
18
- provideNgIconsConfig,
19
- } from '@ng-icons/core'
20
- import { matWarningAmberOutline } from '@ng-icons/material-icons/outline'
21
15
 
22
16
  type AutocompleteItem = { title: string; value: Keyword }
23
17
 
24
18
  @Component({
25
19
  selector: 'gn-ui-generic-keywords',
26
20
  templateUrl: './generic-keywords.component.html',
27
- styleUrls: ['./generic-keywords.component.css'],
28
21
  changeDetection: ChangeDetectionStrategy.OnPush,
29
22
  standalone: true,
30
- imports: [
31
- AutocompleteComponent,
32
- NgIconComponent,
33
- BadgeComponent,
34
- TranslatePipe,
35
- ],
36
- providers: [
37
- provideIcons({
38
- matWarningAmberOutline,
39
- }),
40
- provideNgIconsConfig({
41
- size: '1.5em',
42
- }),
43
- ],
23
+ imports: [AutocompleteComponent, KeywordBadgeComponent],
44
24
  })
45
25
  export class GenericKeywordsComponent {
46
26
  private platformService = inject(PlatformServiceInterface)
@@ -87,9 +67,4 @@ export class GenericKeywordsComponent {
87
67
  this.changedKeywords.emit(this.keywords)
88
68
  this.deletedKeyword.emit(keyword)
89
69
  }
90
-
91
- isPlaceWithoutExtent(keyword: Keyword): boolean {
92
- if (keyword.type !== 'place') return false
93
- return !keyword.bbox
94
- }
95
70
  }
@@ -3,13 +3,13 @@
3
3
  editor.record.form.field.onlineResource.edit.protocol
4
4
  </h3>
5
5
  @if (protocolHint) {
6
- <span
7
- class="material-symbols-outlined m-2 gn-ui-icon-small"
6
+ <ng-icon
7
+ name="matHelpOutline"
8
+ size="16"
9
+ class="m-2"
8
10
  [matTooltip]="protocolHint"
9
11
  matTooltipPosition="above"
10
- >
11
- help
12
- </span>
12
+ ></ng-icon>
13
13
  }
14
14
  </div>
15
15
  <div class="flex flex-row items-center gap-4 h-[48px]">
@@ -30,6 +30,7 @@ import {
30
30
  provideNgIconsConfig,
31
31
  } from '@ng-icons/core'
32
32
  import { iconoirRefresh } from '@ng-icons/iconoir'
33
+ import { matHelpOutline } from '@ng-icons/material-icons/outline'
33
34
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
34
35
  import {
35
36
  BehaviorSubject,
@@ -65,7 +66,7 @@ marker(
65
66
  UrlInputComponent,
66
67
  ],
67
68
  providers: [
68
- provideIcons({ iconoirRefresh }),
69
+ provideIcons({ iconoirRefresh, matHelpOutline }),
69
70
  provideNgIconsConfig({
70
71
  size: '1.5em',
71
72
  }),
@@ -10,7 +10,7 @@ export class FieldFocusDirective {
10
10
 
11
11
  private el = inject(ElementRef)
12
12
 
13
- public focusField() {
13
+ public focusField(focusInnerTarget = true) {
14
14
  setTimeout(() => {
15
15
  const host = this.el.nativeElement as HTMLElement
16
16
  const glowClass = this.gnUiFieldFocusGlowClass
@@ -25,6 +25,9 @@ export class FieldFocusDirective {
25
25
  )
26
26
 
27
27
  host.scrollIntoView({ behavior: 'smooth', block: 'start' })
28
+ if (!focusInnerTarget) {
29
+ return
30
+ }
28
31
  const target =
29
32
  host.querySelector<HTMLElement>(
30
33
  'input, textarea, select, [contenteditable="true"]'
@@ -131,14 +131,14 @@ export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
131
131
  map((c) => c.length > 0),
132
132
  distinctUntilChanged()
133
133
  )
134
- combineLatest([
135
- isConstraintNotEmpty$,
136
- this.anyToggleActivated$,
137
- ]).subscribe(([isNotEmpty, anyToggleActivated]) => {
138
- const visible = isNotEmpty && !anyToggleActivated
139
- this.editorFacade.setFieldVisibility({ model }, visible)
140
- })
134
+ combineLatest([isConstraintNotEmpty$, this.anyToggleActivated$])
135
+ .pipe(takeUntil(this.onDestroy$))
136
+ .subscribe(([isNotEmpty, anyToggleActivated]) => {
137
+ const visible = isNotEmpty && !anyToggleActivated
138
+ this.editorFacade.setFieldVisibility({ model }, visible)
139
+ })
141
140
  }
141
+ hideEmptyConstraints(this.legalConstraints$, 'legalConstraints')
142
142
  hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
143
143
  hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
144
144
  })
@@ -3,11 +3,11 @@ import {
3
3
  ChangeDetectorRef,
4
4
  Component,
5
5
  EventEmitter,
6
+ inject,
6
7
  Input,
7
8
  Output,
8
9
  TemplateRef,
9
10
  ViewChild,
10
- inject,
11
11
  } from '@angular/core'
12
12
  import { MatDialog } from '@angular/material/dialog'
13
13
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'