geonetwork-ui 2.6.0-dev.600519f47 → 2.6.0-dev.6e0b3d33d

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 (101) 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/gn4-repository.mjs +45 -15
  3. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +7 -1
  4. package/esm2022/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.mjs +2 -1
  5. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  6. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  7. package/esm2022/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.mjs +4 -3
  8. package/esm2022/libs/feature/search/src/lib/results-table/results-table-container.component.mjs +13 -10
  9. package/esm2022/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.mjs +19 -34
  10. package/esm2022/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.mjs +59 -0
  11. package/esm2022/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.mjs +50 -29
  12. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +7 -3
  13. package/esm2022/libs/ui/layout/src/index.mjs +2 -1
  14. package/esm2022/libs/ui/layout/src/lib/cell-popin/cell-popin.component.mjs +110 -0
  15. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table.component.mjs +20 -10
  16. package/esm2022/libs/ui/layout/src/lib/truncated-text/truncated-text.component.mjs +15 -49
  17. package/esm2022/libs/ui/layout/src/lib/ui-layout.module.mjs +2 -2
  18. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +5 -3
  19. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +16 -8
  20. package/esm2022/translations/de.json +15 -6
  21. package/esm2022/translations/en.json +15 -6
  22. package/esm2022/translations/es.json +14 -5
  23. package/esm2022/translations/fr.json +15 -6
  24. package/esm2022/translations/it.json +14 -5
  25. package/esm2022/translations/nl.json +14 -5
  26. package/esm2022/translations/pt.json +14 -5
  27. package/fesm2022/geonetwork-ui.mjs +867 -624
  28. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  29. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  30. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +8 -2
  31. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  32. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts +2 -0
  33. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  34. package/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.d.ts +1 -0
  35. package/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.d.ts.map +1 -1
  36. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +20 -17
  37. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  38. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +3 -0
  39. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  40. package/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.d.ts.map +1 -1
  41. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +6 -5
  42. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  43. package/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.d.ts +3 -6
  44. package/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.d.ts.map +1 -1
  45. package/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.d.ts +14 -0
  46. package/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.d.ts.map +1 -0
  47. package/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.d.ts +20 -9
  48. package/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.d.ts.map +1 -1
  49. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +2 -1
  50. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  51. package/libs/ui/layout/src/index.d.ts +1 -0
  52. package/libs/ui/layout/src/index.d.ts.map +1 -1
  53. package/libs/ui/layout/src/lib/cell-popin/cell-popin.component.d.ts +28 -0
  54. package/libs/ui/layout/src/lib/cell-popin/cell-popin.component.d.ts.map +1 -0
  55. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts +4 -2
  56. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts.map +1 -1
  57. package/libs/ui/layout/src/lib/truncated-text/truncated-text.component.d.ts +5 -10
  58. package/libs/ui/layout/src/lib/truncated-text/truncated-text.component.d.ts.map +1 -1
  59. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts +2 -1
  60. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -1
  61. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +5 -3
  62. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +7 -0
  65. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +57 -14
  66. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -0
  67. package/src/libs/api/repository/src/lib/gn4/settings/gn4-settings.service.ts +3 -0
  68. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +20 -14
  69. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +3 -0
  70. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +63 -0
  71. package/src/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.html +1 -1
  72. package/src/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.ts +1 -0
  73. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +1 -0
  74. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +13 -3
  75. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +7 -75
  76. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +20 -51
  77. package/src/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.html +69 -0
  78. package/src/libs/ui/elements/src/lib/internal-link-card-contact/internal-link-card-contact.component.ts +61 -0
  79. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +96 -37
  80. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +60 -29
  81. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -0
  82. package/src/libs/ui/layout/src/index.ts +1 -0
  83. package/src/libs/ui/layout/src/lib/cell-popin/cell-popin.component.html +40 -0
  84. package/src/libs/ui/layout/src/lib/cell-popin/cell-popin.component.ts +141 -0
  85. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +2 -2
  86. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +13 -6
  87. package/src/libs/ui/layout/src/lib/truncated-text/truncated-text.component.html +25 -42
  88. package/src/libs/ui/layout/src/lib/truncated-text/truncated-text.component.ts +10 -49
  89. package/src/libs/ui/layout/src/lib/ui-layout.module.ts +1 -1
  90. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +6 -4
  91. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +1 -0
  92. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +14 -20
  93. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +13 -5
  94. package/translations/de.json +15 -6
  95. package/translations/en.json +15 -6
  96. package/translations/es.json +14 -5
  97. package/translations/fr.json +15 -6
  98. package/translations/it.json +14 -5
  99. package/translations/nl.json +14 -5
  100. package/translations/pt.json +14 -5
  101. package/translations/sk.json +14 -5
