geonetwork-ui 2.10.0-dev.9b93be024 → 2.10.0-dev.a9cc01fc7
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 +223 -101
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +51 -16
- package/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +14 -2
- package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
- package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -0
- package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +0 -1
- package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +10 -1
- package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
- package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +34 -34
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +8 -15
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +6 -4
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +8 -7
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +6 -6
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +3 -0
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -0
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +57 -3
- package/src/libs/ui/elements/src/index.ts +1 -0
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +16 -0
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +26 -0
- package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
- package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +27 -66
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
- package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
- package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
- package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/src/libs/util/shared/src/lib/utils/user-display.ts +23 -0
- package/tailwind.base.css +11 -2
- package/translations/de.json +1 -0
- package/translations/en.json +1 -0
- package/translations/es.json +1 -0
- package/translations/fr.json +1 -0
- package/translations/it.json +1 -0
- package/translations/nl.json +1 -0
- package/translations/pt.json +1 -0
- package/translations/sk.json +1 -0
|
@@ -7,11 +7,26 @@
|
|
|
7
7
|
>editor.record.form.metadataQuality.title</span
|
|
8
8
|
>
|
|
9
9
|
</div>
|
|
10
|
-
@for (
|
|
10
|
+
@for (
|
|
11
|
+
properties of (propertiesByPage$ | async) ?? [];
|
|
12
|
+
track properties;
|
|
13
|
+
let isLast = $last
|
|
14
|
+
) {
|
|
11
15
|
<div class="flex flex-col gap-2">
|
|
12
16
|
@for (property of properties; track property) {
|
|
13
17
|
<gn-ui-button
|
|
14
|
-
|
|
18
|
+
style="
|
|
19
|
+
--gn-ui-button-justify: space-between;
|
|
20
|
+
--gn-ui-button-height: 34px;
|
|
21
|
+
--gn-ui-button-width: 100%;
|
|
22
|
+
--gn-ui-button-border-width: 0;
|
|
23
|
+
--gn-ui-button-color: black;
|
|
24
|
+
--gn-ui-button-background: transparent;
|
|
25
|
+
--gn-ui-button-bg-hover: #f3f4f6;
|
|
26
|
+
--gn-ui-button-disabled-opacity: 1;
|
|
27
|
+
"
|
|
28
|
+
[disabled]="property.value"
|
|
29
|
+
(buttonClick)="onCriterionClick(property)"
|
|
15
30
|
type="outline"
|
|
16
31
|
attr.data-cy="md-quality-btn-{{ property.label }}"
|
|
17
32
|
>
|
|
@@ -28,7 +43,7 @@
|
|
|
28
43
|
</div>
|
|
29
44
|
</gn-ui-button>
|
|
30
45
|
}
|
|
31
|
-
@if (
|
|
46
|
+
@if (!isLast) {
|
|
32
47
|
<hr class="border-gray-300 w-11/12 mx-auto" />
|
|
33
48
|
}
|
|
34
49
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Component,
|
|
2
|
-
import {
|
|
1
|
+
import { Component, inject } from '@angular/core'
|
|
2
|
+
import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
3
3
|
import { ButtonComponent } from '../../../../../../../libs/ui/inputs/src'
|
|
4
4
|
import {
|
|
5
5
|
getAllKeysValidator,
|
|
@@ -13,8 +13,10 @@ import {
|
|
|
13
13
|
} from '@ng-icons/core'
|
|
14
14
|
import { iconoirBadgeCheck, iconoirSystemShut } from '@ng-icons/iconoir'
|
|
15
15
|
import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
|
|
16
|
-
import { EditorConfig } from '../../models'
|
|
17
16
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
17
|
+
import { EditorFacade } from '../../+state/editor.facade'
|
|
18
|
+
import { combineLatest, map } from 'rxjs'
|
|
19
|
+
import { AsyncPipe } from '@angular/common'
|
|
18
20
|
|
|
19
21
|
//forced translations that are not available in fields.config.ts
|
|
20
22
|
marker('editor.record.form.field.keywords')
|
|
@@ -29,6 +31,7 @@ marker('editor.record.form.field.organisation')
|
|
|
29
31
|
TranslatePipe,
|
|
30
32
|
ButtonComponent,
|
|
31
33
|
NgIconComponent,
|
|
34
|
+
AsyncPipe,
|
|
32
35
|
],
|
|
33
36
|
providers: [
|
|
34
37
|
provideIcons({
|
|
@@ -42,47 +45,37 @@ marker('editor.record.form.field.organisation')
|
|
|
42
45
|
templateUrl: './metadata-quality-panel.component.html',
|
|
43
46
|
styleUrl: './metadata-quality-panel.component.css',
|
|
44
47
|
})
|
|
45
|
-
export class MetadataQualityPanelComponent
|
|
48
|
+
export class MetadataQualityPanelComponent {
|
|
49
|
+
facade = inject(EditorFacade)
|
|
46
50
|
propsToValidate: ValidatorMapperKeys[] = getAllKeysValidator()
|
|
47
|
-
propertiesByPage: { label: string; value: boolean }[][] = []
|
|
48
|
-
@Input() editorConfig: EditorConfig
|
|
49
|
-
@Input() record: CatalogRecord
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.record,
|
|
71
|
-
fields as ValidatorMapperKeys[]
|
|
72
|
-
).map(({ name, validator }) => ({
|
|
73
|
-
label: `editor.record.form.field.${name}`, // use same translations as in fields.config.ts
|
|
74
|
-
value: validator(),
|
|
75
|
-
}))
|
|
52
|
+
propertiesByPage$ = combineLatest([
|
|
53
|
+
this.facade.editorConfig$,
|
|
54
|
+
this.facade.record$,
|
|
55
|
+
]).pipe(
|
|
56
|
+
map(([editorConfig, record]) => {
|
|
57
|
+
if (!editorConfig || !record) return []
|
|
58
|
+
const validators = getQualityValidators(record, this.propsToValidate)
|
|
59
|
+
return editorConfig.pages
|
|
60
|
+
.map((page) =>
|
|
61
|
+
page.sections
|
|
62
|
+
.flatMap((section) => section.fields)
|
|
63
|
+
.flatMap(({ model }) =>
|
|
64
|
+
validators.filter((v) => (v.alias ?? v.name) === model)
|
|
65
|
+
)
|
|
66
|
+
.map(({ name, validator, alias }) => ({
|
|
67
|
+
label: `editor.record.form.field.${name}`,
|
|
68
|
+
value: validator(),
|
|
69
|
+
model: (alias ?? name) as CatalogRecordKeys,
|
|
70
|
+
}))
|
|
76
71
|
)
|
|
77
72
|
.filter((arr) => arr.length > 0)
|
|
78
|
-
}
|
|
79
|
-
|
|
73
|
+
})
|
|
74
|
+
)
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
? `${baseClasses} bg-neutral-100 hover:bg-neutral-100`
|
|
86
|
-
: `${baseClasses} bg-transparent hover:bg-transparent`
|
|
76
|
+
onCriterionClick(property: { value: boolean; model: CatalogRecordKeys }) {
|
|
77
|
+
if (!property.value) {
|
|
78
|
+
this.facade.setFocusedField(property.model)
|
|
79
|
+
}
|
|
87
80
|
}
|
|
88
81
|
}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
4
|
OnDestroy,
|
|
5
|
-
|
|
5
|
+
afterNextRender,
|
|
6
6
|
inject,
|
|
7
7
|
} from '@angular/core'
|
|
8
8
|
import { CommonModule } from '@angular/common'
|
|
@@ -67,9 +67,7 @@ export type ConstraintChoice =
|
|
|
67
67
|
styleUrls: ['./form-field-constraints-shortcuts.component.css'],
|
|
68
68
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
69
69
|
})
|
|
70
|
-
export class FormFieldConstraintsShortcutsComponent
|
|
71
|
-
implements OnInit, OnDestroy
|
|
72
|
-
{
|
|
70
|
+
export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
|
|
73
71
|
private editorFacade = inject(EditorFacade)
|
|
74
72
|
|
|
75
73
|
legalConstraints$ = this.editorFacade.record$.pipe(
|
|
@@ -112,37 +110,39 @@ export class FormFieldConstraintsShortcutsComponent
|
|
|
112
110
|
|
|
113
111
|
onDestroy$ = new Subject<void>()
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
constructor() {
|
|
114
|
+
// Deferred to afterNextRender to avoid dispatching store actions
|
|
115
|
+
// synchronously during Angular's change detection cycle (NG0100)
|
|
116
|
+
afterNextRender(() => {
|
|
117
|
+
this.anyToggleActivated$
|
|
118
|
+
.pipe(takeUntil(this.onDestroy$), distinctUntilChanged())
|
|
119
|
+
.subscribe((anyToggleActivated) => {
|
|
120
|
+
if (anyToggleActivated) {
|
|
121
|
+
this.hideAllConstraintSections()
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
125
|
+
const hideEmptyConstraints = (
|
|
126
|
+
constraints$: Observable<Constraint[]>,
|
|
127
|
+
model: ConstraintChoice
|
|
128
|
+
) => {
|
|
129
|
+
const isConstraintNotEmpty$ = constraints$.pipe(
|
|
130
|
+
takeUntil(this.onDestroy$),
|
|
131
|
+
map((c) => c.length > 0),
|
|
132
|
+
distinctUntilChanged()
|
|
133
|
+
)
|
|
134
|
+
combineLatest([
|
|
135
|
+
isConstraintNotEmpty$,
|
|
136
|
+
this.anyToggleActivated$,
|
|
137
|
+
]).subscribe(([isNotEmpty, anyToggleActivated]) => {
|
|
138
|
+
const visible = isNotEmpty && !anyToggleActivated
|
|
139
|
+
this.editorFacade.setFieldVisibility({ model }, visible)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
hideEmptyConstraints(this.legalConstraints$, 'legalConstraints')
|
|
143
|
+
hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
|
|
144
|
+
hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
|
|
145
|
+
})
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
ngOnDestroy() {
|
|
@@ -10,21 +10,14 @@
|
|
|
10
10
|
</gn-ui-autocomplete>
|
|
11
11
|
|
|
12
12
|
@if (contacts.length > 0) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<gn-ui-
|
|
20
|
-
|
|
21
|
-
(itemsOrderChange)="handleContactsChanged($event)"
|
|
22
|
-
[elementTemplate]="contactTemplate"
|
|
23
|
-
></gn-ui-sortable-list>
|
|
24
|
-
<ng-template #contactTemplate let-contact>
|
|
25
|
-
<gn-ui-contact-card [contact]="contact"></gn-ui-contact-card>
|
|
26
|
-
</ng-template>
|
|
27
|
-
}
|
|
13
|
+
<gn-ui-sortable-list
|
|
14
|
+
[items]="contacts"
|
|
15
|
+
(itemsOrderChange)="handleContactsChanged($event)"
|
|
16
|
+
[elementTemplate]="contactTemplate"
|
|
17
|
+
></gn-ui-sortable-list>
|
|
18
|
+
<ng-template #contactTemplate let-contact>
|
|
19
|
+
<gn-ui-contact-card [contact]="contact"></gn-ui-contact-card>
|
|
20
|
+
</ng-template>
|
|
28
21
|
} @else {
|
|
29
22
|
<div
|
|
30
23
|
class="p-4 text-sm border border-primary bg-primary-lightest rounded-lg"
|
|
@@ -28,7 +28,11 @@ 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 {
|
|
31
|
+
import {
|
|
32
|
+
createFuzzyFilter,
|
|
33
|
+
getIndividualDisplayName,
|
|
34
|
+
toIndividual,
|
|
35
|
+
} from '../../../../../../../../../libs/util/shared/src'
|
|
32
36
|
import { map } from 'rxjs/operators'
|
|
33
37
|
import { SortableListComponent } from '../../../../../../../../../libs/ui/layout/src'
|
|
34
38
|
|
|
@@ -117,9 +121,7 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges {
|
|
|
117
121
|
* gn-ui-autocomplete
|
|
118
122
|
*/
|
|
119
123
|
displayWithFn: (user: UserModel) => string = (user) =>
|
|
120
|
-
|
|
121
|
-
user.organisation ? `(${user.organisation})` : ''
|
|
122
|
-
}`
|
|
124
|
+
getIndividualDisplayName(toIndividual(user))
|
|
123
125
|
|
|
124
126
|
/**
|
|
125
127
|
* gn-ui-autocomplete
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
</gn-ui-button>
|
|
9
9
|
}
|
|
10
10
|
</div>
|
|
11
|
+
@if (value.length === 0) {
|
|
12
|
+
<div
|
|
13
|
+
class="p-4 border border-primary bg-primary-lightest rounded-lg"
|
|
14
|
+
translate
|
|
15
|
+
>
|
|
16
|
+
editor.record.form.field.contactsForResource.noContact
|
|
17
|
+
</div>
|
|
18
|
+
}
|
|
11
19
|
@if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {
|
|
12
20
|
<div class="mt-8" data-test="displayedRoles">
|
|
13
21
|
@for (
|
|
@@ -53,12 +61,5 @@
|
|
|
53
61
|
</div>
|
|
54
62
|
}
|
|
55
63
|
</div>
|
|
56
|
-
} @else {
|
|
57
|
-
<div
|
|
58
|
-
class="p-4 border border-primary bg-primary-lightest rounded-lg"
|
|
59
|
-
translate
|
|
60
|
-
>
|
|
61
|
-
editor.record.form.field.contactsForResource.noContact
|
|
62
|
-
</div>
|
|
63
64
|
}
|
|
64
65
|
</div>
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
AutocompleteComponent,
|
|
24
24
|
ButtonComponent,
|
|
25
25
|
} from '../../../../../../../../../libs/ui/inputs/src'
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createFuzzyFilter,
|
|
28
|
+
getIndividualDisplayName,
|
|
29
|
+
toIndividual,
|
|
30
|
+
} from '../../../../../../../../../libs/util/shared/src'
|
|
27
31
|
import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
|
|
28
32
|
import {
|
|
29
33
|
debounceTime,
|
|
@@ -169,11 +173,7 @@ export class FormFieldContactsForResourceComponent
|
|
|
169
173
|
* gn-ui-autocomplete
|
|
170
174
|
*/
|
|
171
175
|
displayWithFn: (user: UserModel) => string = (user) =>
|
|
172
|
-
user
|
|
173
|
-
? `${user.name} ${user.surname} ${
|
|
174
|
-
user.organisation ? `(${user.organisation})` : ''
|
|
175
|
-
}`
|
|
176
|
-
: ``
|
|
176
|
+
getIndividualDisplayName(toIndividual(user))
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
179
|
* gn-ui-autocomplete
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { CommonModule } from '@angular/common'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
ElementRef,
|
|
6
|
+
inject,
|
|
7
|
+
OnDestroy,
|
|
8
|
+
OnInit,
|
|
9
|
+
} from '@angular/core'
|
|
3
10
|
import { EditorFacade } from '../../+state/editor.facade'
|
|
4
11
|
import { EditorFieldValue } from '../../models'
|
|
5
12
|
import { FormFieldComponent } from './form-field'
|
|
@@ -8,7 +15,7 @@ import {
|
|
|
8
15
|
EditorFieldWithValue,
|
|
9
16
|
EditorSectionWithValues,
|
|
10
17
|
} from '../../+state/editor.models'
|
|
11
|
-
import { map } from 'rxjs'
|
|
18
|
+
import { filter, firstValueFrom, map, Subscription, switchMap } from 'rxjs'
|
|
12
19
|
import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
13
20
|
|
|
14
21
|
@Component({
|
|
@@ -19,13 +26,50 @@ import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/l
|
|
|
19
26
|
standalone: true,
|
|
20
27
|
imports: [CommonModule, FormFieldComponent, TranslateDirective],
|
|
21
28
|
})
|
|
22
|
-
export class RecordFormComponent {
|
|
29
|
+
export class RecordFormComponent implements OnInit, OnDestroy {
|
|
30
|
+
anchorIdPrefix = 'gn-ui--field-'
|
|
23
31
|
facade = inject(EditorFacade)
|
|
32
|
+
private el = inject(ElementRef)
|
|
33
|
+
subscription = new Subscription()
|
|
24
34
|
|
|
25
35
|
recordUniqueIdentifier$ = this.facade.record$.pipe(
|
|
26
36
|
map((record) => record.uniqueIdentifier)
|
|
27
37
|
)
|
|
28
38
|
|
|
39
|
+
ngOnInit() {
|
|
40
|
+
this.subscription.add(
|
|
41
|
+
this.facade.focusedField$
|
|
42
|
+
.pipe(
|
|
43
|
+
filter((field) => !!field),
|
|
44
|
+
switchMap(async (field) => ({
|
|
45
|
+
field: field as CatalogRecordKeys,
|
|
46
|
+
pageIndex: await this.getPageIndexForField(
|
|
47
|
+
field as CatalogRecordKeys
|
|
48
|
+
),
|
|
49
|
+
}))
|
|
50
|
+
)
|
|
51
|
+
.subscribe(async ({ field, pageIndex }) => {
|
|
52
|
+
const currentPage = await firstValueFrom(this.facade.currentPage$)
|
|
53
|
+
if (pageIndex !== null && pageIndex !== currentPage) {
|
|
54
|
+
this.facade.setCurrentPage(pageIndex)
|
|
55
|
+
this.el.nativeElement.scrollIntoView({
|
|
56
|
+
behavior: 'instant',
|
|
57
|
+
block: 'start',
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
setTimeout(() =>
|
|
61
|
+
document
|
|
62
|
+
.getElementById(this.anchorIdPrefix + field)
|
|
63
|
+
?.scrollIntoView({ behavior: 'instant', block: 'start' })
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ngOnDestroy() {
|
|
70
|
+
this.subscription.unsubscribe()
|
|
71
|
+
}
|
|
72
|
+
|
|
29
73
|
handleFieldValueChange(model: CatalogRecordKeys, newValue: EditorFieldValue) {
|
|
30
74
|
if (!model) {
|
|
31
75
|
return
|
|
@@ -40,4 +84,14 @@ export class RecordFormComponent {
|
|
|
40
84
|
sectionTracker(index: number, section: EditorSectionWithValues) {
|
|
41
85
|
return section.labelKey
|
|
42
86
|
}
|
|
87
|
+
|
|
88
|
+
async getPageIndexForField(model: CatalogRecordKeys): Promise<number | null> {
|
|
89
|
+
const config = await firstValueFrom(this.facade.editorConfig$)
|
|
90
|
+
const pageIndex = config.pages.findIndex((page) =>
|
|
91
|
+
page.sections.some((section) =>
|
|
92
|
+
section.fields.some((field) => field.model === model)
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
return pageIndex >= 0 ? pageIndex : null
|
|
96
|
+
}
|
|
43
97
|
}
|
|
@@ -18,6 +18,7 @@ export * from './lib/metadata-doi/metadata-doi.component'
|
|
|
18
18
|
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
|
+
export * from './lib/contact-pill/contact-pill.component'
|
|
21
22
|
export * from './lib/notification/notification.component'
|
|
22
23
|
export * from './lib/record-api-form/record-api-form.component'
|
|
23
24
|
export * from './lib/thumbnail/thumbnail.component'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<gn-ui-button
|
|
2
|
+
type="primary-light"
|
|
3
|
+
extraClass="group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded"
|
|
4
|
+
data-test="contact-pill"
|
|
5
|
+
>
|
|
6
|
+
<span
|
|
7
|
+
class="font-title font-medium text-base leading-tight truncate group-hover:text-white"
|
|
8
|
+
[title]="displayName"
|
|
9
|
+
>{{ displayName }}</span
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="gn-ui-card-icon items-center justify-center w-10 h-8 group-hover:border-white group-hover:text-white"
|
|
13
|
+
>
|
|
14
|
+
<ng-icon class="!w-6 !h-6 !text-[24px]" name="matInfoOutline"></ng-icon>
|
|
15
|
+
</div>
|
|
16
|
+
</gn-ui-button>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
2
|
+
import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
3
|
+
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
4
|
+
import { matInfoOutline } from '@ng-icons/material-icons/outline'
|
|
5
|
+
import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
|
|
6
|
+
import { getIndividualDisplayName } from '../../../../../../libs/util/shared/src'
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'gn-ui-contact-pill',
|
|
10
|
+
templateUrl: './contact-pill.component.html',
|
|
11
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
12
|
+
standalone: true,
|
|
13
|
+
imports: [NgIcon, ButtonComponent],
|
|
14
|
+
viewProviders: [
|
|
15
|
+
provideIcons({
|
|
16
|
+
matInfoOutline,
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
})
|
|
20
|
+
export class ContactPillComponent {
|
|
21
|
+
@Input() contact: Individual
|
|
22
|
+
|
|
23
|
+
get displayName(): string {
|
|
24
|
+
return getIndividualDisplayName(this.contact)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -58,7 +58,6 @@ export class InternalLinkCardComponent implements OnInit {
|
|
|
58
58
|
protected elementRef = inject(ElementRef)
|
|
59
59
|
|
|
60
60
|
@Input() record: CatalogRecord
|
|
61
|
-
@Input() linkTarget = '_blank'
|
|
62
61
|
@Input() linkHref: string = null
|
|
63
62
|
@Input() metadataQualityDisplay: boolean
|
|
64
63
|
@Input() favoriteTemplate: TemplateRef<{ $implicit: CatalogRecord }>
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
<gn-ui-max-lines [maxLines]="7">
|
|
29
29
|
<div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
|
|
30
30
|
@for (keyword of metadata.keywords; track keyword) {
|
|
31
|
-
<gn-ui-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
<gn-ui-button
|
|
32
|
+
type="primary-light"
|
|
33
|
+
class="inline-block opacity-70"
|
|
34
|
+
extraClass="lowercase text-sm py-1 px-2"
|
|
35
|
+
(buttonClick)="onKeywordClick(keyword)"
|
|
36
|
+
>{{ keyword.label }}</gn-ui-button
|
|
36
37
|
>
|
|
37
38
|
}
|
|
38
39
|
</div>
|
|
@@ -105,9 +106,29 @@
|
|
|
105
106
|
</div>
|
|
106
107
|
</gn-ui-expandable-panel>
|
|
107
108
|
}
|
|
109
|
+
@if (metadata.contactsForResource?.length) {
|
|
110
|
+
<gn-ui-expandable-panel
|
|
111
|
+
[title]="'record.metadata.resource.contacts' | translate"
|
|
112
|
+
data-test="contacts-panel"
|
|
113
|
+
>
|
|
114
|
+
<div class="flex flex-col gap-1 pt-3 pb-4">
|
|
115
|
+
@for (group of contactGroups; track group.role) {
|
|
116
|
+
<div class="flex flex-col gap-1 rounded bg-gray-50 py-4 px-2">
|
|
117
|
+
<p class="text-xs font-normal text-black">
|
|
118
|
+
{{ group.roleLabel | translate }}
|
|
119
|
+
</p>
|
|
120
|
+
<div class="grid gap-0.5 grid-cols-1 md:grid-cols-2">
|
|
121
|
+
@for (contact of group.contacts; track contact.email) {
|
|
122
|
+
<gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
|
|
123
|
+
}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
}
|
|
127
|
+
</div>
|
|
128
|
+
</gn-ui-expandable-panel>
|
|
129
|
+
}
|
|
108
130
|
@if (
|
|
109
131
|
(metadata.kind === 'dataset' && metadata.lineage) ||
|
|
110
|
-
resourceContact ||
|
|
111
132
|
metadata.resourceCreated ||
|
|
112
133
|
metadata.resourcePublished ||
|
|
113
134
|
metadata.resourceUpdated ||
|
|
@@ -129,66 +150,6 @@
|
|
|
129
150
|
</p>
|
|
130
151
|
</div>
|
|
131
152
|
}
|
|
132
|
-
@if (resourceContact) {
|
|
133
|
-
<div
|
|
134
|
-
class="flex flex-row gap-6 mt-5 mb-8 resource-contact"
|
|
135
|
-
data-test="details-panel-resource-contact"
|
|
136
|
-
>
|
|
137
|
-
@if (resourceContact.organization?.logoUrl?.href) {
|
|
138
|
-
<div
|
|
139
|
-
class="flex items-center justify-center border-solid border border-gray-300 rounded-md bg-white h-32 overflow-hidden"
|
|
140
|
-
>
|
|
141
|
-
<gn-ui-thumbnail
|
|
142
|
-
class="relative h-full w-full"
|
|
143
|
-
[thumbnailUrl]="resourceContact.organization.logoUrl.href"
|
|
144
|
-
fit="contain"
|
|
145
|
-
></gn-ui-thumbnail>
|
|
146
|
-
</div>
|
|
147
|
-
}
|
|
148
|
-
<div class="flex flex-col gap-1">
|
|
149
|
-
<p class="text-sm font-medium" translate>record.metadata.producer</p>
|
|
150
|
-
<div
|
|
151
|
-
class="text-primary font-title text-21 mr-2 cursor-pointer hover:underline"
|
|
152
|
-
data-cy="organization-name"
|
|
153
|
-
>
|
|
154
|
-
{{ resourceContact.organization?.name }}
|
|
155
|
-
</div>
|
|
156
|
-
@if (resourceContact.organization?.website) {
|
|
157
|
-
<div>
|
|
158
|
-
<a
|
|
159
|
-
[href]="resourceContact.organization.website"
|
|
160
|
-
target="_blank"
|
|
161
|
-
class="contact-website text-primary text-sm cursor-pointer hover:underline transition-all"
|
|
162
|
-
>{{ resourceContact.organization.website }}
|
|
163
|
-
<ng-icon
|
|
164
|
-
class="!w-[12px] !h-[12px] !text-[12px] opacity-75 shrink-0"
|
|
165
|
-
name="matOpenInNew"
|
|
166
|
-
></ng-icon>
|
|
167
|
-
</a>
|
|
168
|
-
</div>
|
|
169
|
-
}
|
|
170
|
-
@if (resourceContact.email) {
|
|
171
|
-
<div class="mt-4">
|
|
172
|
-
<div class="flex">
|
|
173
|
-
<ng-icon
|
|
174
|
-
class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
|
|
175
|
-
name="matMailOutline"
|
|
176
|
-
></ng-icon>
|
|
177
|
-
@if (resourceContact.email) {
|
|
178
|
-
<a
|
|
179
|
-
[href]="'mailto:' + resourceContact.email"
|
|
180
|
-
class="text-sm hover:underline ml-2"
|
|
181
|
-
target="_blank"
|
|
182
|
-
data-cy="contact-email"
|
|
183
|
-
>{{ resourceContact?.email }}</a
|
|
184
|
-
>
|
|
185
|
-
}
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
}
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
}
|
|
192
153
|
<div
|
|
193
154
|
class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
|
|
194
155
|
>
|