geonetwork-ui 2.6.0-dev.600519f47 → 2.6.0-dev.609dd7257

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 (138) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.mjs +4 -1
  2. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/constant.mjs +4 -2
  3. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.mjs +2 -2
  4. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +45 -15
  5. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +7 -1
  6. package/esm2022/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.mjs +2 -1
  7. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  8. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  9. package/esm2022/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.mjs +4 -3
  10. package/esm2022/libs/feature/map/src/lib/add-layer-from-catalog/add-layer-from-catalog.component.mjs +1 -1
  11. package/esm2022/libs/feature/record/src/lib/map-view/map-view.component.mjs +49 -23
  12. package/esm2022/libs/feature/search/src/lib/constants.mjs +3 -2
  13. package/esm2022/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.mjs +5 -3
  14. package/esm2022/libs/feature/search/src/lib/results-table/results-table-container.component.mjs +13 -10
  15. package/esm2022/libs/ui/elements/src/index.mjs +1 -2
  16. package/esm2022/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.mjs +19 -34
  17. package/esm2022/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.mjs +59 -0
  18. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +35 -10
  19. package/esm2022/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.mjs +5 -1
  20. package/esm2022/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.mjs +50 -29
  21. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +7 -3
  22. package/esm2022/libs/ui/layout/src/index.mjs +2 -1
  23. package/esm2022/libs/ui/layout/src/lib/cell-popin/cell-popin.component.mjs +110 -0
  24. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table.component.mjs +20 -10
  25. package/esm2022/libs/ui/layout/src/lib/truncated-text/truncated-text.component.mjs +15 -49
  26. package/esm2022/libs/ui/layout/src/lib/ui-layout.module.mjs +2 -2
  27. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +5 -3
  28. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +16 -8
  29. package/esm2022/translations/de.json +20 -6
  30. package/esm2022/translations/en.json +20 -6
  31. package/esm2022/translations/es.json +19 -5
  32. package/esm2022/translations/fr.json +20 -6
  33. package/esm2022/translations/it.json +22 -8
  34. package/esm2022/translations/nl.json +19 -5
  35. package/esm2022/translations/pt.json +19 -5
  36. package/fesm2022/geonetwork-ui.mjs +1003 -711
  37. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  38. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  39. package/libs/api/repository/src/lib/gn4/elasticsearch/constant.d.ts.map +1 -1
  40. package/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.d.ts.map +1 -1
  41. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +8 -2
  42. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  43. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts +2 -0
  44. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  45. package/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.d.ts +1 -0
  46. package/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.d.ts.map +1 -1
  47. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +20 -17
  48. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  49. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +3 -0
  50. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  51. package/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.d.ts.map +1 -1
  52. package/libs/feature/record/src/lib/map-view/map-view.component.d.ts +13 -5
  53. package/libs/feature/record/src/lib/map-view/map-view.component.d.ts.map +1 -1
  54. package/libs/feature/search/src/lib/constants.d.ts.map +1 -1
  55. package/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.d.ts +2 -1
  56. package/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.d.ts.map +1 -1
  57. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +6 -5
  58. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  59. package/libs/ui/elements/src/index.d.ts +0 -1
  60. package/libs/ui/elements/src/index.d.ts.map +1 -1
  61. package/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.d.ts +3 -6
  62. package/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.d.ts.map +1 -1
  63. package/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.d.ts +14 -0
  64. package/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.d.ts.map +1 -0
  65. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts +3 -0
  66. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  67. package/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.d.ts.map +1 -1
  68. package/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.d.ts +20 -9
  69. package/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.d.ts.map +1 -1
  70. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +2 -1
  71. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  72. package/libs/ui/layout/src/index.d.ts +1 -0
  73. package/libs/ui/layout/src/index.d.ts.map +1 -1
  74. package/libs/ui/layout/src/lib/cell-popin/cell-popin.component.d.ts +28 -0
  75. package/libs/ui/layout/src/lib/cell-popin/cell-popin.component.d.ts.map +1 -0
  76. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts +4 -2
  77. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts.map +1 -1
  78. package/libs/ui/layout/src/lib/truncated-text/truncated-text.component.d.ts +5 -10
  79. package/libs/ui/layout/src/lib/truncated-text/truncated-text.component.d.ts.map +1 -1
  80. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts +2 -1
  81. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -1
  82. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +5 -3
  83. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  84. package/package.json +1 -1
  85. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +7 -0
  86. package/src/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts +3 -1
  87. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +1 -1
  88. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +57 -14
  89. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -0
  90. package/src/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.ts +3 -0
  91. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +20 -14
  92. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +3 -0
  93. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +61 -0
  94. package/src/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.html +1 -1
  95. package/src/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.ts +1 -0
  96. package/src/libs/feature/record/src/lib/map-view/map-view.component.html +27 -9
  97. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +84 -27
  98. package/src/libs/feature/search/src/lib/constants.ts +2 -1
  99. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html +1 -1
  100. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.ts +1 -0
  101. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +1 -0
  102. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +13 -3
  103. package/src/libs/ui/elements/src/index.ts +0 -1
  104. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +8 -76
  105. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +20 -51
  106. package/src/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.html +69 -0
  107. package/src/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.ts +61 -0
  108. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +48 -9
  109. package/src/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.ts +4 -0
  110. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +96 -37
  111. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +60 -29
  112. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -0
  113. package/src/libs/ui/layout/src/index.ts +1 -0
  114. package/src/libs/ui/layout/src/lib/cell-popin/cell-popin.component.html +40 -0
  115. package/src/libs/ui/layout/src/lib/cell-popin/cell-popin.component.ts +141 -0
  116. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +2 -2
  117. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +13 -6
  118. package/src/libs/ui/layout/src/lib/truncated-text/truncated-text.component.html +25 -42
  119. package/src/libs/ui/layout/src/lib/truncated-text/truncated-text.component.ts +10 -49
  120. package/src/libs/ui/layout/src/lib/ui-layout.module.ts +1 -1
  121. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +6 -4
  122. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +1 -0
  123. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +14 -20
  124. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +13 -5
  125. package/translations/de.json +20 -6
  126. package/translations/en.json +20 -6
  127. package/translations/es.json +19 -5
  128. package/translations/fr.json +20 -6
  129. package/translations/it.json +22 -8
  130. package/translations/nl.json +19 -5
  131. package/translations/pt.json +19 -5
  132. package/translations/sk.json +19 -5
  133. package/esm2022/libs/ui/elements/src/lib/related-record-card/related-record-card.component.mjs +0 -56
  134. package/libs/ui/elements/src/lib/related-record-card/related-record-card.component.d.ts +0 -19
  135. package/libs/ui/elements/src/lib/related-record-card/related-record-card.component.d.ts.map +0 -1
  136. package/src/libs/ui/elements/src/lib/related-record-card/related-record-card.component.css +0 -0
  137. package/src/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html +0 -9
  138. package/src/libs/ui/elements/src/lib/related-record-card/related-record-card.component.ts +0 -61