@@ -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
+ }
@@ -1,48 +1,107 @@
1
- <div class="flex flex-col gap-2 py-5 px-5 h-[562px] overflow-y-auto">
2
- <div
3
- *ngFor="let featureType of filteredFeatureCatalog?.featureTypes"
4
- class="rounded shadow bg-white"
5
- >
6
- <gn-ui-expandable-panel
7
- [collapsed]="filteredFeatureCatalog?.featureTypes?.length !== 1"
8
- iconColor="black"
1
+ <div
2
+ cdkScrollable
3
+ class="h-[562px] relative overflow-y-auto overflow-hidden"
4
+ data-cy="feat-catalog-scrollable"
5
+ >
6
+ <div #scrollContainer class="flex flex-col gap-2 py-5 px-5">
7
+ <div
8
+ *ngFor="let featureType of filteredFeatureCatalog?.featureTypes"
9
+ class="rounded shadow bg-white"
9
10
  >
10
- <ng-template #titleTemplate>
11
- <div class="px-2">
12
- <div class="text-lg font-bold">{{ featureType.name }}</div>
13
- <div class="text-sm" *ngIf="featureType.definition">
14
- {{ featureType.definition }}
15
- </div>
16
- </div>
17
- </ng-template>
18
- <div
19
- class="grid gap-0"
20
- [style.grid-template-columns]="gridTemplateColumns"
21
- data-cy="feature-type-content"
11
+ <gn-ui-expandable-panel
12
+ #expanel
13
+ [collapsed]="filteredFeatureCatalog?.featureTypes?.length !== 1"
14
+ iconColor="black"
22
15
  >
16
+ <ng-template #titleTemplate>
17
+ <div class="px-2">
18
+ <div class="text-lg font-bold">{{ featureType.name }}</div>
19
+ <div class="text-sm" *ngIf="featureType.definition">
20
+ {{ featureType.definition }}
21
+ </div>
22
+ </div>
23
+ </ng-template>
23
24
  <div
24
- class="py-1 px-2 text-sm font-bold text-left border-t"
25
- [class.border-l]="i > 0"
26
- [class.border-gray-300]="i > 0"
27
- *ngFor="let col of columns; let i = index"
28
- data-test="column-label"
25
+ *ngIf="expanel && !expanel.collapsed"
26
+ class="grid gap-0"
27
+ [style.grid-template-columns]="
28
+ getGridTemplateColumns(featureType.attributes)
29
+ "
30
+ data-cy="feature-type-content"
29
31
  >
30
- {{ col.label | translate }}
31
- </div>
32
- <ng-container *ngFor="let row of featureType.attributes">
33
32
  <div
34
- class="bg-white text-sm font-normal text-left border-t"
33
+ class="py-1 px-2 text-sm font-bold text-left border-t break-all"
35
34
  [class.border-l]="i > 0"
36
35
  [class.border-gray-300]="i > 0"
37
- *ngFor="let col of columns; let i = index"
36
+ [ngClass]="col.class || ''"
37
+ *ngFor="
38
+ let col of getColumnsDefinition(featureType.attributes);
39
+ trackBy: trackByColumn;
40
+ let i = index
41
+ "
42
+ data-test="column-label"
38
43
  >
39
- <gn-ui-truncated-text
40
- extraClass="py-3 px-2"
41
- [text]="row[col.key]"
42
- ></gn-ui-truncated-text>
44
+ {{ 'feature.catalog.attribute.' + col.key | translate }}
43
45
  </div>
