geonetwork-ui 2.4.0-dev.db93b4c4 → 2.4.0-dev.e079151e

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 (133) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +2 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +5 -5
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +30 -2
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.mjs +31 -0
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +23 -1
  6. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +6 -1
  7. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +12 -1
  8. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  9. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  10. package/esm2022/libs/data-access/gn4/src/openapi/api/records.api.service.mjs +2 -6
  11. package/esm2022/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.mjs +138 -0
  12. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.mjs +21 -0
  13. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +19 -9
  14. package/esm2022/libs/feature/editor/src/lib/components/record-form/record-form.component.mjs +2 -1
  15. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +12 -2
  16. package/esm2022/libs/feature/map/src/lib/utils/map-utils.service.mjs +3 -4
  17. package/esm2022/libs/feature/search/src/lib/results-table/results-table-container.component.mjs +9 -3
  18. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +5 -11
  19. package/esm2022/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.mjs +3 -3
  20. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +5 -2
  21. package/esm2022/libs/ui/elements/src/lib/user-preview/user-preview.component.mjs +3 -3
  22. package/esm2022/libs/ui/inputs/src/lib/image-input/image-input.component.mjs +11 -5
  23. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +29 -0
  24. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +15 -7
  25. package/esm2022/libs/ui/widgets/src/index.mjs +2 -1
  26. package/esm2022/libs/ui/widgets/src/lib/popover/popover.component.mjs +68 -0
  27. package/esm2022/translations/de.json +11 -17
  28. package/esm2022/translations/en.json +4 -10
  29. package/esm2022/translations/es.json +3 -9
  30. package/esm2022/translations/fr.json +4 -10
  31. package/esm2022/translations/it.json +4 -10
  32. package/esm2022/translations/nl.json +3 -9
  33. package/esm2022/translations/pt.json +3 -9
  34. package/fesm2022/geonetwork-ui.mjs +440 -131
  35. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  36. package/libs/api/metadata-converter/src/index.d.ts +1 -0
  37. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  38. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -1
  39. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +8 -1
  40. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  41. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts +5 -0
  42. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts.map +1 -0
  43. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +3 -1
  44. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  45. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts +1 -0
  46. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts.map +1 -1
  47. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +1 -0
  48. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  49. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +2 -1
  50. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  51. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +9 -0
  52. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  53. package/libs/data-access/gn4/src/openapi/api/records.api.service.d.ts.map +1 -1
  54. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts +27 -0
  55. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts.map +1 -0
  56. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts +11 -0
  57. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts.map +1 -0
  58. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +5 -1
  59. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  60. package/libs/feature/editor/src/lib/components/record-form/record-form.component.d.ts.map +1 -1
  61. package/libs/feature/editor/src/lib/fields.config.d.ts +1 -0
  62. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  63. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts.map +1 -1
  64. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +3 -1
  65. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  66. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  67. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts +0 -3
  68. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  69. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +1 -1
  70. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +7 -6
  71. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  72. package/libs/ui/inputs/src/lib/image-input/image-input.component.d.ts +4 -2
  73. package/libs/ui/inputs/src/lib/image-input/image-input.component.d.ts.map +1 -1
  74. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts +11 -0
  75. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -0
  76. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +4 -2
  77. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  78. package/libs/ui/widgets/src/index.d.ts +1 -0
  79. package/libs/ui/widgets/src/index.d.ts.map +1 -1
  80. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts +19 -0
  81. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts.map +1 -0
  82. package/package.json +1 -1
  83. package/src/libs/api/metadata-converter/src/index.ts +1 -0
  84. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +5 -1
  85. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +37 -12
  86. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -1
  87. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +4 -2
  88. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +72 -2
  89. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.ts +39 -0
  90. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +70 -1
  91. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +8 -0
  92. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +20 -0
  93. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -1
  94. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +12 -0
  95. package/src/libs/data-access/gn4/src/openapi/api/records.api.service.ts +1 -5
  96. package/src/libs/data-access/gn4/src/spec.yaml +0 -8
  97. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html +3 -1
  98. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts +117 -21
  99. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.css +0 -0
  100. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html +5 -0
  101. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +22 -0
  102. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  103. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +13 -1
  104. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +1 -0
  105. package/src/libs/feature/editor/src/lib/fields.config.ts +12 -1
  106. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +2 -3
  107. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +1 -0
  108. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +5 -0
  109. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html +14 -20
  110. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +0 -10
  111. package/src/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html +1 -1
  112. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -1
  113. package/src/libs/ui/elements/src/lib/user-preview/user-preview.component.html +1 -1
  114. package/src/libs/ui/inputs/src/lib/image-input/image-input.component.html +1 -1
  115. package/src/libs/ui/inputs/src/lib/image-input/image-input.component.ts +7 -2
  116. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.css +0 -0
  117. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +17 -0
  118. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +22 -0
  119. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +9 -0
  120. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +26 -12
  121. package/src/libs/ui/widgets/src/index.ts +1 -0
  122. package/src/libs/ui/widgets/src/lib/popover/popover.component.css +0 -0
  123. package/src/libs/ui/widgets/src/lib/popover/popover.component.html +3 -0
  124. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +85 -0
  125. package/tailwind.base.css +2 -1
  126. package/translations/de.json +11 -17
  127. package/translations/en.json +4 -10
  128. package/translations/es.json +3 -9
  129. package/translations/fr.json +4 -10
  130. package/translations/it.json +4 -10
  131. package/translations/nl.json +3 -9
  132. package/translations/pt.json +3 -9
  133. package/translations/sk.json +4 -10
