geonetwork-ui 2.10.0-dev.cbf02ead8 → 2.10.0-dev.cf0577fae
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.
- package/fesm2022/geonetwork-ui.mjs +318 -58
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +49 -7
- package/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
- package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -0
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +10 -2
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +11 -0
- package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
- package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
- package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
- package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +38 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +0 -1
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +37 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +1 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +0 -3
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +0 -1
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +27 -25
- package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
- package/src/libs/feature/editor/src/lib/services/editor.service.ts +1 -1
- package/src/libs/ui/elements/src/index.ts +1 -0
- package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +96 -0
- package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +23 -2
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +51 -7
- package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +2 -2
- package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
- package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
- package/src/libs/util/app-config/src/lib/app-config.ts +14 -1
- package/src/libs/util/app-config/src/lib/model.ts +3 -0
- package/src/libs/util/app-config/src/lib/parse-utils.ts +27 -0
- package/src/libs/util/shared/src/lib/utils/user-display.ts +9 -0
- package/translations/de.json +8 -1
- package/translations/en.json +8 -1
- package/translations/es.json +8 -1
- package/translations/fr.json +8 -1
- package/translations/it.json +8 -1
- package/translations/nl.json +8 -1
- package/translations/pt.json +8 -1
- package/translations/sk.json +8 -1
package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Component,
|
|
5
5
|
ElementRef,
|
|
6
6
|
EventEmitter,
|
|
7
|
+
inject,
|
|
7
8
|
Input,
|
|
8
9
|
Output,
|
|
9
10
|
ViewChild,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import { FormFieldWrapperComponent } from '../../../../../../../../libs/ui/layout/src'
|
|
23
24
|
import { TranslatePipe } from '@ngx-translate/core'
|
|
24
25
|
import {
|
|
26
|
+
FieldFocusDirective,
|
|
25
27
|
FormFieldDateComponent,
|
|
26
28
|
FormFieldLicenseComponent,
|
|
27
29
|
FormFieldTemporalExtentsComponent,
|
|
@@ -77,6 +79,7 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
|
|
|
77
79
|
FormFieldTopicsComponent,
|
|
78
80
|
TextFieldModule,
|
|
79
81
|
],
|
|
82
|
+
hostDirectives: [FieldFocusDirective],
|
|
80
83
|
})
|
|
81
84
|
export class FormFieldComponent {
|
|
82
85
|
@Input() uniqueIdentifier: string
|
|
@@ -92,6 +95,8 @@ export class FormFieldComponent {
|
|
|
92
95
|
@ViewChild('titleInput') titleInput: ElementRef
|
|
93
96
|
isOpenData = false
|
|
94
97
|
|
|
98
|
+
fieldFocus = inject(FieldFocusDirective)
|
|
99
|
+
|
|
95
100
|
toggleIsOpenData(event: boolean) {
|
|
96
101
|
this.isOpenData = event
|
|
97
102
|
}
|
|
@@ -2,10 +2,10 @@ import { CommonModule } from '@angular/common'
|
|
|
2
2
|
import {
|
|
3
3
|
ChangeDetectionStrategy,
|
|
4
4
|
Component,
|
|
5
|
-
ElementRef,
|
|
6
5
|
inject,
|
|
7
6
|
OnDestroy,
|
|
8
7
|
OnInit,
|
|
8
|
+
viewChildren,
|
|
9
9
|
} from '@angular/core'
|
|
10
10
|
import { EditorFacade } from '../../+state/editor.facade'
|
|
11
11
|
import { EditorFieldValue } from '../../models'
|
|
@@ -15,8 +15,9 @@ import {
|
|
|
15
15
|
EditorFieldWithValue,
|
|
16
16
|
EditorSectionWithValues,
|
|
17
17
|
} from '../../+state/editor.models'
|
|
18
|
-
import {
|
|
18
|
+
import { firstValueFrom, map, Subscription, withLatestFrom } from 'rxjs'
|
|
19
19
|
import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
20
|
+
import { switchMap } from 'rxjs/operators'
|
|
20
21
|
|
|
21
22
|
@Component({
|
|
22
23
|
selector: 'gn-ui-record-form',
|
|
@@ -27,41 +28,42 @@ import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/l
|
|
|
27
28
|
imports: [CommonModule, FormFieldComponent, TranslateDirective],
|
|
28
29
|
})
|
|
29
30
|
export class RecordFormComponent implements OnInit, OnDestroy {
|
|
30
|
-
anchorIdPrefix = 'gn-ui--field-'
|
|
31
31
|
facade = inject(EditorFacade)
|
|
32
|
-
private el = inject(ElementRef)
|
|
33
32
|
subscription = new Subscription()
|
|
34
33
|
|
|
35
34
|
recordUniqueIdentifier$ = this.facade.record$.pipe(
|
|
36
35
|
map((record) => record.uniqueIdentifier)
|
|
37
36
|
)
|
|
38
37
|
|
|
38
|
+
focusFieldWithPage$ = this.facade.focusedField$.pipe(
|
|
39
|
+
switchMap(
|
|
40
|
+
async (field) => [field, await this.getPageIndexForField(field)] as const
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
formFields = viewChildren(FormFieldComponent)
|
|
45
|
+
|
|
46
|
+
focusField(model: CatalogRecordKeys) {
|
|
47
|
+
const fields = this.formFields()
|
|
48
|
+
const field = fields.find((f) => f.model === model)
|
|
49
|
+
field?.fieldFocus.focusField()
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
ngOnInit() {
|
|
40
53
|
this.subscription.add(
|
|
41
|
-
this.
|
|
54
|
+
this.focusFieldWithPage$
|
|
42
55
|
.pipe(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
field
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
),
|
|
49
|
-
}))
|
|
56
|
+
withLatestFrom(
|
|
57
|
+
this.facade.currentPage$,
|
|
58
|
+
([field, fieldPage], currentPage) =>
|
|
59
|
+
[field, fieldPage, currentPage] as const
|
|
60
|
+
)
|
|
50
61
|
)
|
|
51
|
-
.subscribe(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.facade.setCurrentPage(pageIndex)
|
|
55
|
-
this.el.nativeElement.scrollIntoView({
|
|
56
|
-
behavior: 'instant',
|
|
57
|
-
block: 'start',
|
|
58
|
-
})
|
|
62
|
+
.subscribe(([field, fieldPage, currentPage]) => {
|
|
63
|
+
if (fieldPage !== null && fieldPage !== currentPage) {
|
|
64
|
+
this.facade.setCurrentPage(fieldPage)
|
|
59
65
|
}
|
|
60
|
-
setTimeout(() =>
|
|
61
|
-
document
|
|
62
|
-
.getElementById(this.anchorIdPrefix + field)
|
|
63
|
-
?.scrollIntoView({ behavior: 'instant', block: 'start' })
|
|
64
|
-
)
|
|
66
|
+
setTimeout(() => this.focusField(field))
|
|
65
67
|
})
|
|
66
68
|
)
|
|
67
69
|
}
|
|
@@ -19,9 +19,13 @@ export interface FormFieldConfig {
|
|
|
19
19
|
// This is used for instance to target only certain online resources in a field
|
|
20
20
|
type OnlineLinkResourceSpecifier = `onlineResourceType:link`
|
|
21
21
|
type DatasetDistributionsSpecifier = `onlineResourceType:!link`
|
|
22
|
+
// When set on the `contacts` field, contacts are rendered as editable detail
|
|
23
|
+
// fields (ContactDetailsFormComponent) instead of cards (ContactCardComponent)
|
|
24
|
+
type EditableContactDetailsSpecifier = `contact:editableDetails`
|
|
22
25
|
export type FieldModelSpecifier =
|
|
23
26
|
| OnlineLinkResourceSpecifier
|
|
24
27
|
| DatasetDistributionsSpecifier
|
|
28
|
+
| EditableContactDetailsSpecifier
|
|
25
29
|
|
|
26
30
|
export type FormFieldComponentName =
|
|
27
31
|
| 'form-field-constraints-shortcuts'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Injectable, inject } from '@angular/core'
|
|
2
|
-
import {
|
|
2
|
+
import { Observable, switchMap } from 'rxjs'
|
|
3
3
|
import { map, tap } from 'rxjs/operators'
|
|
4
4
|
import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
5
5
|
import { EditorConfig } from '../models/'
|
|
@@ -19,6 +19,7 @@ export * from './lib/metadata-info/metadata-info.component'
|
|
|
19
19
|
export * from './lib/metadata-quality-item/metadata-quality-item.component'
|
|
20
20
|
export * from './lib/metadata-quality/metadata-quality.component'
|
|
21
21
|
export * from './lib/contact-pill/contact-pill.component'
|
|
22
|
+
export * from './lib/contact-details/contact-details.component'
|
|
22
23
|
export * from './lib/notification/notification.component'
|
|
23
24
|
export * from './lib/record-api-form/record-api-form.component'
|
|
24
25
|
export * from './lib/thumbnail/thumbnail.component'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="bg-gray-50 rounded border border-gray-200 shadow-md p-4 flex flex-col gap-3 w-full"
|
|
3
|
+
data-test="contact-details"
|
|
4
|
+
>
|
|
5
|
+
@if (displayName) {
|
|
6
|
+
<div class="flex items-center gap-3">
|
|
7
|
+
@if (organization?.logoUrl?.href) {
|
|
8
|
+
<div
|
|
9
|
+
class="flex items-center justify-center rounded-md bg-white w-14 h-14 shrink-0 overflow-hidden"
|
|
10
|
+
>
|
|
11
|
+
<gn-ui-thumbnail
|
|
12
|
+
class="relative h-full w-full"
|
|
13
|
+
[thumbnailUrl]="organization.logoUrl.href"
|
|
14
|
+
fit="contain"
|
|
15
|
+
></gn-ui-thumbnail>
|
|
16
|
+
</div>
|
|
17
|
+
}
|
|
18
|
+
<span
|
|
19
|
+
class="font-title text-xl leading-tight"
|
|
20
|
+
data-test="contact-details-name"
|
|
21
|
+
>{{ displayName }}</span
|
|
22
|
+
>
|
|
23
|
+
</div>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
<!-- Email + phone: always shown; icon dimmed when field is absent -->
|
|
27
|
+
<div class="grid grid-cols-2 gap-1 rounded-md overflow-hidden">
|
|
28
|
+
<div class="bg-gray-100 flex items-center gap-2 px-3 py-2">
|
|
29
|
+
<ng-icon
|
|
30
|
+
class="!w-5 !h-5 !text-[20px] shrink-0"
|
|
31
|
+
[class.opacity-30]="!contact.email"
|
|
32
|
+
[class.opacity-75]="!!contact.email"
|
|
33
|
+
name="matMailOutline"
|
|
34
|
+
></ng-icon>
|
|
35
|
+
@if (contact.email) {
|
|
36
|
+
<a
|
|
37
|
+
[href]="'mailto:' + contact.email"
|
|
38
|
+
class="text-sm break-all hover:underline"
|
|
39
|
+
data-test="contact-details-email"
|
|
40
|
+
>{{ contact.email }}</a
|
|
41
|
+
>
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="bg-gray-100 flex items-center gap-2 px-3 py-2">
|
|
45
|
+
<ng-icon
|
|
46
|
+
class="!w-5 !h-5 !text-[20px] shrink-0"
|
|
47
|
+
[class.opacity-30]="!contact.phone"
|
|
48
|
+
[class.opacity-75]="!!contact.phone"
|
|
49
|
+
name="matCallOutline"
|
|
50
|
+
></ng-icon>
|
|
51
|
+
@if (contact.phone) {
|
|
52
|
+
<span class="text-sm" data-test="contact-details-phone">{{
|
|
53
|
+
contact.phone
|
|
54
|
+
}}</span>
|
|
55
|
+
}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Address: always shown; icon dimmed when absent -->
|
|
60
|
+
<div class="bg-gray-100 rounded-md flex items-start gap-2 px-3 py-2">
|
|
61
|
+
<ng-icon
|
|
62
|
+
class="!w-5 !h-5 !text-[20px] shrink-0 mt-0.5"
|
|
63
|
+
[class.opacity-30]="!addressLines.length"
|
|
64
|
+
[class.opacity-75]="!!addressLines.length"
|
|
65
|
+
name="matLocationOnOutline"
|
|
66
|
+
></ng-icon>
|
|
67
|
+
@if (addressLines.length) {
|
|
68
|
+
<div class="flex flex-col" data-test="contact-details-address">
|
|
69
|
+
@for (line of addressLines; track line) {
|
|
70
|
+
<p class="text-sm m-0">{{ line }}</p>
|
|
71
|
+
}
|
|
72
|
+
</div>
|
|
73
|
+
}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- Website: only shown when org exists; icon dimmed when absent -->
|
|
77
|
+
@if (organization) {
|
|
78
|
+
<div class="bg-gray-100 rounded-md flex items-center gap-2 px-3 py-2">
|
|
79
|
+
<ng-icon
|
|
80
|
+
class="!w-5 !h-5 !text-[20px] shrink-0"
|
|
81
|
+
[class.opacity-30]="!organization.website"
|
|
82
|
+
[class.opacity-75]="!!organization.website"
|
|
83
|
+
name="matOpenInNew"
|
|
84
|
+
></ng-icon>
|
|
85
|
+
@if (organization.website) {
|
|
86
|
+
<a
|
|
87
|
+
[href]="organization.website.href"
|
|
88
|
+
target="_blank"
|
|
89
|
+
class="text-sm break-all hover:underline"
|
|
90
|
+
data-test="contact-details-website"
|
|
91
|
+
>{{ organization.website.href }}</a
|
|
92
|
+
>
|
|
93
|
+
}
|
|
94
|
+
</div>
|
|
95
|
+
}
|
|
96
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
2
|
+
import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
3
|
+
import {
|
|
4
|
+
getAddressLines,
|
|
5
|
+
getIndividualDisplayName,
|
|
6
|
+
} from '../../../../../../libs/util/shared/src'
|
|
7
|
+
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
8
|
+
import { matMailOutline, matOpenInNew } from '@ng-icons/material-icons/baseline'
|
|
9
|
+
import {
|
|
10
|
+
matCallOutline,
|
|
11
|
+
matLocationOnOutline,
|
|
12
|
+
} from '@ng-icons/material-icons/outline'
|
|
13
|
+
import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
|
|
14
|
+
import { TranslateModule } from '@ngx-translate/core'
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
selector: 'gn-ui-contact-details',
|
|
18
|
+
templateUrl: './contact-details.component.html',
|
|
19
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
+
standalone: true,
|
|
21
|
+
imports: [NgIcon, ThumbnailComponent, TranslateModule],
|
|
22
|
+
viewProviders: [
|
|
23
|
+
provideIcons({
|
|
24
|
+
matCallOutline,
|
|
25
|
+
matLocationOnOutline,
|
|
26
|
+
matMailOutline,
|
|
27
|
+
matOpenInNew,
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
})
|
|
31
|
+
export class ContactDetailsComponent {
|
|
32
|
+
@Input() contact: Individual
|
|
33
|
+
|
|
34
|
+
get organization() {
|
|
35
|
+
return this.contact?.organization
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get displayName(): string {
|
|
39
|
+
return getIndividualDisplayName(this.contact)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get addressLines(): string[] {
|
|
43
|
+
return getAddressLines(this.contact?.address)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<gn-ui-button
|
|
2
|
-
type="primary-light"
|
|
2
|
+
[type]="overlayOpen ? 'primary' : 'primary-light'"
|
|
3
3
|
extraClass="group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded"
|
|
4
4
|
data-test="contact-pill"
|
|
5
|
+
(buttonClick)="toggleOverlay()"
|
|
6
|
+
cdkOverlayOrigin
|
|
7
|
+
#overlayOrigin="cdkOverlayOrigin"
|
|
5
8
|
>
|
|
6
9
|
<span
|
|
7
10
|
class="font-title font-medium text-base leading-tight truncate group-hover:text-white"
|
|
@@ -11,6 +14,24 @@
|
|
|
11
14
|
<div
|
|
12
15
|
class="gn-ui-card-icon items-center justify-center w-10 h-8 group-hover:border-white group-hover:text-white"
|
|
13
16
|
>
|
|
14
|
-
|
|
17
|
+
@if (overlayOpen) {
|
|
18
|
+
<ng-icon class="!w-6 !h-6 !text-[24px]" name="matClose"></ng-icon>
|
|
19
|
+
} @else {
|
|
20
|
+
<ng-icon class="!w-6 !h-6 !text-[24px]" name="matInfoOutline"></ng-icon>
|
|
21
|
+
}
|
|
15
22
|
</div>
|
|
16
23
|
</gn-ui-button>
|
|
24
|
+
|
|
25
|
+
<ng-template
|
|
26
|
+
cdkConnectedOverlay
|
|
27
|
+
[cdkConnectedOverlayOrigin]="overlayOrigin"
|
|
28
|
+
[cdkConnectedOverlayOpen]="overlayOpen"
|
|
29
|
+
[cdkConnectedOverlayPositions]="overlayPositions"
|
|
30
|
+
[cdkConnectedOverlayOffsetX]="overlayOffsetX"
|
|
31
|
+
(overlayOutsideClick)="closeOverlay()"
|
|
32
|
+
(detach)="closeOverlay()"
|
|
33
|
+
>
|
|
34
|
+
<div [style.width.px]="overlayWidth">
|
|
35
|
+
<gn-ui-contact-details [contact]="contact"></gn-ui-contact-details>
|
|
36
|
+
</div>
|
|
37
|
+
</ng-template>
|
|
@@ -1,26 +1,70 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConnectedPosition, OverlayModule } from '@angular/cdk/overlay'
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
ElementRef,
|
|
6
|
+
inject,
|
|
7
|
+
Input,
|
|
8
|
+
} from '@angular/core'
|
|
2
9
|
import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
3
10
|
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
11
|
+
import { matClose } from '@ng-icons/material-icons/baseline'
|
|
4
12
|
import { matInfoOutline } from '@ng-icons/material-icons/outline'
|
|
5
13
|
import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
|
|
6
14
|
import { getIndividualDisplayName } from '../../../../../../libs/util/shared/src'
|
|
15
|
+
import { ContactDetailsComponent } from '../contact-details/contact-details.component'
|
|
7
16
|
|
|
8
17
|
@Component({
|
|
9
18
|
selector: 'gn-ui-contact-pill',
|
|
10
19
|
templateUrl: './contact-pill.component.html',
|
|
11
20
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
12
21
|
standalone: true,
|
|
13
|
-
imports: [NgIcon, ButtonComponent],
|
|
14
|
-
viewProviders: [
|
|
15
|
-
provideIcons({
|
|
16
|
-
matInfoOutline,
|
|
17
|
-
}),
|
|
18
|
-
],
|
|
22
|
+
imports: [NgIcon, ButtonComponent, OverlayModule, ContactDetailsComponent],
|
|
23
|
+
viewProviders: [provideIcons({ matClose, matInfoOutline })],
|
|
19
24
|
})
|
|
20
25
|
export class ContactPillComponent {
|
|
21
26
|
@Input() contact: Individual
|
|
22
27
|
|
|
28
|
+
private host = inject(ElementRef<HTMLElement>)
|
|
29
|
+
|
|
30
|
+
overlayOpen = false
|
|
31
|
+
overlayWidth = 0
|
|
32
|
+
overlayOffsetX = 0
|
|
33
|
+
overlayPositions: ConnectedPosition[] = [
|
|
34
|
+
{
|
|
35
|
+
originX: 'start',
|
|
36
|
+
originY: 'bottom',
|
|
37
|
+
overlayX: 'start',
|
|
38
|
+
overlayY: 'top',
|
|
39
|
+
offsetY: 4,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
originX: 'start',
|
|
43
|
+
originY: 'top',
|
|
44
|
+
overlayX: 'start',
|
|
45
|
+
overlayY: 'bottom',
|
|
46
|
+
offsetY: -4,
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
23
50
|
get displayName(): string {
|
|
24
51
|
return getIndividualDisplayName(this.contact)
|
|
25
52
|
}
|
|
53
|
+
|
|
54
|
+
toggleOverlay() {
|
|
55
|
+
if (!this.overlayOpen) {
|
|
56
|
+
// Calculate the width and horizontal offset of the overlay to align it with the parent element
|
|
57
|
+
const parent =
|
|
58
|
+
this.host.nativeElement.parentElement.getBoundingClientRect()
|
|
59
|
+
const pill = this.host.nativeElement.getBoundingClientRect()
|
|
60
|
+
this.overlayWidth = parent.width
|
|
61
|
+
this.overlayOffsetX = parent.left - pill.left
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.overlayOpen = !this.overlayOpen
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
closeOverlay() {
|
|
68
|
+
this.overlayOpen = false
|
|
69
|
+
}
|
|
26
70
|
}
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
matCallOutline,
|
|
22
22
|
matLocationOnOutline,
|
|
23
23
|
} from '@ng-icons/material-icons/outline'
|
|
24
|
-
|
|
24
|
+
import { getAddressLines } from '../../../../../../libs/util/shared/src'
|
|
25
25
|
import { TranslateDirective } from '@ngx-translate/core'
|
|
26
26
|
|
|
27
27
|
@Component({
|
|
@@ -59,10 +59,7 @@ export class MetadataContactComponent {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
get address() {
|
|
62
|
-
|
|
63
|
-
.split(',')
|
|
64
|
-
.map((part) => part.trim())
|
|
65
|
-
return addressParts
|
|
62
|
+
return getAddressLines(this.contacts[0]?.address)
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
onOrganizationClick() {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
data-test="usage-panel"
|
|
54
54
|
>
|
|
55
55
|
<div class="flex flex-col gap-[10px] mr-4 py-[12px] rounded text-gray-900">
|
|
56
|
-
@for (license of licenses; track
|
|
56
|
+
@for (license of licenses; track $index) {
|
|
57
57
|
@if (license.url) {
|
|
58
58
|
<div class="text-primary">
|
|
59
59
|
<a
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
<p class="text-xs font-normal text-black">
|
|
118
118
|
{{ group.roleLabel | translate }}
|
|
119
119
|
</p>
|
|
120
|
-
<div class="grid gap-
|
|
120
|
+
<div class="grid gap-1 grid-cols-1 md:grid-cols-2">
|
|
121
121
|
@for (contact of group.contacts; track contact.email) {
|
|
122
122
|
<gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
|
|
123
123
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
type="url"
|
|
7
7
|
[value]="inputValue"
|
|
8
8
|
(input)="handleInput($event)"
|
|
9
|
-
(keydown.enter)="handleUpload(input)"
|
|
9
|
+
(keydown.enter)="handleUpload(input, $event)"
|
|
10
10
|
[placeholder]="placeholder"
|
|
11
11
|
[attr.aria-label]="placeholder"
|
|
12
12
|
[disabled]="disabled"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
extraClass="absolute inset-y-[var(--side-padding)] right-[var(--side-padding)]"
|
|
26
26
|
type="primary"
|
|
27
27
|
[disabled]="disabled || input.value === '' || !isValidUrl(input.value)"
|
|
28
|
-
(buttonClick)="handleUpload(input)"
|
|
28
|
+
(buttonClick)="handleUpload(input, $event)"
|
|
29
29
|
>
|
|
30
30
|
<ng-content>
|
|
31
31
|
<ng-icon name="iconoirArrowUp"></ng-icon>
|
|
@@ -81,7 +81,8 @@ export class UrlInputComponent implements OnChanges {
|
|
|
81
81
|
this.valueChange.next(value)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
handleUpload(element: HTMLInputElement) {
|
|
84
|
+
handleUpload(element: HTMLInputElement, event: Event) {
|
|
85
|
+
event.stopPropagation()
|
|
85
86
|
const value = element.value
|
|
86
87
|
if (!value || !this.isValidUrl(value)) return
|
|
87
88
|
this.uploadClick.next(value)
|
|
@@ -2,6 +2,7 @@ import * as TOML from '@ltd/j-toml'
|
|
|
2
2
|
import {
|
|
3
3
|
checkMetadataLanguage,
|
|
4
4
|
checkNewRecordDefaultLanguage,
|
|
5
|
+
checkNewRecordStandard,
|
|
5
6
|
parseConfigSection,
|
|
6
7
|
parseMultiConfigSection,
|
|
7
8
|
parseTranslationsConfigSection,
|
|
@@ -297,7 +298,7 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
297
298
|
parsed,
|
|
298
299
|
'editing',
|
|
299
300
|
[],
|
|
300
|
-
['new_record_default_language'],
|
|
301
|
+
['new_record_default_language', 'new_record_standard'],
|
|
301
302
|
warnings,
|
|
302
303
|
errors
|
|
303
304
|
)
|
|
@@ -310,6 +311,15 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
310
311
|
warnings
|
|
311
312
|
)
|
|
312
313
|
}
|
|
314
|
+
if (
|
|
315
|
+
parsedEditingSection !== null &&
|
|
316
|
+
parsedEditingSection.new_record_standard !== undefined
|
|
317
|
+
) {
|
|
318
|
+
parsedEditingSection = checkNewRecordStandard(
|
|
319
|
+
parsedEditingSection,
|
|
320
|
+
warnings
|
|
321
|
+
)
|
|
322
|
+
}
|
|
313
323
|
editorConfig =
|
|
314
324
|
parsedEditingSection === null
|
|
315
325
|
? null
|
|
@@ -318,6 +328,9 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
318
328
|
parsedEditingSection.new_record_default_language as
|
|
319
329
|
| string
|
|
320
330
|
| undefined,
|
|
331
|
+
NEW_RECORD_STANDARD: parsedEditingSection.new_record_standard as
|
|
332
|
+
| EditorConfig['NEW_RECORD_STANDARD']
|
|
333
|
+
| undefined,
|
|
321
334
|
} as EditorConfig)
|
|
322
335
|
|
|
323
336
|
customTranslations = parseTranslationsConfigSection(
|
|
@@ -69,8 +69,11 @@ export interface MetadataQualityConfig {
|
|
|
69
69
|
ENABLED: boolean
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export type NewRecordStandard = 'iso19139' | 'iso19115-3'
|
|
73
|
+
|
|
72
74
|
export interface EditorConfig {
|
|
73
75
|
NEW_RECORD_DEFAULT_LANGUAGE?: string
|
|
76
|
+
NEW_RECORD_STANDARD?: NewRecordStandard
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export type CustomTranslations = { [translationKey: string]: string }
|
|
@@ -159,3 +159,30 @@ export function checkNewRecordDefaultLanguage(
|
|
|
159
159
|
new_record_default_language: lang2,
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
export function checkNewRecordStandard(
|
|
164
|
+
parsedConfigSection: any,
|
|
165
|
+
outWarnings: string[]
|
|
166
|
+
) {
|
|
167
|
+
const standard = parsedConfigSection.new_record_standard
|
|
168
|
+
const normalizedStandard =
|
|
169
|
+
typeof standard === 'string' ? standard.trim().toLowerCase() : null
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
normalizedStandard === 'iso19139' ||
|
|
173
|
+
normalizedStandard === 'iso19115-3'
|
|
174
|
+
) {
|
|
175
|
+
return {
|
|
176
|
+
...parsedConfigSection,
|
|
177
|
+
new_record_standard: normalizedStandard,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
outWarnings.push(
|
|
182
|
+
`In the [editing] section: new_record_standard = "${standard}" is not a supported metadata standard`
|
|
183
|
+
)
|
|
184
|
+
return {
|
|
185
|
+
...parsedConfigSection,
|
|
186
|
+
new_record_standard: undefined,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -12,6 +12,15 @@ export function getIndividualDisplayName(individual: Individual): string {
|
|
|
12
12
|
return individual.organization?.name ?? individual.email ?? ''
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function getAddressLines(address: string | undefined): string[] {
|
|
16
|
+
return address
|
|
17
|
+
? address
|
|
18
|
+
.split(',')
|
|
19
|
+
.map((part) => part.trim())
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
: []
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
export function toIndividual(user: UserModel): Individual {
|
|
16
25
|
return {
|
|
17
26
|
firstName: user.name,
|
package/translations/de.json
CHANGED
|
@@ -150,6 +150,14 @@
|
|
|
150
150
|
"editor.record.form.draft.updateAlert": "Seit der Erstellung dieses Entwurfs wurde dieser Datensatz am { date } von { user } geändert. Durch das Veröffentlichen Ihrer Version können dessen Änderungen gelöscht werden. Um dies zu vermeiden, können Sie Ihre Änderungen rückgängig machen oder Ihre Version in voller Kenntnis der Sachlage veröffentlichen.",
|
|
151
151
|
"editor.record.form.field.abstract": "Kurzbeschreibung",
|
|
152
152
|
"editor.record.form.field.constraintsShortcuts": "",
|
|
153
|
+
"editor.record.form.field.contactDetails.email": "",
|
|
154
|
+
"editor.record.form.field.contactDetails.email.placeholder": "",
|
|
155
|
+
"editor.record.form.field.contactDetails.firstName": "",
|
|
156
|
+
"editor.record.form.field.contactDetails.firstName.placeholder": "",
|
|
157
|
+
"editor.record.form.field.contactDetails.lastName": "",
|
|
158
|
+
"editor.record.form.field.contactDetails.lastName.placeholder": "",
|
|
159
|
+
"editor.record.form.field.contactDetails.organization": "",
|
|
160
|
+
"editor.record.form.field.contactDetails.organization.placeholder": "",
|
|
153
161
|
"editor.record.form.field.contacts": "Ansprechpartner - Email",
|
|
154
162
|
"editor.record.form.field.contacts.noContact": "Bitte geben Sie mindestens einen Ansprechpartner an.",
|
|
155
163
|
"editor.record.form.field.contacts.placeholder": "Kontakt auswählen",
|
|
@@ -495,7 +503,6 @@
|
|
|
495
503
|
"record.metadata.preview.config.idle": "Standardvorschau festlegen",
|
|
496
504
|
"record.metadata.preview.config.saved": "Gespeichert!",
|
|
497
505
|
"record.metadata.preview.config.saving": "Speichern...",
|
|
498
|
-
"record.metadata.producer": "Datenproduzent",
|
|
499
506
|
"record.metadata.publication": "Veröffentlichungsdatum",
|
|
500
507
|
"record.metadata.publications": "{count, plural, =0{Veröffentlichungsdatum} one{Veröffentlichungsdatum} other{Veröffentlichungen}}",
|
|
501
508
|
"record.metadata.quality": "Metadatenqualität",
|
package/translations/en.json
CHANGED
|
@@ -150,6 +150,14 @@
|
|
|
150
150
|
"editor.record.form.draft.updateAlert": "Since you created this draft, the dataset has been updated on { date } by { user }. Publishing your draft might erase their edits. To avoid this, you need to either cancel your changes or knowingly publish your own version.",
|
|
151
151
|
"editor.record.form.field.abstract": "Abstract",
|
|
152
152
|
"editor.record.form.field.constraintsShortcuts": "",
|
|
153
|
+
"editor.record.form.field.contactDetails.email": "Contact email",
|
|
154
|
+
"editor.record.form.field.contactDetails.email.placeholder": "example@domainname.com",
|
|
155
|
+
"editor.record.form.field.contactDetails.firstName": "Contact first name",
|
|
156
|
+
"editor.record.form.field.contactDetails.firstName.placeholder": "First name",
|
|
157
|
+
"editor.record.form.field.contactDetails.lastName": "Contact last name",
|
|
158
|
+
"editor.record.form.field.contactDetails.lastName.placeholder": "Last name",
|
|
159
|
+
"editor.record.form.field.contactDetails.organization": "Contact organization",
|
|
160
|
+
"editor.record.form.field.contactDetails.organization.placeholder": "Company",
|
|
153
161
|
"editor.record.form.field.contacts": "Point of contact - Email",
|
|
154
162
|
"editor.record.form.field.contacts.noContact": "Please provide at least one point of contact.",
|
|
155
163
|
"editor.record.form.field.contacts.placeholder": "Choose a contact",
|
|
@@ -495,7 +503,6 @@
|
|
|
495
503
|
"record.metadata.preview.config.idle": "Set as default preview",
|
|
496
504
|
"record.metadata.preview.config.saved": "Saved !",
|
|
497
505
|
"record.metadata.preview.config.saving": "Saving...",
|
|
498
|
-
"record.metadata.producer": "Data producer",
|
|
499
506
|
"record.metadata.publication": "Date of publication",
|
|
500
507
|
"record.metadata.publications": "{count, plural, =0{publication} one{publication} other{publications}}",
|
|
501
508
|
"record.metadata.quality": "Metadata Quality",
|