44
- </ng-container>
45
- </div>
46
- </gn-ui-expandable-panel>
46
+ <ng-container *ngIf="expanel && !expanel.collapsed">
47
+ <ng-container *ngFor="let row of featureType.attributes">
48
+ <div
49
+ class="bg-white text-sm font-normal text-left border-t"
50
+ [class.border-l]="i > 0"
51
+ [class.border-gray-300]="i > 0"
52
+ *ngFor="
53
+ let col of getColumnsDefinition(featureType.attributes);
54
+ trackBy: trackByColumn;
55
+ let i = index
56
+ "
57
+ data-test="cell-content"
58
+ >
59
+ <ng-container
60
+ *ngIf="col.key === 'values'; then tplValues; else defaultTpl"
61
+ ></ng-container>
62
+
63
+ <ng-template #defaultTpl>
64
+ <gn-ui-truncated-text
65
+ [scrollContainer]="scrollContainer"
66
+ [cdkScrollContainer]="scrollable"
67
+ [text]="row[col.key]"
68
+ ></gn-ui-truncated-text>
69
+ </ng-template>
70
+
71
+ <ng-template #tplValues>
72
+ <gn-ui-cell-popin
73
+ #popinRef
74
+ [scrollContainer]="scrollContainer"
75
+ [cdkScrollContainer]="scrollable"
76
+ *ngIf="row.values?.length > 0"
77
+ >
78
+ <div cellContent data-cy="values-cell-popin-trigger">
79
+ <gn-ui-button
80
+ type="light"
81
+ extraClass="bg-transparent border-none"
82
+ (buttonClick)="popinRef.openOverlay()"
83
+ >
84
+ <ng-icon name="iconoirList" size="24"></ng-icon>
85
+ </gn-ui-button>
86
+ </div>
87
+ <div
88
+ popinContent
89
+ class="max-h-60 overflow-y-auto min-w-64 py-4 px-6"
90
+ style="scrollbar-width: thin"
91
+ >
92
+ <ul class="list-disc list-inside mr-4">
93
+ <li *ngFor="let v of row.values">
94
+ {{ v.label || v.code }}
95
+ </li>
96
+ </ul>
97
+ </div>
98
+ </gn-ui-cell-popin>
99
+ </ng-template>
100
+ </div>
101
+ </ng-container>
102
+ </ng-container>
103
+ </div>
104
+ </gn-ui-expandable-panel>
105
+ </div>
47
106
  </div>
48
107
  </div>
@@ -1,52 +1,83 @@
1
- import { Component, Input, OnInit } from '@angular/core'
1
+ import { Component, ElementRef, Input, ViewChild } from '@angular/core'
2
2
  import { CommonModule } from '@angular/common'
3
3
  import { TranslateModule } from '@ngx-translate/core'
4
- import { DatasetFeatureCatalog } from '../../../../../../../libs/common/domain/src/lib/model/record'
4
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
5
5
  import {
6
+ DatasetFeatureAttribute,
7
+ DatasetFeatureCatalog,
8
+ } from '../../../../../../../libs/common/domain/src/lib/model/record'
9
+ import { ButtonComponent } from '../../../../../../../libs/ui/inputs/src'
10
+ import {
11
+ CellPopinComponent,
6
12
  ExpandablePanelComponent,
7
13
  TruncatedTextComponent,
8
14
  } from '../../../../../../../libs/ui/layout/src'
15
+ import { CdkScrollable, ScrollingModule } from '@angular/cdk/scrolling'
16
+ import { provideIcons, NgIconComponent } from '@ng-icons/core'
17
+ import { iconoirList } from '@ng-icons/iconoir'
18
+
19
+ marker('feature.catalog.attribute.type')
20
+ marker('feature.catalog.attribute.name')
21
+ marker('feature.catalog.attribute.code')
22
+ marker('feature.catalog.attribute.definition')
23
+ marker('feature.catalog.attribute.values')
24
+
25
+ interface ColumnDefinition {
26
+ key: string
27
+ width: string
28
+ class?: string
29
+ }
9
30
 
10
31
  @Component({
11
32
  selector: 'gn-ui-feature-catalog-list',
12
33
  templateUrl: './feature-catalog-list.component.html',
34
+ styleUrls: [],
13
35
  standalone: true,
14
36
  imports: [
37
+ ButtonComponent,
38
+ CellPopinComponent,
15
39
  CommonModule,
16
40
  TranslateModule,
17
41
  ExpandablePanelComponent,
18
42
  TruncatedTextComponent,
43
+ NgIconComponent,
44
+ ScrollingModule,
19
45
  ],
46
+ providers: [provideIcons({ iconoirList })],
20
47
  })
