geonetwork-ui 2.10.0-dev.6fa5006eb → 2.10.0-dev.7e58935b2
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 +270 -55
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +48 -16
- package/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -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/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 +36 -0
- package/src/libs/util/app-config/src/lib/model.ts +4 -0
- package/src/libs/util/app-config/src/lib/parse-utils.ts +23 -1
- 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
|
@@ -15,8 +15,15 @@
|
|
|
15
15
|
(itemsOrderChange)="handleContactsChanged($event)"
|
|
16
16
|
[elementTemplate]="contactTemplate"
|
|
17
17
|
></gn-ui-sortable-list>
|
|
18
|
-
<ng-template #contactTemplate let-contact>
|
|
19
|
-
|
|
18
|
+
<ng-template #contactTemplate let-contact let-index="index">
|
|
19
|
+
@if (modelSpecifier === 'contact:editableDetails') {
|
|
20
|
+
<gn-ui-contact-details-form
|
|
21
|
+
[contact]="contact"
|
|
22
|
+
(contactChange)="handleContactChanged($event, index)"
|
|
23
|
+
></gn-ui-contact-details-form>
|
|
24
|
+
} @else {
|
|
25
|
+
<gn-ui-contact-card [contact]="contact"></gn-ui-contact-card>
|
|
26
|
+
}
|
|
20
27
|
</ng-template>
|
|
21
28
|
} @else {
|
|
22
29
|
<div
|
|
@@ -28,6 +28,7 @@ import { UserModel } from '../../../../../../../../../libs/common/domain/src/lib
|
|
|
28
28
|
import { PlatformServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/platform.service.interface'
|
|
29
29
|
import { OrganizationsServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/organizations.service.interface'
|
|
30
30
|
import { ContactCardComponent } from '../../../contact-card/contact-card.component'
|
|
31
|
+
import { ContactDetailsFormComponent } from '../../../contact-details/contact-details-form.component'
|
|
31
32
|
import {
|
|
32
33
|
createFuzzyFilter,
|
|
33
34
|
getIndividualDisplayName,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
} from '../../../../../../../../../libs/util/shared/src'
|
|
36
37
|
import { map } from 'rxjs/operators'
|
|
37
38
|
import { SortableListComponent } from '../../../../../../../../../libs/ui/layout/src'
|
|
39
|
+
import { FieldModelSpecifier } from '../../../../models/editor-config.model'
|
|
38
40
|
|
|
39
41
|
@Component({
|
|
40
42
|
selector: 'gn-ui-form-field-contacts',
|
|
@@ -47,6 +49,7 @@ import { SortableListComponent } from '../../../../../../../../../libs/ui/layout
|
|
|
47
49
|
TranslateDirective,
|
|
48
50
|
TranslatePipe,
|
|
49
51
|
ContactCardComponent,
|
|
52
|
+
ContactDetailsFormComponent,
|
|
50
53
|
SortableListComponent,
|
|
51
54
|
],
|
|
52
55
|
})
|
|
@@ -56,6 +59,7 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges {
|
|
|
56
59
|
private changeDetectorRef = inject(ChangeDetectorRef)
|
|
57
60
|
|
|
58
61
|
@Input() value: Individual[]
|
|
62
|
+
@Input() modelSpecifier: FieldModelSpecifier
|
|
59
63
|
@Output() valueChange: EventEmitter<Individual[]> = new EventEmitter()
|
|
60
64
|
|
|
61
65
|
contacts: Individual[] = []
|
|
@@ -117,6 +121,14 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges {
|
|
|
117
121
|
this.valueChange.emit(contacts)
|
|
118
122
|
}
|
|
119
123
|
|
|
124
|
+
handleContactChanged(updatedContact: Individual, index: number) {
|
|
125
|
+
const contacts = this.contacts.map((contact, i) =>
|
|
126
|
+
i === index ? updatedContact : contact
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
this.handleContactsChanged(contacts)
|
|
130
|
+
}
|
|
131
|
+
|
|
120
132
|
/**
|
|
121
133
|
* gn-ui-autocomplete
|
|
122
134
|
*/
|
package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
scroll-margin-top: 90px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:host.gn-ui-field-focus-glow {
|
|
6
|
+
position: relative;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:host.gn-ui-field-focus-glow::after {
|
|
10
|
+
content: '';
|
|
11
|
+
position: absolute;
|
|
12
|
+
inset: -8px;
|
|
13
|
+
border-radius: 8px;
|
|
14
|
+
background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
animation: gn-ui-field-focus-glow 3s ease-out forwards;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:host.gn-ui-field-focus-glow::before {
|
|
20
|
+
content: '';
|
|
21
|
+
position: absolute;
|
|
22
|
+
inset: -16px;
|
|
23
|
+
border: 2px dashed var(--color-primary);
|
|
24
|
+
border-radius: 10px;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
animation: gn-ui-field-focus-glow 3s ease-out forwards;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@keyframes gn-ui-field-focus-glow {
|
|
30
|
+
0%,
|
|
31
|
+
55% {
|
|
32
|
+
opacity: 1;
|
|
33
|
+
}
|
|
34
|
+
100% {
|
|
35
|
+
opacity: 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
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'
|
|
@@ -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)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as TOML from '@ltd/j-toml'
|
|
2
2
|
import {
|
|
3
3
|
checkMetadataLanguage,
|
|
4
|
+
checkNewRecordDefaultLanguage,
|
|
4
5
|
parseConfigSection,
|
|
5
6
|
parseMultiConfigSection,
|
|
6
7
|
parseTranslationsConfigSection,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import {
|
|
9
10
|
CustomTranslations,
|
|
10
11
|
CustomTranslationsAllLanguages,
|
|
12
|
+
EditorConfig,
|
|
11
13
|
GlobalConfig,
|
|
12
14
|
LayerConfig,
|
|
13
15
|
MapConfig,
|
|
@@ -51,6 +53,12 @@ export function getOptionalSearchConfig(): SearchConfig | null {
|
|
|
51
53
|
return searchConfig
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
let editorConfig: EditorConfig = null
|
|
57
|
+
|
|
58
|
+
export function getOptionalEditorConfig(): EditorConfig | null {
|
|
59
|
+
return editorConfig
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
let metadataQualityConfig: MetadataQualityConfig = null
|
|
55
63
|
export function getMetadataQualityConfig(): MetadataQualityConfig {
|
|
56
64
|
return (
|
|
@@ -285,6 +293,33 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
285
293
|
SORTABLE: parsedMetadataQualitySection.sortable,
|
|
286
294
|
} as MetadataQualityConfig)
|
|
287
295
|
|
|
296
|
+
let parsedEditingSection = parseConfigSection(
|
|
297
|
+
parsed,
|
|
298
|
+
'editing',
|
|
299
|
+
[],
|
|
300
|
+
['new_record_default_language'],
|
|
301
|
+
warnings,
|
|
302
|
+
errors
|
|
303
|
+
)
|
|
304
|
+
if (
|
|
305
|
+
parsedEditingSection !== null &&
|
|
306
|
+
parsedEditingSection.new_record_default_language !== undefined
|
|
307
|
+
) {
|
|
308
|
+
parsedEditingSection = checkNewRecordDefaultLanguage(
|
|
309
|
+
parsedEditingSection,
|
|
310
|
+
warnings
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
editorConfig =
|
|
314
|
+
parsedEditingSection === null
|
|
315
|
+
? null
|
|
316
|
+
: ({
|
|
317
|
+
NEW_RECORD_DEFAULT_LANGUAGE:
|
|
318
|
+
parsedEditingSection.new_record_default_language as
|
|
319
|
+
| string
|
|
320
|
+
| undefined,
|
|
321
|
+
} as EditorConfig)
|
|
322
|
+
|
|
288
323
|
customTranslations = parseTranslationsConfigSection(
|
|
289
324
|
parsed,
|
|
290
325
|
'translations'
|
|
@@ -309,6 +344,7 @@ export function isConfigLoaded() {
|
|
|
309
344
|
export function _reset() {
|
|
310
345
|
globalConfig = null
|
|
311
346
|
themeConfig = null
|
|
347
|
+
editorConfig = null
|
|
312
348
|
customTranslations = null
|
|
313
349
|
}
|
|
314
350
|
|