@@ -29,7 +29,10 @@ import {
29
29
  } from 'rxjs/operators'
30
30
  import { MdViewFacade } from '../state/mdview.facade'
31
31
  import { DataService } from '../../../../../../libs/feature/dataviz/src'
32
- import { DatasetOnlineResource } from '../../../../../../libs/common/domain/src/lib/model/record'
32
+ import {
33
+ DatasetOnlineResource,
34
+ DatasetServiceDistribution,
35
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
33
36
  import {
34
37
  createViewFromLayer,
35
38
  MapContext,
@@ -62,6 +65,7 @@ import { FetchError } from '../../../../../../libs/util/data-fetcher/src'
62
65
  marker('map.dropdown.placeholder')
63
66
  marker('wfs.feature.limit')
64
67
  marker('dataset.error.restrictedAccess')
68
+ marker('map.select.style')
65
69
 
66
70
  @Component({
67
71
  selector: 'gn-ui-map-view',
@@ -96,32 +100,32 @@ export class MapViewComponent implements AfterViewInit {
96
100
  selection: Feature
97
101
  showLegend = true
98
102
  legendExists = false
103
+ loading = false
104
+ error = null
105
+
106
+ selectLinkToDisplay(i: number) {
107
+ this.selectedLinkIndex$.next(i)
108
+ }
109
+
110
+ selectStyleToDisplay(i: number) {
111
+ this.selectedStyleIndex$.next(i)
112
+ }
99
113
 
100
114
  toggleLegend() {
101
115
  this.showLegend = !this.showLegend
102
116
  }
103
-
104
- onLegendStatusChange(status: boolean) {
105
- this.legendExists = status
117
+ onLegendStatusChange(v: boolean) {
118
+ this.legendExists = v
106
119
  }
107
120
 
108
121
  compatibleMapLinks$ = combineLatest([
109
122
  this.mdViewFacade.mapApiLinks$,
110
123
  this.mdViewFacade.geoDataLinksWithGeometry$,
111
124
  ]).pipe(
112
- switchMap(async ([mapApiLinks, geoDataLinksWithGeometry]) => {
113
- // looking for TMS links to process
114
- let processedMapApiLinks = await Promise.all(
115
- mapApiLinks.map((link) => {
116
- if (link.type === 'service' && link.accessServiceProtocol === 'tms') {
117
- return this.dataService.getGeodataLinksFromTms(link)
118
- }
119
- return link
120
- })
121
- )
122
- processedMapApiLinks = processedMapApiLinks.flat()
123
- return [...processedMapApiLinks, ...geoDataLinksWithGeometry]
124
- }),
125
+ map(([mapApiLinks, geoDataLinksWithGeometry]) => [
126
+ ...mapApiLinks,
127
+ ...geoDataLinksWithGeometry,
128
+ ]),
125
129
  shareReplay(1)
126
130
  )
127
131
 
@@ -135,15 +139,73 @@ export class MapViewComponent implements AfterViewInit {
135
139
  : [{ label: 'map.dropdown.placeholder', value: 0 }]
136
140
  )
137
141
  )
138
- selectedLinkIndex$ = new BehaviorSubject(0)
139
142
 
140
- loading = false
141
- error = null
143
+ private selectedLinkIndex$ = new BehaviorSubject(0)
144
+ private selectedStyleIndex$ = new BehaviorSubject(0)
142
145
 
143
- selectedLink$ = combineLatest([
146
+ selectedSourceLink$ = combineLatest([
144
147
  this.compatibleMapLinks$,
145
148
  this.selectedLinkIndex$.pipe(distinctUntilChanged()),
146
- ]).pipe(map(([links, index]) => links[index]))
149
+ ]).pipe(
150
+ map(([links, idx]) => links[idx]),
151
+ shareReplay(1)
152
+ )
153
+
154
+ styleLinks$ = this.selectedSourceLink$.pipe(
155
+ switchMap((src) => {
156
+ if (
157
+ src &&
158
+ src.type === 'service' &&
159
+ src.accessServiceProtocol === 'tms'
160
+ ) {
161
+ return from(
162
+ this.dataService.getGeodataLinksFromTms(
163
+ src as DatasetServiceDistribution,
164
+ false
165
+ )
166
+ ).pipe(
167
+ // We need to check for maplibre-style links because when a TMS service has no styles,
168
+ // getGeodataLinksFromTms returns the original TMS link, which isn't a maplibre-style link
169
+ map(
170
+ (links) =>
171
+ links?.filter(
172
+ (link) =>
173
+ link.type === 'service' &&
174
+ link.accessServiceProtocol === 'maplibre-style'
175
+ ) || []
176
+ )
177
+ )
178
+ }
179
+ return of([])
180
+ }),
181
+ tap(() => this.selectedStyleIndex$.next(0)),
182
+ shareReplay(1)
183
+ )
184
+
185
+ styleDropdownChoices$ = this.styleLinks$.pipe(
186
+ map((links) =>
187
+ links.length
188
+ ? links.map((link, index) => ({
189
+ label: getLinkLabel(link),
190
+ value: index,
191
+ }))
192
+ : [
193
+ {
194
+ label: '\u00A0\u00A0\u00A0\u00A0',
195
+ value: 0,
196
+ },
197
+ ]
198
+ )
199
+ )
200
+
201
+ selectedLink$ = combineLatest([
202
+ this.selectedSourceLink$,
203
+ this.styleLinks$,
204
+ this.selectedStyleIndex$.pipe(distinctUntilChanged()),
205
+ ]).pipe(
206
+ map(([src, styles, styleIdx]) => (styles.length ? styles[styleIdx] : src)),
207
+ shareReplay(1)
208
+ )
147
209
 
148
210
  currentLayers$ = combineLatest([this.selectedLink$, this.excludeWfs$]).pipe(
149
211
  switchMap(([link, excludeWfs]) => {
@@ -296,11 +358,6 @@ export class MapViewComponent implements AfterViewInit {
296
358
  }
297
359
  return throwError(() => 'protocol not supported')
298
360
  }
299
-
300
- selectLinkToDisplay(link: number) {
301
- this.selectedLinkIndex$.next(link)
302
- }
303
-
304
361
  handleError(error: FetchError | Error | string) {
305
362
  if (error instanceof FetchError) {
306
363
  this.error = this.translateService.instant(
@@ -20,9 +20,10 @@ export const FIELDS_SUMMARY: FieldName[] = [
20
20
  'userSavedCount',
21
21
  'cl_topic',
22
22
  'cl_maintenanceAndUpdateFrequency',
23
- 'MD_LegalConstraintsUseLimitationObject',
23
+ 'MD_LegalConstraints*Object',
24
24
  'qualityScore',
25
25
  'allKeywords',
26
+ 'recordLink',
26
27
  ]
27
28
 
28
29
  export const FIELDS_BRIEF: FieldName[] = [
@@ -1,5 +1,5 @@
1
1
  <gn-ui-autocomplete
2
- [placeholder]="'search.field.any.placeholder' | translate"
2
+ [placeholder]="placeholder ?? ('search.field.any.placeholder' | translate)"
3
3
  [displayWithFn]="displayWithFn"
4
4
  [action]="autoCompleteAction"
5
5
  (itemSelected)="handleItemSelection($event)"
@@ -30,6 +30,7 @@ export class FuzzySearchComponent implements OnInit {
30
30
  @Input() autoFocus = false
31
31
  @Input() forceTrackPosition = false
32
32
  @Input() enterButton = false
33
+ @Input() placeholder?: string
33
34
  @Output() itemSelected = new EventEmitter<CatalogRecord>()
34
35
  @Output() inputSubmitted = new EventEmitter<string>()
35
36
  @Output() isSearchActive = new EventEmitter<boolean>()
@@ -7,6 +7,7 @@
7
7
  [canDelete]="canDelete"
8
8
  [canDuplicate]="canDuplicate"
9
9
  [isDuplicating]="isDuplicating"
10
+ [canEdit]="canEdit"
10
11
  (recordClick)="handleRecordClick($event)"
11
12
  (duplicateRecord)="handleDuplicateRecord($event)"
12
13
  (deleteRecord)="handleDeleteRecord($event)"
@@ -12,7 +12,7 @@ import { SearchService } from '../utils/service/search.service'
12
12
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
13
13
  import { ResultsTableComponent } from '../../../../../../libs/ui/search/src'
14
14
  import { CommonModule } from '@angular/common'
15
- import { Subscription } from 'rxjs'
15
+ import { Observable, Subscription } from 'rxjs'
16
16
  import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
17
17
  import { TranslateService } from '@ngx-translate/core'
18
18
 
@@ -24,8 +24,6 @@ import { TranslateService } from '@ngx-translate/core'
24
24
  imports: [CommonModule, ResultsTableComponent],
25
25
  })
26
26
  export class ResultsTableContainerComponent implements OnDestroy {
27
- @Input() canDuplicate: (record: CatalogRecord) => boolean = () => true
28
- @Input() canDelete: (record: CatalogRecord) => boolean = () => true
29
27
  @Input() isDuplicating: false
30
28
 
31
29
  @Output() recordClick = new EventEmitter<CatalogRecord>()
@@ -39,6 +37,18 @@ export class ResultsTableContainerComponent implements OnDestroy {
39
37
  hasDraft = (record: CatalogRecord): boolean =>
40
38
  this.recordsRepository.recordHasDraft(record.uniqueIdentifier)
41
39
 
40
+ canDuplicate = (record: CatalogRecord): boolean => {
41
+ return this.recordsRepository.canDuplicate(record)
42
+ }
43
+
44
+ canDelete = (record: CatalogRecord): Observable<boolean> => {
45
+ return this.recordsRepository.canDelete(record)
46
+ }
47
+
48
+ canEdit = (record: CatalogRecord): Observable<boolean> => {
49
+ return this.recordsRepository.canEditIndexedRecord(record)
50
+ }
51
+
42
52
  constructor(
43
53
  protected searchFacade: SearchFacade,
44
54
  private searchService: SearchService,
@@ -19,7 +19,6 @@ export * from './lib/metadata-quality-item/metadata-quality-item.component'
19
19
  export * from './lib/metadata-quality/metadata-quality.component'
20
20
  export * from './lib/notification/notification.component'
21
21
  export * from './lib/record-api-form/record-api-form.component'
22
- export * from './lib/related-record-card/related-record-card.component'
23
22
  export * from './lib/thumbnail/thumbnail.component'
24
23
  export * from './lib/ui-elements.module'
25
24
  export * from './lib/user-feedback-item/user-feedback-item.component'
@@ -18,7 +18,7 @@
18
18
  [fit]="'cover'"
19
19
  ></gn-ui-thumbnail>
20
20
  </div>
21
- <div class="grow flex flex-col gap-3">
21
+ <div class="grow flex flex-col gap-3 w-0">
22
22
  <div class="flex items-center gap-2">
23
23
  <div class="grow flex flex-wrap items-center gap-2">
24
24
  <gn-ui-kind-badge
@@ -60,8 +60,9 @@
60
60
  </h4>
61
61
  </div>
62
62
  <div
63
- *ngIf="size === 'L'"
64
- class="mr-6 text-xs text-gray-900 line-clamp-2 overflow-hidden"
63
+ *ngIf="displayAbstract()"
64
+ class="mr-6 text-xs text-gray-900 overflow-hidden"
65
+ [ngClass]="getAbstractClass()"
65
66
  data-cy="recordAbstract"
66
67
  >
67
68
  <gn-ui-markdown-parser
@@ -71,80 +72,11 @@
71
72
  </div>
72
73
  </div>
73
74
 
74
- <div
75
- data-cy="recordOrg"
75
+ <gn-ui-internal-link-card-contact
76
76
  *ngIf="size !== 'XS' && record.ownerOrganization?.name"
77
- class="flex items-center justify-evenly bg-gray-50 rounded-lg h-[53px] px-2"
78
- >
79
- <div class="flex items-center flex-1 min-w-0">
80
- <div
81
- class="w-[45px] h-[45px] rounded-lg overflow-hidden shrink-0 mr-3"
82
- >
83
- <gn-ui-thumbnail
84
- [thumbnailUrl]="
85
- record.ownerOrganization?.logoUrl?.toString() || ''
86
- "
87
- [fit]="'contain'"
88
- class="w-full h-full rounded-lg"
89
- ></gn-ui-thumbnail>
90
- </div>
91
- <div *ngIf="organization?.name" class="flex-1 w-0 overflow-hidden">
92
- <div
93
- class="text-xs text-black font-normal leading-tight truncate"
94
- translate
95
- >
96
- record.card.metadata.contact
97
- </div>
98
- <div
99
- data-cy="recordOrgName"
100
- class="text-xl text-primary-black font-medium truncate"
101
- >
102
- {{ organization.name }}
103
- </div>
104
- </div>
105
- </div>
106
- <div *ngIf="size === 'L'" class="ml-2 flex space-x-2">
107
- <div *ngIf="organization?.website" class="flex">
108
- <button
109
- [title]="organization.website"
110
- class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest"
111
- (click)="openExternalUrl($event, organization.website)"
112
- >
113
- <ng-icon name="iconoirInternet"></ng-icon>
114
- </button>
115
- </div>
116
- <div *ngIf="contacts[0]?.email" class="flex">
117
- <button
118
- [title]="contacts[0].email"
119
- class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest"
120
- data-cy="contact-email"
121
- (click)="openMailto($event, contacts[0].email)"
122
- >
123
- <ng-icon name="matEmailOutline"></ng-icon>
124
- </button>
125
- </div>
126
- <div *ngIf="contacts[0]?.phone" class="flex">
127
- <button
128
- [title]="'Copy to clipboard'"
129
- class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest relative group"
130
- data-cy="contact-phone"
131
- (click)="copyToClipboard($event, contacts[0].phone)"
132
- >
133
- <ng-icon name="matPhoneOutline"></ng-icon>
134
- </button>
135
- </div>
136
- <div *ngIf="contacts[0]?.address" class="flex">
137
- <button
138
- [title]="'Copy to clipboard'"
139
- class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest relative group"
140
- data-cy="contact-phone"
141
- (click)="copyToClipboard($event, contacts[0].address)"
142
- >
143
- <ng-icon name="matLocationOnOutline"></ng-icon>
144
- </button>
145
- </div>
146
- </div>
147
- </div>
77
+ [record]="record"
78
+ [size]="size"
79
+ ></gn-ui-internal-link-card-contact>
148
80
  </div>
149
81
  </div>
150
82
  </div>
@@ -7,35 +7,20 @@ import {
7
7
  EventEmitter,
8
8
  ElementRef,
9
9
  } from '@angular/core'
10
- import {
11
- CatalogRecord,
12
- Organization,
13
- } from '../../../../../../libs/common/domain/src/lib/model/record'
10
+ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
14
11
  import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common'
15
12
  import { GeoDataBadgeComponent } from '../geo-data-badge/geo-data-badge.component'
16
13
  import { KindBadgeComponent } from '../kind-badge/kind-badge.component'
17
14
  import { MarkdownParserComponent } from '../markdown-parser/markdown-parser.component'
18
15
  import { MetadataQualityComponent } from '../metadata-quality/metadata-quality.component'
19
- import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
20
- import {
21
- propagateToDocumentOnly,
22
- removeWhitespace,
23
- stripHtml,
24
- } from '../../../../../../libs/util/shared/src'
25
- import {
26
- NgIconComponent,
27
- provideIcons,
28
- provideNgIconsConfig,
29
- } from '@ng-icons/core'
30
- import {
31
- matLocationSearchingOutline,
32
- matEmailOutline,
33
- matPhoneOutline,
34
- matLocationOnOutline,
35
- } from '@ng-icons/material-icons/outline'
16
+ import { InternalLinkCardContactComponent } from '../internal-link-card-contact/internal-link-card-contact.component'
17
+ import { removeWhitespace, stripHtml } from '../../../../../../libs/util/shared/src'
18
+ import { provideIcons, provideNgIconsConfig } from '@ng-icons/core'
19
+ import { matLocationSearchingOutline } from '@ng-icons/material-icons/outline'
36
20
  import { iconoirInternet } from '@ng-icons/iconoir'
37
21
  import { TranslateModule } from '@ngx-translate/core'
38
22
  import { fromEvent, Subscription } from 'rxjs'
23
+ import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
39
24
 
40
25
  type CardSize = 'L' | 'M' | 'S' | 'XS'
41
26
 
@@ -45,22 +30,19 @@ type CardSize = 'L' | 'M' | 'S' | 'XS'
45
30
  imports: [
46
31
  NgClass,
47
32
  NgIf,
48
- ThumbnailComponent,
49
33
  MetadataQualityComponent,
50
34
  NgTemplateOutlet,
51
- NgIconComponent,
52
35
  TranslateModule,
53
36
  GeoDataBadgeComponent,
54
37
  KindBadgeComponent,
55
38
  MarkdownParserComponent,
39
+ InternalLinkCardContactComponent,
40
+ ThumbnailComponent,
56
41
  ],
57
42
  providers: [
58
43
  provideIcons({
59
44
  iconoirInternet,
60
45
  matLocationSearchingOutline,
61
- matEmailOutline,
62
- matPhoneOutline,
63
- matLocationOnOutline,
64
46
  }),
65
47
  provideNgIconsConfig({
66
48
  size: '1.2em',
@@ -125,35 +107,22 @@ export class InternalLinkCardComponent implements OnInit {
125
107
  )
126
108
  }
127
109
 
128
- get organization(): Organization {
129
- return this.record.ownerOrganization
130
- }
131
-
132
- get contacts() {
133
- return (
134
- (this.record.kind === 'dataset'
135
- ? this.record.contactsForResource
136
- : this.record.contacts) || []
137
- )
138
- }
139
-
140
110
  getTitleClass() {
141
111
  return this.titleClassMap[this._size]
142
112
  }
143
-
144
- openExternalUrl(event: Event, url: URL): void {
145
- event.stopPropagation()
146
- window.open(url, '_blank')
147
- }
148
-
149
- openMailto(event: Event, email: string): void {
150
- event.stopPropagation()
151
- window.open(`mailto:${email}`, '_blank')
113
+ getAbstractClass(): string {
114
+ const marginClass = ['S', 'XS'].includes(this.size) ? 'ml-2' : ''
115
+ const clampClass =
116
+ this.size === 'L' && !this.record.ownerOrganization?.name
117
+ ? 'line-clamp-6'
118
+ : 'line-clamp-2'
119
+ return `${clampClass} ${marginClass}`.trim()
152
120
  }
153
-
154
- copyToClipboard(event: Event, text: string): void {
155
- event.stopPropagation()
156
- navigator.clipboard.writeText(text)
121
+ displayAbstract(): boolean {
122
+ return (
123
+ this.size === 'L' ||
124
+ (['M', 'S'].includes(this.size) && !this.record.ownerOrganization?.name)
125
+ )
157
126
  }
158
127
 
159
128
  get shouldShowThumbnail(): boolean {
@@ -0,0 +1,69 @@
1
+ <div
2
+ data-cy="recordOrg"
3
+ class="flex items-center justify-evenly bg-gray-50 rounded-lg h-[53px] px-2"
4
+ >
5
+ <div class="flex items-center flex-1 min-w-0">
6
+ <div class="w-[45px] h-[45px] rounded-lg overflow-hidden shrink-0 mr-3">
7
+ <gn-ui-thumbnail
8
+ [thumbnailUrl]="record.ownerOrganization?.logoUrl?.toString() || ''"
9
+ [fit]="'contain'"
10
+ class="w-full h-full rounded-lg"
11
+ ></gn-ui-thumbnail>
12
+ </div>
13
+ <div *ngIf="organization?.name" class="flex-1 w-0 overflow-hidden">
14
+ <div
15
+ class="text-xs text-black font-normal leading-tight truncate"
16
+ translate
17
+ >
18
+ record.card.metadata.contact
19
+ </div>
20
+ <div
21
+ data-cy="recordOrgName"
22
+ class="text-xl text-primary-black font-medium truncate"
23
+ >
24
+ {{ organization.name }}
25
+ </div>
26
+ </div>
27
+ </div>
28
+ <div *ngIf="size === 'L'" class="ml-2 flex space-x-2">
29
+ <div *ngIf="organization?.website" class="flex">
30
+ <button
31
+ [title]="organization.website"
32
+ class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest"
33
+ (click)="openExternalUrl($event, organization.website)"
34
+ >
35
+ <ng-icon name="iconoirInternet"></ng-icon>
36
+ </button>
37
+ </div>
38
+ <div *ngIf="contacts[0]?.email" class="flex">
39
+ <button
40
+ [title]="contacts[0].email"
41
+ class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest"
42
+ data-cy="contact-email"
43
+ (click)="openMailto($event, contacts[0].email)"
44
+ >
45
+ <ng-icon name="matEmailOutline"></ng-icon>
46
+ </button>
47
+ </div>
48
+ <div *ngIf="contacts[0]?.phone" class="flex">
49
+ <button
50
+ [title]="'Copy to clipboard'"
51
+ class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest relative group"
52
+ data-cy="contact-phone"
53
+ (click)="copyToClipboard($event, contacts[0].phone)"
54
+ >
55
+ <ng-icon name="matPhoneOutline"></ng-icon>
56
+ </button>
57
+ </div>
58
+ <div *ngIf="contacts[0]?.address" class="flex">
59
+ <button
60
+ [title]="'Copy to clipboard'"
61
+ class="w-[40px] h-[32px] flex items-center justify-center rounded-lg border border-[#D4D3D7] px-[8px] py-[4px] hover:bg-primary-lightest relative group"
62
+ data-cy="contact-phone"
63
+ (click)="copyToClipboard($event, contacts[0].address)"
64
+ >
65
+ <ng-icon name="matLocationOnOutline"></ng-icon>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </div>
@@ -0,0 +1,61 @@
1
+ import { Component, Input } from '@angular/core'
2
+ import { NgIf } from '@angular/common'
3
+ import {
4
+ CatalogRecord,
5
+ Organization,
6
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
7
+ import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
8
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
9
+ import {
10
+ matEmailOutline,
11
+ matPhoneOutline,
12
+ matLocationOnOutline,
13
+ } from '@ng-icons/material-icons/outline'
14
+ import { iconoirInternet } from '@ng-icons/iconoir'
15
+ import { TranslateModule } from '@ngx-translate/core'
16
+
17
+ @Component({
18
+ selector: 'gn-ui-internal-link-card-contact',
19
+ standalone: true,
20
+ imports: [NgIf, ThumbnailComponent, NgIconComponent, TranslateModule],
21
+ providers: [
22
+ provideIcons({
23
+ iconoirInternet,
24
+ matEmailOutline,
25
+ matPhoneOutline,
26
+ matLocationOnOutline,
27
+ }),
28
+ ],
29
+ templateUrl: './internal-link-card-contact.component.html',
30
+ })
31
+ export class InternalLinkCardContactComponent {
32
+ @Input() record: CatalogRecord
33
+ @Input() size: 'L' | 'M' | 'S' | 'XS' = 'M'
34
+
35
+ get organization(): Organization {
36
+ return this.record.ownerOrganization
37
+ }
38
+
39
+ get contacts() {
40
+ return (
41
+ (this.record.kind === 'dataset'
42
+ ? this.record.contactsForResource
43
+ : this.record.contacts) || []
44
+ )
45
+ }
46
+
47
+ openExternalUrl(event: Event, url: URL): void {
48
+ event.stopPropagation()
49
+ window.open(url, '_blank')
50
+ }
51
+
52
+ openMailto(event: Event, email: string): void {
53
+ event.stopPropagation()
54
+ window.open(`mailto:${email}`, '_blank')
55
+ }
56
+
57
+ copyToClipboard(event: Event, text: string): void {
58
+ event.stopPropagation()
59
+ navigator.clipboard.writeText(text)
60
+ }
61
+ }
@@ -20,6 +20,10 @@ import { NgIcon } from '@ng-icons/core'
20
20
  import { matInfoOutline } from '@ng-icons/material-icons/outline'
21
21
  import { provideIcons, provideNgIconsConfig } from '@ng-icons/core'
22
22
 
23
+ type QualityChecks = {
24
+ [key: string]: (metadata: Partial<CatalogRecord>) => boolean
25
+ }
26
+
23
27
  @Component({
24
28
  selector: 'gn-ui-metadata-quality',
25
29
  templateUrl: './metadata-quality.component.html',
@@ -69,18 +73,53 @@ export class MetadataQualityComponent implements OnChanges {
69
73
  this.items.push({ name, value })
70
74
  }
71
75
  }
76
+ hasGetCapabilities(url: string): boolean {
77
+ return url.toLowerCase().includes('capabilities')
78
+ }
79
+
80
+ private readonly COMMON_CHECKS: QualityChecks = {
81
+ title: (metadata) => !!metadata?.title,
82
+ description: (metadata) => !!metadata?.abstract,
83
+ keywords: (metadata) => (metadata?.keywords?.length ?? 0) > 0,
84
+ legalConstraints: (metadata) =>
85
+ (metadata?.legalConstraints?.length ?? 0) > 0,
86
+ contact: (metadata) => !!metadata?.contacts?.[0]?.email,
87
+ }
88
+
89
+ private readonly SPECIFIC_CHECKS: Record<string, QualityChecks> = {
90
+ dataset: {
91
+ updateFrequency: (metadata) => !!metadata?.updateFrequency,
92
+ topic: (metadata) => (metadata?.topics?.length ?? 0) > 0,
93
+ organisation: (metadata) => !!metadata?.contacts?.[0]?.organization?.name,
94
+ },
95
+ service: {
96
+ capabilities: (metadata) =>
97
+ (metadata?.onlineResources ?? []).some((resource) =>
98
+ this.hasGetCapabilities(resource?.url?.href ?? '')
99
+ ),
100
+ },
101
+ reuse: {
102
+ topic: (metadata) => (metadata?.topics?.length ?? 0) > 0,
103
+ organisation: (metadata) => !!metadata?.contacts?.[0]?.organization?.name,
104
+ source: (metadata) => !!metadata?.extras?.sourcesIdentifiers,
105
+ },
106
+ }
72
107
 
73
108
  initialize() {
74
- const contact = this.metadata?.contacts?.[0]
75
109
  this.items = []
76
- this.add('title', !!this.metadata?.title)
77
- this.add('description', !!this.metadata?.abstract)
78
- this.add('topic', this.metadata?.topics?.length > 0)
79
- this.add('keywords', this.metadata?.keywords?.length > 0)
80
- this.add('legalConstraints', this.metadata?.legalConstraints?.length > 0)
81
- this.add('organisation', !!contact?.organization)
82
- this.add('contact', !!contact?.email)
83
- this.add('updateFrequency', !!this.metadata?.updateFrequency)
110
+
111
+ Object.entries(this.COMMON_CHECKS).forEach(([name, check]) => {
112
+ this.add(name, check(this.metadata))
113
+ })
114
+
115
+ const datasetType = this.metadata?.kind
116
+ if (datasetType && this.SPECIFIC_CHECKS[datasetType]) {
117
+ Object.entries(this.SPECIFIC_CHECKS[datasetType]).forEach(
118
+ ([name, check]) => {
119
+ this.add(name, check(this.metadata))
120
+ }
121
+ )
122
+ }
84
123
  }
85
124
 
86
125
  ngOnChanges(changes: SimpleChanges): void {