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

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 (158) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2285 -6919
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +248 -173
  4. package/index.d.ts.map +1 -1
  5. package/material-styles.css +483 -0
  6. package/package.json +25 -35
  7. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  20. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  21. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  23. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +5 -4
  24. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
  32. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  33. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  34. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +72 -38
  35. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  40. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  41. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  42. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  43. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  44. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +56 -0
  45. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  46. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  47. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  48. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  49. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  50. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  51. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  52. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  53. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  54. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  55. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  56. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +32 -34
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  61. 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
  62. 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
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  64. 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
  65. 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
  66. 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
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +21 -16
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +14 -0
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  77. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  79. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  80. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  81. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  82. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  83. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  84. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  85. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  86. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  87. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  88. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  89. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  90. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  91. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  92. package/src/libs/ui/dataviz/src/index.ts +0 -1
  93. package/src/libs/ui/elements/src/index.ts +2 -0
  94. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  95. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  96. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  97. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  98. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  99. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  100. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  101. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  102. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  103. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  104. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  105. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  106. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  107. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  108. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +39 -68
  109. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +59 -11
  110. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  111. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  112. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  113. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  114. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  115. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  116. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  117. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  118. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  119. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  120. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  121. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  122. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  123. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  124. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  125. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  126. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  127. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  128. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  129. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  130. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  131. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  132. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  133. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  134. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  135. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  136. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  137. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  138. package/src/libs/util/i18n/src/index.ts +0 -1
  139. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  140. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  141. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  142. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  143. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  144. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  145. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  146. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  147. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  148. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  149. package/style.css +0 -1
  150. package/tailwind.base.css +19 -2
  151. package/translations/de.json +81 -48
  152. package/translations/en.json +86 -53
  153. package/translations/es.json +55 -22
  154. package/translations/fr.json +83 -50
  155. package/translations/it.json +81 -48
  156. package/translations/nl.json +54 -21
  157. package/translations/pt.json +55 -22
  158. package/translations/sk.json +55 -22
@@ -3,9 +3,10 @@ import {
3
3
  HttpErrorResponse,
4
4
  HttpHeaders,
5
5
  } from '@angular/common/http'