21
- export class FeatureCatalogListComponent implements OnInit {
22
- @Input() filteredFeatureCatalog: DatasetFeatureCatalog
23
-
24
- columns = [
25
- {
26
- key: 'type',
27
- label: 'feature.catalog.attribute.type',
28
- width: '19%',
29
- },
30
- {
31
- key: 'name',
32
- label: 'feature.catalog.attribute.name',
33
- width: '32%',
34
- },
35
- {
36
- key: 'code',
37
- label: 'feature.catalog.attribute.code',
38
- width: '24%',
39
- },
40
- {
41
- key: 'title',
42
- label: 'feature.catalog.attribute.description',
43
- width: '25%',
44
- },
48
+ export class FeatureCatalogListComponent {
49
+ @Input() filteredFeatureCatalog!: DatasetFeatureCatalog
50
+
51
+ @ViewChild('scrollContainer') scrollContainer!: ElementRef
52
+ @ViewChild(CdkScrollable, { static: true }) scrollable!: CdkScrollable
53
+ @ViewChild('expanel', { read: ExpandablePanelComponent, static: true })
54
+ panelComponent: ExpandablePanelComponent
55
+
56
+ readonly COLUMNS_DEFAULT: ColumnDefinition[] = [
57
+ { key: 'type', width: '17%' },
58
+ { key: 'name', width: '32%' },
59
+ { key: 'code', width: '17%' },
60
+ { key: 'definition', width: 'minmax(0px, 1fr)' },
45
61
  ]
46
62
 
47
- gridTemplateColumns = ''
63
+ readonly COLUMN_VALUES: ColumnDefinition = {
64
+ key: 'values',
65
+ width: '73px',
66
+ class: 'text-center',
67
+ }
68
+
69
+ getColumnsDefinition(attrs: DatasetFeatureAttribute[]): ColumnDefinition[] {
70
+ const hasValues = attrs.some((a) => a.values?.length > 0)
71
+ return [...this.COLUMNS_DEFAULT, ...(hasValues ? [this.COLUMN_VALUES] : [])]
72
+ }
73
+
74
+ getGridTemplateColumns(attributes: DatasetFeatureAttribute[]) {
75
+ return this.getColumnsDefinition(attributes)
76
+ .map((col) => col.width)
77
+ .join(' ')
78
+ }
48
79
 
49
- ngOnInit(): void {
50
- this.gridTemplateColumns = this.columns.map((col) => col.width).join(' ')
80
+ trackByColumn(_i: number, col: { key: string }) {
81
+ return col.key
51
82
  }
52
83
  }
@@ -24,6 +24,7 @@ import { UserPreviewComponent } from './user-preview/user-preview.component'
24
24
  import { ApplicationBannerComponent } from './application-banner/application-banner.component'
25
25
  import { InternalLinkCardComponent } from './internal-link-card/internal-link-card.component'
26
26
  import { ServiceCapabilitiesComponent } from './service-capabilities/service-capabilities.component'
27
+ import { ScrollingModule } from '@angular/cdk/scrolling'
27
28
  @NgModule({
28
29
  imports: [
29
30
  CommonModule,
@@ -49,6 +50,7 @@ import { ServiceCapabilitiesComponent } from './service-capabilities/service-cap
49
50
  ServiceCapabilitiesComponent,
50
51
  KindBadgeComponent,
51
52
  GeoDataBadgeComponent,
53
+ ScrollingModule,
52
54
  ],
53
55
  providers: [
54
56
  provideNgIconsConfig({
@@ -1,5 +1,6 @@
1
1
  export * from './lib/anchor-link/anchor-link.directive'
2
2
  export * from './lib/carousel/carousel.component'
3
+ export * from './lib/cell-popin/cell-popin.component'
3
4
  export * from './lib/expandable-panel-button/expandable-panel-button.component'
4
5
  export * from './lib/expandable-panel/expandable-panel.component'
5
6
  export * from './lib/form-field-wrapper/form-field-wrapper.component'
@@ -0,0 +1,40 @@
1
+ <div
2
+ class="h-full flex items-center justify-center"
3
+ cdkOverlayOrigin
4
+ #anchorRef
5
+ #trigger="cdkOverlayOrigin"
6
+ [class]="extraClass"
7
+ data-cy="cell-popin"
8
+ >
9
+ <ng-content select="[cellContent]"></ng-content>
10
+ <ng-template
11
+ *ngIf="activePopin"
12
+ cdkConnectedOverlay
13
+ [cdkConnectedOverlayPush]="false"
14
+ [cdkConnectedOverlayOrigin]="trigger"
15
+ [cdkConnectedOverlayOpen]="isOpen"
16
+ [cdkConnectedOverlayPositions]="overlayPositions"
17
+ [cdkConnectedOverlayWidth]="'auto'"
18
+ [cdkConnectedOverlayFlexibleDimensions]="true"
19
+ [cdkConnectedOverlayGrowAfterOpen]="true"
20
+ (detach)="closeOverlay()"
21
+ >
22
+ <div
23
+ *ngIf="activePopin && isVisible"
24
+ class="bg-white shadow-lg border border-gray-300 relative"
25
+ data-cy="cell-popin-content"
26
+ >
27
+ <ng-content select="[popinContent]"></ng-content>
28
+ <gn-ui-button
29
+ data-cy="cell-popin-close"
30
+ [style.--gn-ui-button-background]="'bg-transparent'"
31
+ class="absolute top-2 right-2"
32
+ type="light"
33
+ (buttonClick)="closeOverlay()"
34
+ extraClass="w-10 h-8 px-1"
35
+ >
36
+ <ng-icon name="iconoirReduce" size="24"></ng-icon>
37
+ </gn-ui-button>
38
+ </div>
39
+ </ng-template>
40
+ </div>
@@ -0,0 +1,141 @@
1
+ import {
2
+ AfterViewInit,
3
+ Component,
4
+ Input,
5
+ OnDestroy,
6
+ ElementRef,
7
+ TemplateRef,
8
+ ViewChild,
9
+ Injector,
10
+ ChangeDetectorRef,
11
+ } from '@angular/core'
12
+ import { CommonModule } from '@angular/common'
13
+ import { provideIcons, NgIconComponent } from '@ng-icons/core'
14
+ import { iconoirReduce } from '@ng-icons/iconoir'
15
+ import { MatButtonModule } from '@angular/material/button'
16
+ import {
17
+ OverlayModule,
18
+ ConnectedPosition,
19
+ ScrollDispatcher,
20
+ CdkScrollable,
21
+ CdkConnectedOverlay,
22
+ OverlayContainer,
23
+ } from '@angular/cdk/overlay'
24
+ import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
25
+ @Component({
26
+ selector: 'gn-ui-cell-popin',
27
+ standalone: true,
28
+ imports: [
29
+ CommonModule,
30
+ MatButtonModule,
31
+ OverlayModule,
32
+ ButtonComponent,
33
+ NgIconComponent,
34
+ ],
35
+ providers: [provideIcons({ iconoirReduce })],
36
+ templateUrl: './cell-popin.component.html',
37
+ styleUrls: [],
38
+ })
39
+ export class CellPopinComponent implements AfterViewInit, OnDestroy {
40
+ @Input() extraClass = ''
41
+ @Input() cdkScrollContainer!: CdkScrollable
42
+ @Input() scrollContainer!: ElementRef
43
+
44
+ @ViewChild('anchorRef') anchorRef!: ElementRef
45
+
46
+ protected isOpen = false
47
+ protected isVisible = true
48
+ protected overlayPositions: ConnectedPosition[] = [
49
+ {
50
+ originX: 'end',
51
+ originY: 'top',
52
+ overlayX: 'end',
53
+ overlayY: 'top',
54
+ },
55
+ {
56
+ originX: 'start',
57
+ originY: 'top',
58
+ overlayX: 'start',
59
+ overlayY: 'top',
60
+ },
61
+ ]
62
+
63
+ private firstCheck = true
64
+ private intersectionObserver: IntersectionObserver
65
+
66
+ private _activePopin = true
67
+
68
+ @Input()
69
+ set activePopin(active: boolean | undefined) {
70
+ this._activePopin = !!active
71
+ this.updateIntersectionObserver()
72
+ }
73
+ get activePopin(): boolean {
74
+ return this._activePopin
75
+ }
76
+
77
+ constructor(
78
+ private scrollDispatcher: ScrollDispatcher,
79
+ private cdr: ChangeDetectorRef
80
+ ) {}
81
+
82
+ ngAfterViewInit() {
83
+ if (!this.activePopin) {
84
+ return
85
+ }
86
+
87
+ if (this.cdkScrollContainer) {
88
+ this.scrollDispatcher.register(this.cdkScrollContainer)
89
+ }
90
+
91
+ this.updateIntersectionObserver()
92
+ }
93
+
94
+ ngOnDestroy() {
95
+ this.intersectionObserver?.disconnect()
96
+ this.scrollDispatcher.deregister(this.cdkScrollContainer)
97
+ }
98
+
99
+ updateIntersectionObserver() {
100
+ if (!this.scrollContainer || !this.anchorRef) {
101
+ return
102
+ }
103
+
104
+ if (this.intersectionObserver) {
105
+ this.intersectionObserver?.disconnect()
106
+ }
107
+
108
+ this.intersectionObserver = new IntersectionObserver(
109
+ ([entry]) => {
110
+ const visible = entry.intersectionRatio >= 1
111
+
112
+ // Ignore first visibility if first time opening it
113
+ if (this.firstCheck && !visible) {
114
+ this.firstCheck = false
115
+ return
116
+ }
117
+
118
+ this.firstCheck = false
119
+
120
+ if (this.isVisible !== visible) {
121
+ this.isVisible = visible
122
+ this.cdr.detectChanges()
123
+ }
124
+ },
125
+ {
126
+ root: this.scrollContainer.nativeElement,
127
+ threshold: 1.0,
128
+ }
129
+ )
130
+
131
+ this.intersectionObserver.observe(this.anchorRef.nativeElement)
132
+ }
133
+
134
+ openOverlay() {
135
+ this.isOpen = true
136
+ }
137
+
138
+ closeOverlay() {
139
+ this.isOpen = false
140
+ }
141
+ }
@@ -49,9 +49,9 @@
49
49
  class="table-row-cell px-3 py-1.5 flex items-center bg-white transition-colors duration-75 truncate border-b border-gray-200"
50
50
  [ngClass]="{
51
51
  'text-purple-light cursor-default':
52
- (!item.extras?.edit && !isDraftPage) || item.kind !== 'dataset',
52
+ (canEditItem(item) | async) === false,
53
53
  'group-hover:text-main group-hover:bg-gray-50':
54
- (item.extras?.edit || isDraftPage) && item.kind === 'dataset',
54
+ canEditItem(item) | async,
55
55
  }"
56
56
  >
57
57
  <ng-container
@@ -14,9 +14,11 @@ import { iconoirNavArrowDown, iconoirNavArrowUp } from '@ng-icons/iconoir'
14
14
  import { TranslateModule } from '@ngx-translate/core'
15
15
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
16
16
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
17
+ import { Observable, of } from 'rxjs'
17
18
 
18
- marker('editor.record.lock.reason')
19
- marker('editor.record.lock.format')
19
+ marker('editor.record.lock.resourceType')
20
+ marker('editor.record.lock.harvested')
21
+ marker('editor.record.lock.owner')
20
22
  @Component({
21
23
  selector: 'gn-ui-interactive-table',
22
24
  templateUrl: './interactive-table.component.html',
@@ -31,6 +33,7 @@ export class InteractiveTableComponent {
31
33
  columns: QueryList<InteractiveTableColumnComponent>
32
34
 
33
35
  @Input() items: unknown[] = []
36
+ @Input() canEditItem: (item: unknown) => Observable<boolean> = () => of(true)
34
37
  @Input() isDraftPage = false
35
38
  @Output() itemClick = new EventEmitter<unknown>()
36
39
 
@@ -49,10 +52,14 @@ export class InteractiveTableComponent {
49
52
  }
50
53
 
51
54
  getItemTitle(item: CatalogRecord) {
52
- if (!item.extras?.edit && !this.isDraftPage && item.kind === 'dataset') {
53
- return 'editor.record.lock.reason'
54
- } else if (item.kind !== 'dataset') {
55
- return 'editor.record.lock.format'
55
+ if (!this.isDraftPage) {
56
+ if (item.kind !== 'dataset') {
57
+ return 'editor.record.lock.resourceType'
58
+ } else if (item.extras?.isHarvested) {
59
+ return 'editor.record.lock.harvested'
60
+ } else if (!item.extras?.edit) {
61
+ return 'editor.record.lock.owner'
62
+ }
56
63
  }
57
64
  return ''
58
65
  }