geonetwork-ui 2.8.0-dev.8cb6904b7 → 2.8.0-dev.94b119e28

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 (111) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.mjs +3 -3
  2. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.mjs +24 -1
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +7 -7
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +3 -2
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +4 -3
  6. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/constant.mjs +1 -2
  7. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  8. package/esm2022/libs/feature/dataviz/src/lib/service/data.service.mjs +18 -7
  9. package/esm2022/libs/feature/dataviz/src/lib/stac-view/stac-view.component.mjs +37 -8
  10. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +23 -3
  11. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +2 -2
  12. package/esm2022/libs/feature/record/src/lib/state/mdview.facade.mjs +13 -1
  13. package/esm2022/libs/feature/search/src/lib/constants.mjs +1 -2
  14. package/esm2022/libs/ui/elements/src/index.mjs +2 -1
  15. package/esm2022/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.mjs +37 -0
  16. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +5 -9
  17. package/esm2022/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.mjs +5 -5
  18. package/esm2022/libs/util/i18n/src/lib/date-locales.mjs +33 -0
  19. package/esm2022/libs/util/shared/src/index.mjs +2 -1
  20. package/esm2022/libs/util/shared/src/lib/humanize-date.directive.mjs +33 -0
  21. package/esm2022/libs/util/shared/src/lib/services/date.service.mjs +19 -2
  22. package/esm2022/translations/de.json +10 -4
  23. package/esm2022/translations/en.json +10 -4
  24. package/esm2022/translations/es.json +6 -0
  25. package/esm2022/translations/fr.json +10 -4
  26. package/esm2022/translations/it.json +10 -4
  27. package/esm2022/translations/nl.json +6 -0
  28. package/esm2022/translations/pt.json +6 -0
  29. package/esm2022/translations/sk.json +7 -1
  30. package/fesm2022/geonetwork-ui-date-locales-MYnkDJ5h.mjs +35 -0
  31. package/fesm2022/geonetwork-ui-date-locales-MYnkDJ5h.mjs.map +1 -0
  32. package/fesm2022/geonetwork-ui.mjs +268 -115
  33. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  34. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  35. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -1
  36. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +5 -1
  37. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  38. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  39. package/libs/api/repository/src/lib/gn4/elasticsearch/constant.d.ts.map +1 -1
  40. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +6 -1
  41. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  42. package/libs/feature/dataviz/src/lib/service/data.service.d.ts.map +1 -1
  43. package/libs/feature/dataviz/src/lib/stac-view/stac-view.component.d.ts +11 -6
  44. package/libs/feature/dataviz/src/lib/stac-view/stac-view.component.d.ts.map +1 -1
  45. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +2 -0
  46. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  47. package/libs/feature/record/src/lib/state/mdview.facade.d.ts +4 -0
  48. package/libs/feature/record/src/lib/state/mdview.facade.d.ts.map +1 -1
  49. package/libs/feature/search/src/lib/constants.d.ts.map +1 -1
  50. package/libs/ui/elements/src/index.d.ts +1 -0
  51. package/libs/ui/elements/src/index.d.ts.map +1 -1
  52. package/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.d.ts +8 -0
  53. package/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.d.ts.map +1 -0
  54. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts +0 -2
  55. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts.map +1 -1
  56. package/libs/util/i18n/src/lib/date-locales.d.ts +5 -0
  57. package/libs/util/i18n/src/lib/date-locales.d.ts.map +1 -0
  58. package/libs/util/shared/src/index.d.ts +1 -0
  59. package/libs/util/shared/src/index.d.ts.map +1 -1
  60. package/libs/util/shared/src/lib/humanize-date.directive.d.ts +15 -0
  61. package/libs/util/shared/src/lib/humanize-date.directive.d.ts.map +1 -0
  62. package/libs/util/shared/src/lib/services/date.service.d.ts +4 -0
  63. package/libs/util/shared/src/lib/services/date.service.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -2
  66. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -1
  67. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.service+eaux-usees.ts +1 -1
  68. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +5 -2
  69. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -1
  70. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -1
  71. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +2 -2
  72. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +1 -1
  73. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.service+napitswallonia.ts +1 -1
  74. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +26 -0
  75. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -6
  76. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +6 -2
  77. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +4 -2
  78. package/src/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts +0 -1
  79. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +8 -1
  80. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +16 -5
  81. package/src/libs/feature/dataviz/src/lib/stac-view/stac-view.component.html +39 -4
  82. package/src/libs/feature/dataviz/src/lib/stac-view/stac-view.component.ts +44 -9
  83. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +3 -3
  84. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +30 -0
  85. package/src/libs/feature/editor/src/lib/fields.config.ts +1 -1
  86. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +19 -0
  87. package/src/libs/feature/search/src/lib/constants.ts +0 -1
  88. package/src/libs/ui/elements/src/index.ts +1 -0
  89. package/src/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.css +0 -0
  90. package/src/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.html +31 -0
  91. package/src/libs/ui/elements/src/lib/metadata-doi/metadata-doi.component.ts +30 -0
  92. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +16 -12
  93. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -9
  94. package/src/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.html +1 -1
  95. package/src/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.ts +2 -2
  96. package/src/libs/util/i18n/src/lib/date-locales.ts +63 -0
  97. package/src/libs/util/shared/src/index.ts +1 -0
  98. package/src/libs/util/shared/src/lib/humanize-date.directive.ts +35 -0
  99. package/src/libs/util/shared/src/lib/services/date.service.ts +27 -1
  100. package/translations/de.json +10 -4
  101. package/translations/en.json +10 -4
  102. package/translations/es.json +6 -0
  103. package/translations/fr.json +10 -4
  104. package/translations/it.json +10 -4
  105. package/translations/nl.json +6 -0
  106. package/translations/pt.json +6 -0
  107. package/translations/sk.json +7 -1
  108. package/esm2022/libs/ui/elements/src/lib/user-feedback-item/time-since.pipe.mjs +0 -59
  109. package/libs/ui/elements/src/lib/user-feedback-item/time-since.pipe.d.ts +0 -11
  110. package/libs/ui/elements/src/lib/user-feedback-item/time-since.pipe.d.ts.map +0 -1
  111. package/src/libs/ui/elements/src/lib/user-feedback-item/time-since.pipe.ts +0 -54