@@ -6,6 +6,7 @@ import {
6
6
  XmlText,
7
7
  } from '@rgrove/parse-xml'
8
8
  import { ChainableFunction, fallback } from './function-utils'
9
+
9
10
  export { XmlDocument, XmlElement } from '@rgrove/parse-xml'
10
11
 
11
12
  export class XmlParseError extends Error {
@@ -115,6 +116,10 @@ export function allChildrenElement(element: XmlElement): Array<XmlElement> {
115
116
  ] as Array<XmlElement>
116
117
  }
117
118
 
119
+ export function firstChildElement(element: XmlElement): XmlElement {
120
+ return allChildrenElement(element)[0] ?? null
121
+ }
122
+
118
123
  /**
119
124
  * Will return all matching elements nested according to the given
120
125
  * names (similar to a path), starting form the input element;
@@ -228,6 +233,7 @@ export function xmlToString(
228
233
  ${padding}<${el.name}${attrs}/>
229
234
  ${parentPadding}`
230
235
  }
236
+
231
237
  return `
232
238
  ${padding}<${el.name}${attrs}>${children}</${el.name}>
233
239
  ${parentPadding}`
@@ -307,11 +313,13 @@ function getTreeRoot(element: XmlElement): XmlElement {
307
313
 
308
314
  // stays on the parent element
309
315
  // if the given elements are part of a subtree, will add the root of subtree
316
+ // will filter out falsy elements
310
317
  export function appendChildren(
311
318
  ...childrenFns: Array<ChainableFunction<void, XmlElement>>
312
319
  ): ChainableFunction<XmlElement, XmlElement> {
313
320
  return (element) => {
314
321
  if (!element) return null
322
+ childrenFns = childrenFns.filter((fn) => fn)
315
323
  element.children.push(...childrenFns.map((fn) => fn()).map(getTreeRoot))
316
324
  element.children.forEach((el) => (el.parent = element))
317
325
  return element
@@ -24,6 +24,7 @@ import {
24
24
  } from '../../../../../../libs/common/domain/src/lib/model/search'
25
25
  import { catchError, map, tap } from 'rxjs/operators'
26
26
  import {
27
+ BaseConverter,
27
28
  findConverterForDocument,
28
29
  Gn4Converter,
29
30
  Gn4SearchResults,
@@ -230,6 +231,25 @@ export class Gn4Repository implements RecordsRepositoryInterface {
230
231
  )
231
232
  }
232
233
 
234
+ openRecordForDuplication(
235
+ uniqueIdentifier: string
236
+ ): Observable<[CatalogRecord, string, false] | null> {
237
+ return this.loadRecordAsXml(uniqueIdentifier).pipe(
238
+ switchMap(async (recordAsXml) => {
239
+ const converter = findConverterForDocument(recordAsXml)
240
+ const record = await converter.readRecord(recordAsXml)
241
+ record.uniqueIdentifier = `TEMP-ID-${Date.now()}`
242
+ record.title = `${record.title} (Copy)`
243
+ const xml = await converter.writeRecord(record, recordAsXml)
244
+ window.localStorage.setItem(
245
+ this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
246
+ xml
247
+ )
248
+ return [record, xml, false] as [CatalogRecord, string, false]
249
+ })
250
+ )
251
+ }
252
+
233
253
  private serializeRecordToXml(
234
254
  record: CatalogRecord,
235
255
  referenceRecordSource?: string
@@ -159,7 +159,8 @@ export interface GraphicOverview {
159
159
  }
160
160
 
161
161
  export interface DatasetSpatialExtent {
162
- geometry: Geometry
162
+ bbox?: [number, number, number, number]
163
+ geometry?: Geometry
163
164
  description?: string
164
165
  }
165
166
 
@@ -30,6 +30,18 @@ export abstract class RecordsRepositoryInterface {
30
30
  uniqueIdentifier: string
31
31
  ): Observable<[CatalogRecord, string, boolean] | null>
32
32
 
33
+ /**
34
+ * This emits once:
35
+ * - record object with a new unique identifier and suffixed title
36
+ * - serialized representation of the record as text
37
+ * - false, as the duplicated record is always a draft
38
+ * @param uniqueIdentifier
39
+ * @returns Observable<[CatalogRecord, string, false] | null>
40
+ */
41
+ abstract openRecordForDuplication(
42
+ uniqueIdentifier: string
43
+ ): Observable<[CatalogRecord, string, false] | null>
44
+
33
45
  /**
34
46
  * @param record
35
47
  * @param referenceRecordSource
@@ -7201,11 +7201,7 @@ export class RecordsApiService {
7201
7201
  }
7202
7202
 
7203
7203
  // to determine the Content-Type header
7204
- const consumes: string[] = [
7205
- 'application/xml',
7206
- 'application/json',
7207
- 'application/x-www-form-urlencoded',
7208
- ]
7204
+ const consumes: string[] = ['application/xml']
7209
7205
  const httpContentTypeSelected: string | undefined =
7210
7206
  this.configuration.selectHeaderContentType(consumes)
7211
7207
  if (httpContentTypeSelected !== undefined) {
@@ -1832,14 +1832,6 @@ paths:
1832
1832
  schema:
1833
1833
  type: string
1834
1834
  description: XML fragment.
1835
- application/json:
1836
- schema:
1837
- type: string
1838
- description: XML fragment.
1839
- application/x-www-form-urlencoded:
1840
- schema:
1841
- type: string
1842
- description: XML fragment.
1843
1835
  responses:
1844
1836
  default:
1845
1837
  description: default response
@@ -1,8 +1,10 @@
1
1
  <gn-ui-image-input
2
2
  [maxSizeMB]="5"
3
3
  [previewUrl]="resourceUrl"
4
- [altText]="resourceFileName"
4
+ [altText]="resourceAltText"
5
+ [formControl]="formControl"
5
6
  (fileChange)="handleFileChange($event)"
6
7
  (urlChange)="handleUrlChange($event)"
8
+ (altTextChange)="handleAltTextChange($event)"
7
9
  (delete)="handleDelete()"
8
10
  ></gn-ui-image-input>
@@ -2,12 +2,24 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  ChangeDetectorRef,
4
4
  Component,
5
+ EventEmitter,
5
6
  Input,
7
+ OnChanges,
6
8
  OnInit,
9
+ Output,
10
+ SimpleChanges,
7
11
  } from '@angular/core'
8
12
  import { CommonModule } from '@angular/common'
9
13
  import { RecordsApiService } from '../../../../../../../libs/data-access/gn4/src'
10
14
  import { UiInputsModule } from '../../../../../../../libs/ui/inputs/src'
15
+ import { FormControl } from '@angular/forms'
16
+ import { GraphicOverview } from '../../../../../../../libs/common/domain/src/lib/model/record'
17
+
18
+ const extractFileNameFromUrl = (url: string): string => {
19
+ const pattern = new RegExp(`attachments/([^/?#]+)(?:[/?#]|$)`, 'i')
20
+ const match = url.match(pattern)
21
+ return match ? match[1] : ''
22
+ }
11
23
 
12
24
  @Component({
13
25
  selector: 'gn-ui-overview-upload',
@@ -17,11 +29,15 @@ import { UiInputsModule } from '../../../../../../../libs/ui/inputs/src'
17
29
  styleUrls: ['./overview-upload.component.css'],
18
30
  changeDetection: ChangeDetectionStrategy.OnPush,
19
31
  })
20
- export class OverviewUploadComponent implements OnInit {
32
+ export class OverviewUploadComponent implements OnInit, OnChanges {
21
33
  @Input() metadataUuid: string
34
+ @Input() formControl!: FormControl
35
+ @Output() overviewChange = new EventEmitter<GraphicOverview | null>()
36
+ @Output() altTextChange: EventEmitter<string> = new EventEmitter()
22
37
 
23
- resourceFileName: string
24
- resourceUrl: string
38
+ resourceAltText = '' // = ressourceFileName by default
39
+ resourceFileName = ''
40
+ resourceUrl: URL
25
41
 
26
42
  constructor(
27
43
  private recordsApiService: RecordsApiService,
@@ -29,42 +45,122 @@ export class OverviewUploadComponent implements OnInit {
29
45
  ) {}
30
46
 
31
47
  ngOnInit(): void {
32
- this.recordsApiService
33
- .getAllResources(this.metadataUuid)
34
- .subscribe((resources) => {
35
- this.resourceFileName = resources[0]?.filename
36
- this.resourceUrl = resources[0]?.url
48
+ this.recordsApiService.getAllResources(this.metadataUuid).subscribe({
49
+ next: (resources) => {
50
+ if (resources && resources.length > 0) {
51
+ this.resourceUrl = new URL(resources[0]?.url)
52
+ this.resourceAltText =
53
+ this.resourceAltText === ''
54
+ ? resources[0].filename
55
+ : this.resourceAltText
56
+ this.resourceFileName = extractFileNameFromUrl(resources[0]?.url)
57
+ } else {
58
+ this.resourceUrl = null
59
+ this.resourceAltText = ''
60
+ this.resourceFileName = ''
61
+ }
62
+
37
63
  this.cd.markForCheck()
38
- })
64
+ },
65
+ error: this.errorHandle,
66
+ })
39
67
  }
40
68
 
41
69
  handleFileChange(file: File) {
42
70
  this.recordsApiService
43
71
  .putResource(this.metadataUuid, file, 'public')
44
- .subscribe((resource) => {
45
- this.resourceFileName = resource.filename
46
- this.resourceUrl = resource.url
47
- this.cd.markForCheck()
72
+ .subscribe({
73
+ next: (resource) => {
74
+ this.resourceUrl = new URL(resource.url)
75
+ this.resourceAltText = resource.filename
76
+
77
+ this.overviewChange.emit({
78
+ url: new URL(resource.url),
79
+ description: resource.filename,
80
+ })
81
+
82
+ this.cd.markForCheck()
83
+ },
84
+ error: this.errorHandle,
48
85
  })
49
86
  }
50
87
 
51
88
  handleUrlChange(url: string) {
52
89
  this.recordsApiService
53
90
  .putResourceFromURL(this.metadataUuid, url, 'public')
54
- .subscribe((resource) => {
55
- this.resourceFileName = resource.filename
56
- this.resourceUrl = resource.url
57
- this.cd.markForCheck()
91
+ .subscribe({
92
+ next: (resource) => {
93
+ this.resourceUrl = new URL(resource.url)
94
+ this.resourceAltText = resource.filename
95
+
96
+ this.overviewChange.emit({
97
+ url: new URL(resource.url),
98
+ description: resource.filename,
99
+ })
100
+
101
+ this.cd.markForCheck()
102
+ },
103
+ error: this.errorHandle,
58
104
  })
59
105
  }
60
106
 
107
+ handleAltTextChange(newAltText: string) {
108
+ this.resourceAltText = newAltText
109
+
110
+ this.overviewChange.emit({
111
+ url: this.resourceUrl,
112
+ description: this.resourceAltText,
113
+ })
114
+ this.cd.markForCheck()
115
+ }
116
+
61
117
  handleDelete() {
62
118
  this.recordsApiService
63
119
  .delResource(this.metadataUuid, this.resourceFileName)
64
- .subscribe(() => {
65
- this.resourceFileName = null
66
- this.resourceUrl = null
67
- this.cd.markForCheck()
120
+ .subscribe({
121
+ next: () => {
122
+ this.resourceAltText = null
123
+ this.resourceUrl = null
124
+
125
+ this.overviewChange.emit(null)
126
+
127
+ this.cd.markForCheck()
128
+ },
129
+ error: this.errorHandle,
68
130
  })
69
131
  }
132
+
133
+ private errorHandle = (error: never) => {
134
+ console.error(error)
135
+
136
+ this.resourceUrl = null
137
+ this.resourceAltText = ''
138
+ this.resourceFileName = ''
139
+
140
+ this.overviewChange.emit(null)
141
+
142
+ this.cd.markForCheck()
143
+ }
144
+
145
+ ngOnChanges(changes: SimpleChanges): void {
146
+ const overviewChanges = changes['formControl']
147
+ if (
148
+ overviewChanges &&
149
+ overviewChanges.currentValue !== overviewChanges.previousValue
150
+ ) {
151
+ let overview: GraphicOverview
152
+ if (
153
+ overviewChanges.currentValue.value &&
154
+ overviewChanges.currentValue.value.length > 0
155
+ ) {
156
+ overview = overviewChanges.currentValue.value[0] as GraphicOverview
157
+ } else {
158
+ return
159
+ }
160
+ if (overview.description && overview.description !== '') {
161
+ this.resourceAltText = overview.description
162
+ this.cd.markForCheck()
163
+ }
164
+ }
165
+ }
70
166
  }
@@ -0,0 +1,5 @@
1
+ <gn-ui-overview-upload
2
+ [metadataUuid]="metadataUuid"
3
+ [formControl]="control"
4
+ (overviewChange)="handleOverviewChange($event)"
5
+ ></gn-ui-overview-upload>
@@ -0,0 +1,22 @@
1
+ import { CommonModule } from '@angular/common'
2
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
3
+ import { OverviewUploadComponent } from '../../../overview-upload/overview-upload.component'
4
+ import { FormControl } from '@angular/forms'
5
+ import { GraphicOverview } from '../../../../../../../../../libs/common/domain/src/lib/model/record'
6
+
7
+ @Component({
8
+ selector: 'gn-ui-form-field-overviews',
9
+ templateUrl: './form-field-overviews.component.html',
10
+ styleUrls: ['./form-field-overviews.component.css'],
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ standalone: true,
13
+ imports: [CommonModule, OverviewUploadComponent],
14
+ })
15
+ export class FormFieldOverviewsComponent {
16
+ @Input() metadataUuid: string
17
+ @Input() control!: FormControl
18
+
19
+ handleOverviewChange(overView: GraphicOverview | null) {
20
+ this.control.setValue(overView ? [overView] : [])
21
+ }
22
+ }
@@ -76,6 +76,12 @@
76
76
  <ng-container *ngIf="isSpatialExtentField">
77
77
  <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>
78
78
  </ng-container>
79
+ <ng-container *ngIf="isGraphicOverview">
80
+ <gn-ui-form-field-overviews
81
+ [control]="formControl"
82
+ [metadataUuid]="metadataUuid$ | async"
83
+ ></gn-ui-form-field-overviews>
84
+ </ng-container>
79
85
  <ng-container *ngIf="isKeywords">
80
86
  <gn-ui-form-field-keywords
81
87
  [control]="formControl"
@@ -28,6 +28,9 @@ import { FormFieldSpatialExtentComponent } from './form-field-spatial-extent/for
28
28
  import { FormFieldUpdateFrequencyComponent } from './form-field-update-frequency/form-field-update-frequency.component'
29
29
  import { CatalogRecordKeys } from '../../../../../../../../libs/common/domain/src/lib/model/record'
30
30
  import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component'
31
+ import { FormFieldOverviewsComponent } from './form-field-overviews/form-field-overviews.component'
32
+ import { map, take } from 'rxjs/operators'
33
+ import { EditorFacade } from '../../../+state/editor.facade'
31
34
  import { FormFieldConfig } from '../../../models'
32
35
 
33
36
  @Component({
@@ -55,6 +58,7 @@ import { FormFieldConfig } from '../../../models'
55
58
  FormFieldArrayComponent,
56
59
  FormFieldKeywordsComponent,
57
60
  TranslateModule,
61
+ FormFieldOverviewsComponent,
58
62
  ],
59
63
  })
60
64
  export class FormFieldComponent {
@@ -70,9 +74,14 @@ export class FormFieldComponent {
70
74
 
71
75
  @ViewChild('titleInput') titleInput: ElementRef
72
76
 
77
+ metadataUuid$ = this.facade.record$.pipe(
78
+ take(1),
79
+ map((record) => record.uniqueIdentifier)
80
+ )
81
+
73
82
  formControl = new FormControl()
74
83
 
75
- constructor() {
84
+ constructor(private facade: EditorFacade) {
76
85
  this.valueChange = this.formControl.valueChanges
77
86
  }
78
87
 
@@ -101,6 +110,9 @@ export class FormFieldComponent {
101
110
  get isSpatialExtentField() {
102
111
  return this.model === 'spatialExtents'
103
112
  }
113
+ get isGraphicOverview() {
114
+ return this.model === 'overviews'
115
+ }
104
116
  get isSimpleField() {
105
117
  return this.model === 'uniqueIdentifier' || this.model === 'recordUpdated'
106
118
  }
@@ -24,6 +24,7 @@ export class RecordFormComponent {
24
24
  if (!model) {
25
25
  return
26
26
  }
27
+ console.log(newValue)
27
28
  this.facade.updateRecordField(model, newValue)
28
29
  }
29
30
 
@@ -83,6 +83,13 @@ export const RECORD_ABSTRACT_FIELD: EditorField = {
83
83
  },
84
84
  }
85
85
 
86
+ export const RECORD_GRAPHICAL_OVERVIEW_FIELD: EditorField = {
87
+ model: 'overviews',
88
+ formFieldConfig: {
89
+ labelKey: marker('editor.record.form.field.overviews'),
90
+ },
91
+ }
92
+
86
93
  /************************************************************
87
94
  *************** SECTIONS *****************
88
95
  ************************************************************
@@ -90,7 +97,11 @@ export const RECORD_ABSTRACT_FIELD: EditorField = {
90
97
 
91
98
  export const TITLE_SECTION: EditorSection = {
92
99
  hidden: false,
93
- fields: [RECORD_TITLE_FIELD, RECORD_ABSTRACT_FIELD],
100
+ fields: [
101
+ RECORD_TITLE_FIELD,
102
+ RECORD_ABSTRACT_FIELD,
103
+ RECORD_GRAPHICAL_OVERVIEW_FIELD,
104
+ ],
94
105
  }
95
106
 
96
107
  export const ABOUT_SECTION: EditorSection = {
@@ -220,11 +220,10 @@ export class MapUtilsService {
220
220
  if (!('spatialExtents' in record) || record.spatialExtents.length === 0) {
221
221
  return null
222
222
  }
223
- // transform an array of geojson geometries into a bbox
223
+ // extend all the spatial extents bbox into an including bbox
224
224
  const totalExtent = record.spatialExtents.reduce(
225
225
  (prev, curr) => {
226
- const geom = GEOJSON.readGeometry(curr.geometry)
227
- return extend(prev, geom.getExtent())
226
+ return extend(prev, curr.bbox)
228
227
  },
229
228
  [Infinity, Infinity, -Infinity, -Infinity]
230
229
  )
@@ -4,6 +4,7 @@
4
4
  [selectedRecordsIdentifiers]="selectedRecords$ | async"
5
5
  [sortOrder]="sortBy$ | async"
6
6
  (recordClick)="handleRecordClick($event)"
7
+ (duplicateRecord)="handleDuplicateRecord($event)"
7
8
  (recordsSelectedChange)="handleRecordsSelectedChange($event[0], $event[1])"
8
9
  (sortByChange)="handleSortByChange($event[0], $event[1])"
9
10
  ></gn-ui-results-table>
@@ -16,6 +16,7 @@ import { CommonModule } from '@angular/common'
16
16
  })
17
17
  export class ResultsTableContainerComponent {
18
18
  @Output() recordClick = new EventEmitter<CatalogRecord>()
19
+ @Output() duplicateRecord = new EventEmitter<CatalogRecord>()
19
20
 
20
21
  records$ = this.searchFacade.results$
21
22
  selectedRecords$ = this.selectionService.selectedRecordsIdentifiers$
@@ -35,6 +36,10 @@ export class ResultsTableContainerComponent {
35
36
  this.recordClick.emit(item as CatalogRecord)
36
37
  }
37
38
 
39
+ handleDuplicateRecord(item: unknown) {
40
+ this.duplicateRecord.emit(item as CatalogRecord)
41
+ }
42
+
38
43
  handleSortByChange(col: string, order: 'asc' | 'desc') {
39
44
  this.searchService.setSortBy([order, col])
40
45
  }
@@ -1,22 +1,16 @@
1
- <div
2
- *ngIf="metadataQualityDisplay"
3
- class="mb-6 metadata-quality"
4
- (mouseenter)="showMenu()"
5
- (mouseleave)="hideMenu()"
6
- >
7
- <div class="min-w-[200px]" [class]="smaller ? 'leading-[8px]' : ''">
8
- <gn-ui-progress-bar
9
- (focus)="showMenu()"
10
- (blur)="hideMenu()"
11
- tabindex="0"
12
- [value]="qualityScore"
13
- type="primary"
14
- ></gn-ui-progress-bar>
15
- </div>
16
- <div
17
- class="absolute z-10 bg-white border border-black border-opacity-35 rounded-lg shadow-lg p-5 whitespace-nowrap"
18
- [class]="isMenuShown ? 'block' : 'hidden'"
19
- >
1
+ <div *ngIf="metadataQualityDisplay" class="mb-6 metadata-quality">
2
+ <gn-ui-popover [content]="popoverItems" theme="light-border">
3
+ <div class="min-w-[200px]" [class]="smaller ? 'leading-[8px]' : ''">
4
+ <gn-ui-progress-bar
5
+ tabindex="0"
6
+ [value]="qualityScore"
7
+ type="primary"
8
+ ></gn-ui-progress-bar>
9
+ </div>
10
+ </gn-ui-popover>
11
+ </div>
12
+ <ng-template #popoverItems>
13
+ <div class="p-2 py-4">
20
14
  <div class="mb-4 font-bold" translate>record.metadata.quality.details</div>
21
15
  <gn-ui-metadata-quality-item
22
16
  *ngFor="let e of items"
@@ -24,4 +18,4 @@
24
18
  [value]="e.value"
25
19
  ></gn-ui-metadata-quality-item>
26
20
  </div>
27
- </div>
21
+ </ng-template>
@@ -21,8 +21,6 @@ export class MetadataQualityComponent implements OnChanges {
21
21
 
22
22
  items: MetadataQualityItem[] = []
23
23
 
24
- isMenuShown = false
25
-
26
24
  get qualityScore() {
27
25
  const qualityScore = this.metadata?.extras?.qualityScore
28
26
  return typeof qualityScore === 'number'
@@ -36,14 +34,6 @@ export class MetadataQualityComponent implements OnChanges {
36
34
  )
37
35
  }
38
36
 
39
- showMenu() {
40
- this.isMenuShown = true
41
- }
42
-
43
- hideMenu() {
44
- this.isMenuShown = false
45
- }
46
-
47
37
  private add(name: string, value: boolean) {
48
38
  if (this.metadataQualityDisplay?.[name] !== false) {
49
39
  this.items.push({ name, value })
@@ -1,4 +1,4 @@
1
1
  <div class="ml-4 flex flex-row">
2
- <mat-icon class="material-symbols-outlined">{{ icon }}</mat-icon>
2
+ <mat-icon class="material-symbols-outlined min-w-fit">{{ icon }}</mat-icon>
3
3
  <p class="ml-2 text">{{ labelKey | translate }}</p>
4
4
  </div>
@@ -9,7 +9,7 @@ import { ContentGhostComponent } from './content-ghost/content-ghost.component'
9
9
  import { DownloadItemComponent } from './download-item/download-item.component'
10
10
  import { DownloadsListComponent } from './downloads-list/downloads-list.component'
11
11
  import { ApiCardComponent } from './api-card/api-card.component'
12
- import { UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
12
+ import { PopoverComponent, UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
13
13
  import { MaxLinesComponent, UiLayoutModule } from '../../../../../libs/ui/layout/src'
14
14
  import { TranslateModule } from '@ngx-translate/core'
15
15
  import { RelatedRecordCardComponent } from './related-record-card/related-record-card.component'
@@ -45,6 +45,7 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
45
45
  UiInputsModule,
46
46
  FormsModule,
47
47
  NgOptimizedImage,
48
+ PopoverComponent,
48
49
  MarkdownParserComponent,
49
50
  ThumbnailComponent,
50
51
  TimeSincePipe,
@@ -1,6 +1,6 @@
1
1
  <figure class="text-center">
2
2
  <div
3
- class="w-12 h-12 border border-primary rounded-full capitalize"
3
+ class="w-10 h-10 border border-primary rounded-full capitalize"
4
4
  [matTooltip]="userFullName"
5
5
  >
6
6
  <gn-ui-avatar
@@ -51,7 +51,7 @@
51
51
  <div class="w-full h-full flex flex-col gap-2">
52
52
  <label
53
53
  gnUiFilesDrop
54
- class="block flex-1 border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center gap-4"
54
+ class="block flex-1 border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center gap-4 hover:cursor-pointer"
55
55
  (dragFilesOver)="handleDragFilesOver($event)"
56
56
  (dropFiles)="handleDropFiles($event)"
57
57
  >
@@ -16,6 +16,7 @@ import { ButtonComponent } from '../button/button.component'
16
16
  import { FilesDropDirective } from '../files-drop/files-drop.directive'
17
17
  import { TranslateModule } from '@ngx-translate/core'
18
18
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
19
+ import { FormControl, ReactiveFormsModule } from '@angular/forms'
19
20
 
20
21
  @Component({
21
22
  selector: 'gn-ui-image-input',
@@ -30,11 +31,13 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
30
31
  FilesDropDirective,
31
32
  MatProgressSpinnerModule,
32
33
  TranslateModule,
34
+ ReactiveFormsModule,
33
35
  ],
34
36
  })
35
37
  export class ImageInputComponent {
38
+ @Input() formControl!: FormControl
36
39
  @Input() maxSizeMB: number
37
- @Input() previewUrl?: string
40
+ @Input() previewUrl?: URL
38
41
  @Input() altText?: string
39
42
  @Input() uploadProgress?: number
40
43
  @Input() uploadError?: boolean
@@ -127,7 +130,8 @@ export class ImageInputComponent {
127
130
  const file = new File([blob], name)
128
131
  this.fileChange.emit(file)
129
132
  },
130
- error: () => {
133
+ error: (error) => {
134
+ console.error(error)
131
135
  this.downloadError = true
132
136
  this.cd.markForCheck()
133
137
  this.urlChange.emit(this.urlInputValue)
@@ -165,6 +169,7 @@ export class ImageInputComponent {
165
169
  }
166
170
 
167
171
  handleDelete() {
172
+ this.formControl.markAsDirty()
168
173
  this.delete.emit()
169
174
  }
170
175