6
- import { Injectable, inject } from '@angular/core'
6
+ import { inject, Injectable, InjectionToken } from '@angular/core'
7
7
  import {
8
8
  assertValidXml,
9
+ BaseConverter,
9
10
  findConverterForDocument,
10
11
  Gn4Converter,
11
12
  Gn4SearchResults,
@@ -57,6 +58,16 @@ const TEMPORARY_ID_PREFIX = 'TEMP-ID-'
57
58
 
58
59
  export type RecordAsXml = string
59
60
 
61
+ export const DISABLE_DRAFT = new InjectionToken<boolean>('gnDisableDraft', {
62
+ factory: () => false,
63
+ })
64
+
65
+ export const DEFAULT_RECORD_CONVERTER = new InjectionToken<
66
+ BaseConverter<string>
67
+ >('defaultRecordConverter', {
68
+ factory: () => new Iso19139Converter(),
69
+ })
70
+
60
71
  @Injectable()
61
72
  export class Gn4Repository implements RecordsRepositoryInterface {
62
73
  private httpClient = inject(HttpClient)
@@ -67,6 +78,8 @@ export class Gn4Repository implements RecordsRepositoryInterface {
67
78
  private platformService = inject(PlatformServiceInterface)
68
79
  private gn4LanguagesApi = inject(LanguagesApiService)
69
80
  private settingsService = inject(Gn4SettingsService)
81
+ private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
82
+ private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
70
83
 
71
84
  _draftsChanged = new Subject<void>()
72
85
  draftsChanged$ = this._draftsChanged.asObservable()
@@ -323,10 +336,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
323
336
 
324
337
  private canEdit(record: CatalogRecord, allowEditHarvested: boolean): boolean {
325
338
  return (
326
- this.platformService.supportsAuthentication() &&
327
- record.kind === 'dataset' &&
328
- record.extras['edit'] &&
329
- (!record.extras['isHarvested'] || allowEditHarvested)
339
+ (this.platformService.supportsAuthentication() &&
340
+ record.extras?.['edit'] &&
341
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
342
+ false
330
343
  )
331
344
  }
332
345
 
@@ -350,7 +363,9 @@ export class Gn4Repository implements RecordsRepositoryInterface {
350
363
  openRecordForEdition(
351
364
  uniqueIdentifier: string
352
365
  ): Observable<[CatalogRecord, string, boolean] | null> {
353
- const draft$ = of(this.getRecordFromLocalStorage(uniqueIdentifier))
366
+ const draft$ = this.disableDraft
367
+ ? of(null)
368
+ : of(this.getRecordFromLocalStorage(uniqueIdentifier))
354
369
  const recordAsXml$ = this.getRecordAsXml(uniqueIdentifier)
355
370
 
356
371
  return combineLatest([draft$, recordAsXml$]).pipe(
@@ -370,38 +385,52 @@ export class Gn4Repository implements RecordsRepositoryInterface {
370
385
  openRecordForDuplication(
371
386
  uniqueIdentifier: string
372
387
  ): Observable<[CatalogRecord, string, true] | null> {
373
- return this.gn4RecordsApi
374
- .create(
375
- uniqueIdentifier,
376
- '2',
377
- 'METADATA',
378
- '',
379
- false,
380
- undefined,
381
- true,
382
- false,
383
- undefined,
384
- 'body',
385
- false,
386
- {
387
- httpHeaderAccept: 'application/json',
388
- httpContentTypeSelected: 'application/json;charset=UTF-8',
389
- }
390
- )
391
- .pipe(
392
- switchMap((uniqueIdentifier) => {
393
- return this.getRecordAsXml(uniqueIdentifier)
394
- }),
395
- switchMap((xml) => {
396
- return from(
397
- findConverterForDocument(xml)
398
- .readRecord(xml)
399
- .then((record) => {
400
- return [record, xml, true] as [CatalogRecord, string, true]
401
- })
388
+ return this.platformService.getUserPermissionsByGroup().pipe(
389
+ map((permissions) => {
390
+ const groupId =
391
+ permissions.find((p) => p.canApprove)?.groupId?.toString() ??
392
+ permissions.find((p) => p.canEdit)?.groupId?.toString()
393
+ if (!groupId)
394
+ throw new Error(
395
+ 'Current user has no writable group to duplicate into'
396
+ )
397
+ return groupId
398
+ }),
399
+ switchMap((groupId) =>
400
+ this.gn4RecordsApi
401
+ .create(
402
+ uniqueIdentifier,
403
+ groupId,
404
+ 'METADATA',
405
+ '',
406
+ false,
407
+ undefined,
408
+ true,
409
+ false,
410
+ undefined,
411
+ 'body',
412
+ false,
413
+ {
414
+ httpHeaderAccept: 'application/json',
415
+ httpContentTypeSelected: 'application/json;charset=UTF-8',
416
+ }
417
+ )
418
+ .pipe(
419
+ switchMap((uniqueIdentifier) => {
420
+ return this.getRecordAsXml(uniqueIdentifier)
421
+ }),
422
+ switchMap((xml) => {
423
+ return from(
424
+ findConverterForDocument(xml)
425
+ .readRecord(xml)
426
+ .then((record) => {
427
+ return [record, xml, true] as [CatalogRecord, string, true]
428
+ })
429
+ )
430
+ })
402
431
  )
403
- })
404
432
  )
433
+ )
405
434
  }
406
435
 
407
436
  saveRecord(
@@ -475,6 +504,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
475
504
  record: CatalogRecord,
476
505
  referenceRecordSource?: string
477
506
  ): Observable<string> {
507
+ if (this.disableDraft) return of('')
478
508
  return this.serializeRecordToXml(record, referenceRecordSource).pipe(
479
509
  tap((recordXml) => {
480
510
  this.saveRecordToLocalStorage(recordXml, record.uniqueIdentifier)
@@ -484,16 +514,19 @@ export class Gn4Repository implements RecordsRepositoryInterface {
484
514
  }
485
515
 
486
516
  clearRecordDraft(uniqueIdentifier: string): void {
517
+ if (this.disableDraft) return
487
518
  this.removeRecordFromLocalStorage(uniqueIdentifier)
488
519
  this._draftsChanged.next()
489
520
  }
490
521
 
491
522
  recordHasDraft(uniqueIdentifier: string): boolean {
523
+ if (this.disableDraft) return false
492
524
  return this.getRecordFromLocalStorage(uniqueIdentifier) !== null
493
525
  }
494
526
 
495
527
  // generated by copilot
496
528
  getAllDrafts(): Observable<CatalogRecord[]> {
529
+ if (this.disableDraft) return of([])
497
530
  const items = { ...window.localStorage }
498
531
  const drafts = Object.keys(items)
499
532
  .filter((key) => key.startsWith('geonetwork-ui-draft-'))
@@ -509,6 +542,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
509
542
  }
510
543
 
511
544
  getDraftsCount(): Observable<number> {
545
+ if (this.disableDraft) return of(0)
512
546
  const items = { ...window.localStorage }
513
547
  const draftCount = Object.keys(items)
514
548
  .filter((key) => key.startsWith('geonetwork-ui-draft-'))
@@ -593,10 +627,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
593
627
  record: CatalogRecord,
594
628
  referenceRecordSource?: string
595
629
  ): Observable<string> {
596
- // if there's a reference record, use that standard; otherwise, use iso19139
630
+ // if there's a reference record, use that standard; otherwise, use standard based on configuration or default
597
631
  const converter = referenceRecordSource
598
632
  ? findConverterForDocument(referenceRecordSource)
599
- : new Iso19139Converter()
633
+ : this.defaultConverter
600
634
  return from(converter.writeRecord(record, referenceRecordSource))
601
635
  }
602
636
 
@@ -6,7 +6,7 @@ import {
6
6
  } from './platform/gn4-platform.service'
7
7
  import { Gn4PlatformMapper } from './platform/gn4-platform.mapper'
8
8
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
9
- import { Gn4Repository } from './gn4-repository'
9
+ import { Gn4Repository, DISABLE_DRAFT } from './gn4-repository'
10
10
  import { AvatarServiceInterface, GravatarService } from './auth'
11
11
  import { OrganizationsServiceInterface } from '../../../../../../libs/common/domain/src/lib/organizations.service.interface'
12
12
  import {
@@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'
22
22
 
23
23
  interface Gn4ProvideOptions {
24
24
  disableAuth?: boolean
25
+ disableDraft?: boolean
25
26
  }
26
27
 
27
28
  export function provideGn4(provideOptions?: Gn4ProvideOptions): Provider[] {
@@ -30,6 +31,10 @@ export function provideGn4(provideOptions?: Gn4ProvideOptions): Provider[] {
30
31
  provide: DISABLE_AUTH,
31
32
  useValue: provideOptions?.disableAuth,
32
33
  },
34
+ {
35
+ provide: DISABLE_DRAFT,
36
+ useValue: provideOptions?.disableDraft,
37
+ },
33
38
  {
34
39
  provide: PlatformServiceInterface,
35
40
  useClass: Gn4PlatformService,
@@ -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,
@@ -12,11 +12,13 @@ import {
12
12
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
13
13
  import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
14
14
  import { UserModel } from '../../../../../../../libs/common/domain/src/lib/model/user/user.model'
15
+ import { GroupModel } from '../../../../../../../libs/common/domain/src/lib/model/user/group.model'
15
16
  import {
16
17
  PlatformServiceInterface,
17
18
  UploadEvent,
18
19
  } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
19
20
  import {
21
+ GroupsApiService,
20
22
  MeApiService,
21
23
  RecordsApiService,
22
24
  RegistriesApiService,
@@ -57,6 +59,7 @@ export const DISABLE_AUTH = new InjectionToken<boolean>('gnDisableAuth', {
57
59
  export class Gn4PlatformService implements PlatformServiceInterface {
58
60
  private meApi = inject(MeApiService)
59
61
  private usersApi = inject(UsersApiService)
62
+ private groupsApi = inject(GroupsApiService)
60
63
  private mapper = inject(Gn4PlatformMapper)
61
64
  private toolsApiService = inject(ToolsApiService)
62
65
  private registriesApiService = inject(RegistriesApiService)
@@ -97,7 +100,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
97
100
  private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
98
101
 
99
102
  private get lang3() {
100
- return toLang3(this.translateService.currentLang)
103
+ return toLang3(this.translateService.getCurrentLang())
101
104
  }
102
105
 
103
106
  constructor() {
@@ -155,6 +158,49 @@ export class Gn4PlatformService implements PlatformServiceInterface {
155
158
  return this.users$
156
159
  }
157
160
 
161
+ getUserPermissionsByGroup(): Observable<GroupModel[]> {
162
+ if (this.disableAuth) return of([])
163
+ return combineLatest([this.meApi.getMe(), this.groupsApi.getGroups()]).pipe(
164
+ map(([meResponse, groups]) => {
165
+ if (!meResponse) return []
166
+ if (meResponse.admin) {
167
+ return groups.map((group) => ({
168
+ groupId: group.id,
169
+ groupName: group.name,
170
+ isMember: true,
171
+ canEdit: true,
172
+ canApprove: true,
173
+ canAdministrate: true,
174
+ }))
175
+ }
176
+ const reviewerIds = meResponse.groupsWithReviewer ?? []
177
+ const editorIds = meResponse.groupsWithEditor ?? []
178
+ const memberIds = meResponse.groupsWithRegisteredUser ?? []
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
+ })
200
+ })
201
+ )
202
+ }
203
+
158
204
  translateKey(key: string): Observable<string> {
159
205
  // if the key is a URI, use the registries API to look for the translation
160
206
  if (key.match(/^https?:\/\//)) {
@@ -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
@@ -254,10 +251,20 @@ export interface DatasetTemporalExtent {
254
251
  end?: Date
255
252
  }
256
253
 
254
+ /**
255
+ * Represents a source dataset referenced from a lineage entry.
256
+ */
257
+ export interface SourceRecord {
258
+ uuid?: string
259
+ title?: string
260
+ href?: string
261
+ }
262
+
257
263
  export interface DatasetRecord extends BaseRecord {
258
264
  kind: 'dataset'
259
265
  status: RecordStatus
260
266
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
267
+ sourceRecords: Array<SourceRecord>
261
268
  onlineResources: Array<DatasetOnlineResource>
262
269
  spatialExtents: Array<DatasetSpatialExtent>
263
270
  temporalExtents: Array<DatasetTemporalExtent>
@@ -285,6 +292,7 @@ export interface ServiceRecord extends BaseRecord {
285
292
  export interface ReuseRecord extends BaseRecord {
286
293
  kind: 'reuse'
287
294
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
295
+ sourceRecords: Array<SourceRecord>
288
296
  onlineResources: Array<DatasetOnlineResource>
289
297
  reuseType: ReuseType
290
298
  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
@@ -0,0 +1,8 @@
1
+ export interface GroupModel {
2
+ groupId: number
3
+ groupName: string
4
+ isMember: boolean
5
+ canEdit: boolean
6
+ canApprove: boolean
7
+ canAdministrate: boolean
8
+ }
@@ -1 +1,2 @@
1
1
  export * from './user.model'
2
+ export * from './group.model'
@@ -1,5 +1,6 @@
1
1
  import type { Observable } from 'rxjs'
2
2
  import type { UserModel } from './model/user/user.model'
3
+ import type { GroupModel } from './model/user/group.model'
3
4
  import type { Organization } from './model/record/organization.model'
4
5
  import { CatalogRecord, Keyword, UserFeedback } from './model/record'
5
6
  import { KeywordType } from './model/thesaurus'
@@ -28,6 +29,7 @@ export abstract class PlatformServiceInterface {
28
29
  abstract getMe(): Observable<UserModel>
29
30
  abstract isAnonymous(): Observable<boolean>
30
31
  abstract getUsers(): Observable<UserModel[]>
32
+ abstract getUserPermissionsByGroup(): Observable<GroupModel[]>
31
33
  abstract getUsersByOrganization(
32
34
  organisation: Organization
33
35
  ): Observable<UserModel[]>
@@ -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
 
@@ -134,6 +135,7 @@ Cette section contient des *caractères internationaux* (ainsi que des "caractè
134
135
  lineage: `This record was edited manually to test the conversion processes
135
136
 
136
137
  As such, **it is not very interesting at all.**`,
138
+ sourceRecords: [],
137
139
  licenses: [
138
140
  {
139
141
  text: 'Licence ODbL mai 2013 (basée sur ODbL 1.0)',
@@ -310,6 +312,7 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
310
312
  ],
311
313
  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.
312
314
  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.`,
315
+ sourceRecords: [],
313
316
  legalConstraints: [],
314
317
  securityConstraints: [],
315
318
  otherConstraints: [],
@@ -361,6 +364,7 @@ export const simpleDatasetRecordFixture = (): DatasetRecord => ({
361
364
  overviews: [],
362
365
  spatialExtents: [],
363
366
  temporalExtents: [],
367
+ sourceRecords: [],
364
368
  onlineResources: [
365
369
  {
366
370
  type: 'download',
@@ -413,6 +417,7 @@ export const simpleDatasetRecordWithFcatsFixture = (): DatasetRecord => ({
413
417
  overviews: [],
414
418
  spatialExtents: [],
415
419
  temporalExtents: [],
420
+ sourceRecords: [],
416
421
  onlineResources: [],
417
422
  updateFrequency: { per: 'month', updatedTimes: 3 },
418
423
  translations: {},
@@ -978,6 +983,7 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
978
983
  title: 'English Title',
979
984
  abstract: 'English Abstract',
980
985
  lineage: 'English Lineage',
986
+ sourceRecords: [],
981
987
  translations: {
982
988
  title: { fr: 'Titre Français', de: 'Titel DE' },
983
989
  abstract: { fr: 'Résumé Français', de: 'Beschreibung DE' },
@@ -1022,3 +1028,53 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
1022
1028
  topics: [],
1023
1029
  recordUpdated: null,
1024
1030
  })
1031
+
1032
+ export const simpleReuseRecordFixture = (): ReuseRecord => ({
1033
+ abstract: `Sample reuse record`,
1034
+ kind: 'reuse',
1035
+ reuseType: 'application',
1036
+ recordUpdated: new Date('2023-03-17T07:38:08.875Z'),
1037
+ recordPublished: null,
1038
+ ownerOrganization: null,
1039
+ licenses: [],
1040
+ legalConstraints: [],
1041
+ securityConstraints: [],
1042
+ otherConstraints: [],
1043
+ contacts: [],
1044
+ contactsForResource: [],
1045
+ keywords: [],
1046
+ topics: [],
1047
+ spatialExtents: [],
1048
+ temporalExtents: [],
1049
+ lineage: 'bla',
1050
+ sourceRecords: [],
1051
+ overviews: [],
1052
+ defaultLanguage: null,
1053
+ otherLanguages: [],
1054
+ title: 'Sites de gestion des déchets miniers - Service de visualisation WMS',
1055
+ onlineResources: [
1056
+ {
1057
+ name: 'Rapport de disponibilité du service WMS',
1058
+ description:
1059
+ '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".',
1060
+ mimeType: 'text/html',
1061
+ type: 'link',
1062
+ url: new URL(
1063
+ 'https://geoservices.wallonie.be/rapportDisponibilite/wms/sites_de_gestion_des_dechets_miniers_srie.html'
1064
+ ),
1065
+ accessRestricted: false,
1066
+ },
1067
+ ],
1068
+ uniqueIdentifier: '00b22798-ec8e-4500-89e8-90eeeda45919',
1069
+ landingPage: new URL(
1070
+ 'http://localhost:4200/geonetwork/srv/fre/catalog.search#/metadata/00b22798-ec8e-4500-89e8-90eeeda45919'
1071
+ ),
1072
+ extras: {
1073
+ isPublishedToAll: true,
1074
+ id: '723',
1075
+ isHarvested: true,
1076
+ ownerInfo: 'admin|admin|admin|Administrator',
1077
+ edit: true,
1078
+ },
1079
+ recordCreated: new Date('2023-03-17T07:38:08.875Z'),
1080
+ })
@@ -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
  }
@@ -1,6 +1,7 @@
1
1
  import { createAction, props } from '@ngrx/store'
2
2
  import {
3
3
  CatalogRecord,
4
+ CatalogRecordKeys,
4
5
  LanguageCode,
5
6
  } from '../../../../../../libs/common/domain/src/lib/model/record'
6
7
  import { SaveRecordError } from './editor.models'
@@ -28,7 +29,10 @@ export const markRecordAsChanged = createAction(
28
29
  '[Editor] Mark record as changed'
29
30
  )
30
31
 
31
- export const saveRecord = createAction('[Editor] Save record')
32
+ export const saveRecord = createAction(
33
+ '[Editor] Save record',
34
+ props<{ publish: boolean }>()
35
+ )
32
36
  export const saveRecordSuccess = createAction('[Editor] Save record success')
33
37
  export const saveRecordFailure = createAction(
34
38
  '[Editor] Save record failure',
@@ -73,3 +77,8 @@ export const canEditRecord = createAction(
73
77
  '[Editor] User can edit record',
74
78
  props<{ canEditRecord: boolean }>()
75
79
  )
80
+
81
+ export const setFocusedField = createAction(
82
+ '[Editor] Set focused field',
83
+ props<{ model: CatalogRecordKeys }>()
84
+ )
@@ -12,7 +12,6 @@ import {
12
12
  } from './editor.selectors'
13
13
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
14
14
  import { PlatformServiceInterface } from '../../../../../../libs/common/domain/src/lib/platform.service.interface'
15
-
16
15
  @Injectable()
17
16
  export class EditorEffects {
18
17
  private actions$ = inject(Actions)
@@ -29,30 +28,37 @@ export class EditorEffects {
29
28
  this.store.select(selectRecordSource),
30
29
  this.store.select(selectEditorConfig)
31
30
  ),
32
- switchMap(([, record, recordSource, fieldsConfig]) =>
33
- this.editorService.saveRecord(record, recordSource, fieldsConfig).pipe(
34
- switchMap(([savedRecord, savedRecordSource]) => {
35
- 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()]
36
37
 
37
- if (!record?.uniqueIdentifier) {
38
- actions.push(
39
- EditorActions.openRecord({
40
- record: savedRecord,
41
- recordSource: savedRecordSource,
38
+ if (publish) {
39
+ // saving without publishing doesn't unpublish
40
+ actions.push(EditorActions.isPublished({ isPublished: true }))
41
+ }
42
+
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,
42
58
  })
43
59
  )
44
- }
45
-
46
- return of(...actions)
47
- }),
48
- catchError((error) =>
49
- of(
50
- EditorActions.saveRecordFailure({
51
- error,
52
- })
53
60
  )
54
61
  )
55
- )
56
62
  )
57
63
  )
58
64
  )
@@ -4,9 +4,10 @@ import * as EditorActions from './editor.actions'
4
4
  import * as EditorSelectors from './editor.selectors'
5
5
  import {
6
6
  CatalogRecord,
7
+ CatalogRecordKeys,
7
8
  LanguageCode,
8
9
  } from '../../../../../../libs/common/domain/src/lib/model/record'
9
- import { filter } from 'rxjs'
10
+ import { filter, map } from 'rxjs'
10
11
  import { Actions, ofType } from '@ngrx/effects'
11
12
  import { EditorConfig, EditorFieldIdentification } from '../models'
12
13
 
@@ -37,6 +38,10 @@ export class EditorFacade {
37
38
  )
38
39
  isPublished$ = this.store.pipe(select(EditorSelectors.selectIsPublished))
39
40
  canEditRecord$ = this.store.pipe(select(EditorSelectors.selectCanEditRecord))
41
+ focusedField$ = this.actions$.pipe(
42
+ ofType(EditorActions.setFocusedField),
43
+ map(({ model }) => model)
44
+ )
40
45
 
41
46
  openRecord(record: CatalogRecord, recordSource: string) {
42
47
  this.store.dispatch(
@@ -49,7 +54,11 @@ export class EditorFacade {
49
54
  }
50
55
 
51
56
  saveRecord() {
52
- 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 }))
53
62
  }
54
63
 
55
64
  undoRecordDraft() {
@@ -77,6 +86,10 @@ export class EditorFacade {
77
86
  this.store.dispatch(EditorActions.setCurrentPage({ page }))
78
87
  }
79
88
 
89
+ setFocusedField(model: CatalogRecordKeys) {
90
+ this.store.dispatch(EditorActions.setFocusedField({ model }))
91
+ }
92
+
80
93
  setFieldVisibility(field: EditorFieldIdentification, visible: boolean) {
81
94
  this.store.dispatch(EditorActions.setFieldVisibility({ field, visible }))
82
95
  }