@@ -7,7 +7,6 @@ export const ES_SOURCE_SUMMARY = [
7
7
  'resourceAbstractObject',
8
8
  'overview',
9
9
  'logo',
10
- 'codelist_status_text',
11
10
  'link',
12
11
  'linkProtocol',
13
12
  'contactForResource*.organisation*',
@@ -113,6 +113,12 @@ export interface INSPIRE_topic {
113
113
  label: string
114
114
  }
115
115
 
116
+ export interface ResourceIdentifier {
117
+ code: string
118
+ codeSpace?: string
119
+ url?: string
120
+ }
121
+
116
122
  export interface BaseRecord {
117
123
  uniqueIdentifier: Uuid
118
124
  ownerOrganization: Organization
@@ -135,7 +141,8 @@ export interface BaseRecord {
135
141
  updateFrequency?: UpdateFrequency
136
142
 
137
143
  // information related to the resource (dataset, service)
138
- resourceIdentifier?: string
144
+
145
+ resourceIdentifiers?: Array<ResourceIdentifier>
139
146
  contactsForResource: Array<Individual>
140
147
  resourceCreated?: Date
141
148
  resourcePublished?: Date
@@ -97,16 +97,27 @@ export class DataService {
97
97
  if (!featureType) {
98
98
  throw new Error('wfs.featuretype.notfound')
99
99
  }
100
+
101
+ const wfsVersion = endpoint.getVersion()
102
+ const addSrsName = wfsVersion === '1.1.0' || wfsVersion === '2.0.0'
103
+ const defaultCrs = featureType.defaultCrs
104
+
105
+ const shouldAddOutputCrs = addSrsName && defaultCrs
106
+
100
107
  return {
101
- all: featureType.outputFormats.reduce(
102
- (prev, curr) => ({
108
+ all: featureType.outputFormats.reduce((prev, curr) => {
109
+ const isJsonFormat = curr.toLowerCase().includes('json')
110
+ return {
103
111
  ...prev,
104
112
  [curr]: endpoint.getFeatureUrl(featureType.name, {
105
113
  outputFormat: curr,
114
+ ...(shouldAddOutputCrs &&
115
+ !isJsonFormat && {
116
+ outputCrs: defaultCrs,
117
+ }),
106
118
  }),
107
- }),
108
- {}
109
- ),
119
+ }
120
+ }, {}),
110
121
  geojson: endpoint.supportsJson(featureType.name)
111
122
  ? endpoint.getFeatureUrl(featureType.name, {
112
123
  asJson: true,
@@ -1,5 +1,40 @@
1
- <div class="w-full h-full flex flex-col">
2
- <div
3
- class="relative h-full mt-6 bg-white border border-gray-300 rounded-lg overflow-hidden"
4
- ></div>
1
+ <div
2
+ class="w-full h-full flex flex-row mt-6 bg-white border border-gray-300 rounded-lg overflow-hidden"
3
+ >
4
+ <div class="flex-1 flex flex-col">
5
+ <div class="m-8">
6
+ <p class="mb-4" translate>stac.filter.period</p>
7
+ <p translate>stac.filter.from</p>
8
+ <gn-ui-date-picker
9
+ id="start-date-picker"
10
+ [date]="currentTemporalExtent?.start"
11
+ (dateChange)="onStartDateChange($event)"
12
+ />
13
+ <p class="mt-4" translate>stac.filter.to</p>
14
+ <gn-ui-date-picker
15
+ id="end-date-picker"
16
+ [date]="currentTemporalExtent?.end"
17
+ (dateChange)="onEndDateChange($event)"
18
+ />
19
+ </div>
20
+
21
+ <div class="mt-auto mb-8 mx-8" *ngIf="isTemporalFilterModified">
22
+ <button
23
+ id="reset-filters-button"
24
+ type="button"
25
+ class="flex items-center"
26
+ (click)="onResetFilters()"
27
+ >
28
+ <span translate>stac.filter.reset</span>
29
+ <ng-icon
30
+ name="matDeleteOutline"
31
+ class="pointer-events-none ml-2"
32
+ ></ng-icon>
33
+ </button>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="w-[655px] flex-shrink-0 flex items-center justify-center">
38
+ <span>Map...</span>
39
+ </div>
5
40
  </div>
@@ -3,11 +3,16 @@ import {
3
3
  ChangeDetectionStrategy,
4
4
  Component,
5
5
  Input,
6
- OnDestroy,
6
+ OnInit,
7
7
  } from '@angular/core'
8
- import { DatasetServiceDistribution } from '../../../../../../libs/common/domain/src/lib/model/record'
9
- import { TranslatePipe } from '@ngx-translate/core'
10
- import { Subscription } from 'rxjs'
8
+ import {
9
+ DatasetServiceDistribution,
10
+ DatasetTemporalExtent,
11
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
12
+ import { DatePickerComponent } from '../../../../../../libs/ui/inputs/src'
13
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
14
+ import { matDeleteOutline } from '@ng-icons/material-icons/outline'
15
+ import { TranslateDirective } from '@ngx-translate/core'
11
16
 
12
17
  @Component({
13
18
  selector: 'gn-ui-stac-view',
@@ -15,13 +20,43 @@ import { Subscription } from 'rxjs'
15
20
  styleUrls: ['./stac-view.component.css'],
16
21
  changeDetection: ChangeDetectionStrategy.OnPush,
17
22
  standalone: true,
18
- imports: [CommonModule, TranslatePipe],
23
+ imports: [
24
+ CommonModule,
25
+ DatePickerComponent,
26
+ NgIconComponent,
27
+ TranslateDirective,
28
+ ],
29
+ viewProviders: [provideIcons({ matDeleteOutline })],
19
30
  })
20
- export class StacViewComponent implements OnDestroy {
31
+ export class StacViewComponent implements OnInit {
21
32
  @Input() link: DatasetServiceDistribution
22
- private subscription = new Subscription()
33
+ @Input() initialTemporalExtent: DatasetTemporalExtent | null
34
+
35
+ currentTemporalExtent: DatasetTemporalExtent | null = null
36
+ isTemporalFilterModified = false
37
+
38
+ onStartDateChange(date: Date) {
39
+ this.currentTemporalExtent = {
40
+ ...this.currentTemporalExtent,
41
+ start: date,
42
+ }
43
+ this.isTemporalFilterModified = true
44
+ }
45
+
46
+ onEndDateChange(date: Date) {
47
+ this.currentTemporalExtent = {
48
+ ...this.currentTemporalExtent,
49
+ end: date,
50
+ }
51
+ this.isTemporalFilterModified = true
52
+ }
53
+
54
+ onResetFilters() {
55
+ this.currentTemporalExtent = this.initialTemporalExtent
56
+ this.isTemporalFilterModified = false
57
+ }
23
58
 
24
- ngOnDestroy() {
25
- this.subscription.unsubscribe()
59
+ ngOnInit() {
60
+ this.currentTemporalExtent = this.initialTemporalExtent
26
61
  }
27
62
  }
@@ -69,11 +69,11 @@
69
69
  (valueChange)="valueChange.emit($event)"
70
70
  ></gn-ui-form-field-overviews>
71
71
  </ng-container>
72
- <ng-container *ngSwitchCase="'resourceIdentifier'">
72
+ <ng-container *ngSwitchCase="'resourceIdentifiers'">
73
73
  <gn-ui-form-field-simple
74
74
  [type]="'text'"
75
- [value]="valueAsString"
76
- (valueChange)="valueChange.emit($event)"
75
+ [value]="valueAsResourceIdentifierCode"
76
+ (valueChange)="handleResourceIdentifierChange($event)"
77
77
  ></gn-ui-form-field-simple>
78
78
  </ng-container>
79
79
  <ng-container *ngSwitchCase="'resourceCreated'">
@@ -142,4 +142,34 @@ export class FormFieldComponent {
142
142
  get valueAsOnlineResources() {
143
143
  return this.value as Array<OnlineResource>
144
144
  }
145
+ get valueAsResourceIdentifierCode() {
146
+ const identifiers = this.value as Array<{
147
+ code: string
148
+ codeSpace?: string
149
+ url?: string
150
+ }>
151
+ return identifiers?.[0]?.code || ''
152
+ }
153
+
154
+ handleResourceIdentifierChange(code: string) {
155
+ const identifiers = this.value as Array<{
156
+ code: string
157
+ codeSpace?: string
158
+ url?: string
159
+ }>
160
+
161
+ if (!code) {
162
+ this.valueChange.emit(identifiers?.slice(1) || [])
163
+ return
164
+ }
165
+
166
+ if (identifiers?.[0]) {
167
+ this.valueChange.emit([
168
+ { ...identifiers[0], code },
169
+ ...identifiers.slice(1),
170
+ ])
171
+ } else {
172
+ this.valueChange.emit([{ code }])
173
+ }
174
+ }
145
175
  }
@@ -81,7 +81,7 @@ export const RECORD_RESOURCE_CREATED_FIELD: EditorField = {
81
81
  }
82
82
 
83
83
  export const RESOURCE_IDENTIFIER_FIELD: EditorField = {
84
- model: 'resourceIdentifier',
84
+ model: 'resourceIdentifiers',
85
85
  formFieldConfig: {
86
86
  labelKey: marker('editor.record.form.field.resourceIdentifier'),
87
87
  },
@@ -89,6 +89,25 @@ export class MdViewFacade {
89
89
  shareReplay(1)
90
90
  )
91
91
 
92
+ resourceDoi$ = this.metadata$.pipe(
93
+ map((record) => {
94
+ if (!record?.resourceIdentifiers?.length) return null
95
+ const doiIdentifier = record.resourceIdentifiers.find(
96
+ (id) =>
97
+ id.codeSpace?.toLowerCase().includes('doi.org') ||
98
+ id.code.startsWith('10.')
99
+ )
100
+
101
+ if (!doiIdentifier) return null
102
+
103
+ return {
104
+ code: doiIdentifier.code,
105
+ url: doiIdentifier.url ? doiIdentifier.url : null,
106
+ }
107
+ }),
108
+ shareReplay(1)
109
+ )
110
+
92
111
  apiLinks$ = this.allLinks$.pipe(
93
112
  map((links) =>
94
113
  links.filter((link) => this.linkClassifier.hasUsage(link, LinkUsage.API))
@@ -11,7 +11,6 @@ export const FIELDS_SUMMARY: FieldName[] = [
11
11
  'resourceAbstractObject',
12
12
  'overview',
13
13
  'logo',
14
- 'codelist_status_text',
15
14
  'link',
16
15
  'linkProtocol',
17
16
  'contactForResource*.organisation*',
@@ -14,6 +14,7 @@ export * from './lib/markdown-editor/markdown-editor.component'
14
14
  export * from './lib/markdown-parser/markdown-parser.component'
15
15
  export * from './lib/metadata-catalog/metadata-catalog.component'
16
16
  export * from './lib/metadata-contact/metadata-contact.component'
17
+ export * from './lib/metadata-doi/metadata-doi.component'
17
18
  export * from './lib/metadata-info/metadata-info.component'
18
19
  export * from './lib/metadata-quality-item/metadata-quality-item.component'
19
20
  export * from './lib/metadata-quality/metadata-quality.component'
@@ -0,0 +1,31 @@
1
+ <div
2
+ class="border border-gray-300 rounded-lg py-4 px-5 text-black flex justify-between items-center gap-4"
3
+ >
4
+ <div class="overflow-hidden flex-1">
5
+ <p class="text-base font-medium mb-3">DOI</p>
6
+ <p
7
+ class="text-base font-medium overflow-hidden text-ellipsis whitespace-nowrap"
8
+ [title]="code"
9
+ >
10
+ {{ code }}
11
+ </p>
12
+ </div>
13
+ <div class="flex gap-2 items-start">
14
+ <gn-ui-copy-text-button
15
+ [text]="code"
16
+ [displayText]="false"
17
+ [tooltipText]="'record.metadata.doi.copy' | translate"
18
+ class="[&>div]:flex [&>div]:items-center [&>div]:justify-center [&_button]:w-[40px] [&_button]:h-[32px] [&_button]:flex [&_button]:items-center [&_button]:justify-center [&_button]:hover:bg-gray-100 [&_button]:rounded-lg [&_button]:transition-colors [&_button]:border [&_button]:border-gray-300 [&_button]:px-2 [&_button]:py-1 [&_ng-icon]:w-5 [&_ng-icon]:h-5"
19
+ ></gn-ui-copy-text-button>
20
+ <a
21
+ *ngIf="link"
22
+ [href]="link"
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ class="w-[40px] h-[32px] flex items-center justify-center hover:bg-gray-100 rounded-lg transition-colors border border-gray-300 px-2 py-1"
26
+ [matTooltip]="'record.metadata.doi.open' | translate"
27
+ >
28
+ <ng-icon name="matOpenInNew" size="20"></ng-icon>
29
+ </a>
30
+ </div>
31
+ </div>
@@ -0,0 +1,30 @@
1
+ import { Component, Input } from '@angular/core'
2
+ import { CommonModule } from '@angular/common'
3
+ import { NgIcon, provideIcons } from '@ng-icons/core'
4
+ import { MatTooltipModule } from '@angular/material/tooltip'
5
+ import { matOpenInNew } from '@ng-icons/material-icons/baseline'
6
+ import { TranslatePipe } from '@ngx-translate/core'
7
+ import { CopyTextButtonComponent } from '../../../../../../libs/ui/inputs/src'
8
+
9
+ @Component({
10
+ selector: 'gn-ui-metadata-doi',
11
+ standalone: true,
12
+ imports: [
13
+ CommonModule,
14
+ MatTooltipModule,
15
+ NgIcon,
16
+ TranslatePipe,
17
+ CopyTextButtonComponent,
18
+ ],
19
+ templateUrl: './metadata-doi.component.html',
20
+ styleUrl: './metadata-doi.component.css',
21
+ viewProviders: [
22
+ provideIcons({
23
+ matOpenInNew,
24
+ }),
25
+ ],
26
+ })
27
+ export class MetadataDoiComponent {
28
+ @Input() code!: string
29
+ @Input() link?: string
30
+ }
@@ -186,27 +186,30 @@
186
186
  data-test="details-panel-resource-created"
187
187
  >
188
188
  <p class="text-sm" translate>record.metadata.creation</p>
189
- <p class="text-primary font-medium mt-1 resource-created">
190
- {{ formatDate(metadata.resourceCreated) }}
191
- </p>
189
+ <p
190
+ class="text-primary font-medium mt-1 resource-created"
191
+ [gnUiHumanizeDate]="metadata.resourceCreated"
192
+ ></p>
192
193
  </div>
193
194
  <div
194
195
  *ngIf="metadata.resourcePublished"
195
196
  data-test="details-panel-resource-published"
196
197
  >
197
198
  <p class="text-sm" translate>record.metadata.publication</p>
198
- <p class="text-primary font-medium mt-1 resource-published">
199
- {{ formatDate(metadata.resourcePublished) }}
200
- </p>
199
+ <p
200
+ class="text-primary font-medium mt-1 resource-published"
201
+ [gnUiHumanizeDate]="metadata.resourcePublished"
202
+ ></p>
201
203
  </div>
202
204
  <div
203
205
  *ngIf="metadata.resourceUpdated"
204
206
  data-test="details-panel-resource-updated"
205
207
  >
206
208
  <p class="text-sm" translate>record.metadata.update</p>
207
- <p class="text-primary font-medium mt-1 resource-updated">
208
- {{ formatDate(metadata.resourceUpdated) }}
209
- </p>
209
+ <p
210
+ class="text-primary font-medium mt-1 resource-updated"
211
+ [gnUiHumanizeDate]="metadata.resourceUpdated"
212
+ ></p>
210
213
  </div>
211
214
  <div
212
215
  *ngIf="metadata.kind === 'dataset' && metadata.updateFrequency"
@@ -294,9 +297,10 @@
294
297
  <div class="flex flex-col gap-4 mr-4 py-5 rounded text-gray-700">
295
298
  <div *ngIf="metadata.recordUpdated">
296
299
  <p class="text-sm" translate>record.metadata.updatedOn</p>
297
- <p class="text-primary font-medium">
298
- {{ metadata.recordUpdated && formatDateTime(metadata.recordUpdated) }}
299
- </p>
300
+ <p
301
+ class="text-primary font-medium"
302
+ [gnUiHumanizeDate]="metadata.recordUpdated"
303
+ ></p>
300
304
  </div>
301
305
  <div *ngIf="metadata.landingPage">
302
306
  <p class="text-sm" translate>record.metadata.sheet</p>
@@ -26,7 +26,7 @@ import { matOpenInNew } from '@ng-icons/material-icons/baseline'
26
26
  import { matMailOutline } from '@ng-icons/material-icons/outline'
27
27
  import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
28
28
  import { GnUiLinkifyDirective } from './linkify.directive'
29
-
29
+ import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/src'
30
30
  import { CommonModule } from '@angular/common'
31
31
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
32
32
 
@@ -49,6 +49,7 @@ import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
49
49
  CopyTextButtonComponent,
50
50
  NgIcon,
51
51
  GnUiLinkifyDirective,
52
+ GnUiHumanizeDateDirective,
52
53
  SpatialExtentComponent,
53
54
  ],
54
55
  viewProviders: [
@@ -146,12 +147,4 @@ export class MetadataInfoComponent {
146
147
  onKeywordClick(keyword: Keyword) {
147
148
  this.keyword.emit(keyword)
148
149
  }
149
-
150
- formatDate(date: Date): string {
151
- return this.dateService.formatDate(date)
152
- }
153
-
154
- formatDateTime(date: Date): string {
155
- return this.dateService.formatDateTime(date)
156
- }
157
150
  }
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  <div class="p-4 flex flex-col">
15
15
  <span>{{ userFeedbackParent.authorName }}</span>
16
- <span> {{ userFeedbackParent.date | timeSince }}</span>
16
+ <span [gnUiHumanizeDate]="userFeedbackParent.date"></span>
17
17
  </div>
18
18
  </div>
19
19
  <div data-cy="commentText" class="mt-4 whitespace-pre-line">
@@ -11,13 +11,13 @@ import {
11
11
  UserFeedbackViewModel,
12
12
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
13
  import { UserModel } from '../../../../../../libs/common/domain/src/lib/model/user'
14
- import { TimeSincePipe } from './time-since.pipe'
15
14
  import { CommonModule } from '@angular/common'
16
15
  import { ButtonComponent, TextAreaComponent } from '../../../../../../libs/ui/inputs/src'
17
16
  import { TranslatePipe } from '@ngx-translate/core'
18
17
  import { SpinningLoaderComponent } from '../../../../../../libs/ui/widgets/src'
19
18
  import { NgIcon, provideIcons } from '@ng-icons/core'
20
19
  import { matSendOutline } from '@ng-icons/material-icons/outline'
20
+ import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/src'
21
21
 
22
22
  @Component({
23
23
  selector: 'gn-ui-user-feedback-item',
@@ -27,11 +27,11 @@ import { matSendOutline } from '@ng-icons/material-icons/outline'
27
27
  standalone: true,
28
28
  imports: [
29
29
  CommonModule,
30
- TimeSincePipe,
31
30
  TextAreaComponent,
32
31
  TranslatePipe,
33
32
  ButtonComponent,
34
33
  SpinningLoaderComponent,
34
+ GnUiHumanizeDateDirective,
35
35
  NgIcon,
36
36
  ],
37
37
  viewProviders: [
@@ -0,0 +1,63 @@
1
+ import { LanguageCode2 } from './language-codes'
2
+ import {
3
+ ar,
4
+ az,
5
+ ca,
6
+ cs,
7
+ cy,
8
+ da,
9
+ de,
10
+ enUS,
11
+ es,
12
+ fi,
13
+ fr,
14
+ hy,
15
+ is,
16
+ it,
17
+ ka,
18
+ ko,
19
+ Locale,
20
+ nl,
21
+ nn,
22
+ pl,
23
+ pt,
24
+ ru,
25
+ sk,
26
+ sv,
27
+ tr,
28
+ uk,
29
+ zhCN,
30
+ } from 'date-fns/locale'
31
+
32
+ // all 2-char language codes should be listed here
33
+ const locales: Record<LanguageCode2, Locale> = {
34
+ en: enUS,
35
+ nl: nl,
36
+ fr: fr,
37
+ de: de,
38
+ ko: ko,
39
+ es: es,
40
+ cs: cs,
41
+ ca: ca,
42
+ fi: fi,
43
+ is: is,
44
+ it: it,
45
+ pt: pt,
46
+ ru: ru,
47
+ zh: zhCN,
48
+ sk: sk,
49
+ ar: ar,
50
+ da: da,
51
+ no: nn,
52
+ pl: pl,
53
+ sv: sv,
54
+ tr: tr,
55
+ hy: hy,
56
+ az: az,
57
+ ka: ka,
58
+ uk: uk,
59
+ cy: cy,
60
+ rm: enUS, // locale is unavailable
61
+ }
62
+
63
+ export default locales
@@ -4,3 +4,4 @@ export * from './lib/links'
4
4
  export * from './lib/image-fallback.directive'
5
5
  export * from './lib/gn-ui-version'
6
6
  export * from './lib/record'
7
+ export * from './lib/humanize-date.directive'
@@ -0,0 +1,35 @@
1
+ import { Renderer2, Directive, ElementRef, Input, OnInit } from '@angular/core'
2
+ import { DateService } from './services/date.service'
3
+
4
+ @Directive({
5
+ selector: '[gnUiHumanizeDate]',
6
+ standalone: true,
7
+ })
8
+ export class GnUiHumanizeDateDirective implements OnInit {
9
+ @Input() gnUiHumanizeDate: Date | string
10
+
11
+ constructor(
12
+ private dateService: DateService,
13
+ private el: ElementRef,
14
+ private renderer: Renderer2
15
+ ) {}
16
+
17
+ async ngOnInit() {
18
+ await this.updateElement()
19
+ }
20
+
21
+ private async updateElement(): Promise<void> {
22
+ const dateValue = this.gnUiHumanizeDate
23
+
24
+ const fullDateTime = this.dateService.formatDateTime(dateValue)
25
+ const relativeDate =
26
+ await this.dateService.formatRelativeDateTime(dateValue)
27
+
28
+ this.renderer.setAttribute(this.el.nativeElement, 'title', fullDateTime)
29
+ this.renderer.setProperty(
30
+ this.el.nativeElement,
31
+ 'textContent',
32
+ relativeDate
33
+ )
34
+ }
35
+ }
@@ -1,10 +1,18 @@
1
1
  import { Injectable } from '@angular/core'
2
2
  import { TranslateService } from '@ngx-translate/core'
3
+ import { type Locale } from 'date-fns/locale'
4
+ import { formatDistance } from 'date-fns/formatDistance'
5
+
6
+ const DEFAULT_LANGUAGE = 'en'
3
7
 
4
8
  @Injectable({
5
9
  providedIn: 'root',
6
10
  })
7
11
  export class DateService {
12
+ dateLocales = import('../../../../../../libs/util/i18n/src/lib/date-locales').then(
13
+ (obj) => obj.default
14
+ )
15
+
8
16
  constructor(private translateService: TranslateService) {}
9
17
 
10
18
  private getDateObject(date: Date | string): Date {
@@ -22,11 +30,17 @@ export class DateService {
22
30
  locale: string
23
31
  dateObj: Date
24
32
  } {
25
- const locale = this.translateService.currentLang || 'en-US'
33
+ const locale = this.translateService.currentLang || DEFAULT_LANGUAGE
26
34
  const dateObj = this.getDateObject(date)
27
35
  return { locale, dateObj }
28
36
  }
29
37
 
38
+ private async getDateLocale(): Promise<Locale> {
39
+ const lang = this.translateService.currentLang || DEFAULT_LANGUAGE
40
+ const locales = await this.dateLocales
41
+ return locales[lang]
42
+ }
43
+
30
44
  formatDate(
31
45
  date: Date | string,
32
46
  options?: Intl.DateTimeFormatOptions
@@ -42,4 +56,16 @@ export class DateService {
42
56
  const { locale, dateObj } = this.getLocaleAndDate(date)
43
57
  return dateObj.toLocaleString(locale, options)
44
58
  }
59
+
60
+ async formatRelativeDateTime(date: Date | string): Promise<string> {
61
+ const dateObj = this.getDateObject(date)
62
+
63
+ const now = new Date()
64
+ const locale = await this.getDateLocale()
65
+
66
+ return formatDistance(dateObj, now, {
67
+ addSuffix: true,
68
+ locale: locale,
69
+ })
70
+ }
45
71
  }