geonetwork-ui 2.9.0 → 2.10.0-dev.122ccbde0
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 +1049 -915
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +122 -17
- 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/common/domain/src/index.ts +1 -0
- package/src/libs/feature/editor/src/index.ts +2 -0
- 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/online-service-resource-input/online-service-resource-input.component.html +6 -1
- package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +16 -1
- 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/inputs/src/lib/url-input/url-input.component.ts +4 -1
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
- package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +11 -10
- 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
|
@@ -4,9 +4,10 @@ import * as EditorActions from './editor.actions'
|
|
|
4
4
|
import * as EditorSelectors from './editor.selectors'
|
|
5
5
|
import {
|
|
6
6
|
CatalogRecord,
|
|
7
|
+
CatalogRecordKeys,
|
|
7
8
|
LanguageCode,
|
|
8
9
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
9
|
-
import { filter } from 'rxjs'
|
|
10
|
+
import { filter, map } from 'rxjs'
|
|
10
11
|
import { Actions, ofType } from '@ngrx/effects'
|
|
11
12
|
import { EditorConfig, EditorFieldIdentification } from '../models'
|
|
12
13
|
|
|
@@ -37,6 +38,10 @@ export class EditorFacade {
|
|
|
37
38
|
)
|
|
38
39
|
isPublished$ = this.store.pipe(select(EditorSelectors.selectIsPublished))
|
|
39
40
|
canEditRecord$ = this.store.pipe(select(EditorSelectors.selectCanEditRecord))
|
|
41
|
+
focusedField$ = this.actions$.pipe(
|
|
42
|
+
ofType(EditorActions.setFocusedField),
|
|
43
|
+
map(({ model }) => model)
|
|
44
|
+
)
|
|
40
45
|
|
|
41
46
|
openRecord(record: CatalogRecord, recordSource: string) {
|
|
42
47
|
this.store.dispatch(
|
|
@@ -77,6 +82,10 @@ export class EditorFacade {
|
|
|
77
82
|
this.store.dispatch(EditorActions.setCurrentPage({ page }))
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
setFocusedField(model: CatalogRecordKeys) {
|
|
86
|
+
this.store.dispatch(EditorActions.setFocusedField({ model }))
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
setFieldVisibility(field: EditorFieldIdentification, visible: boolean) {
|
|
81
90
|
this.store.dispatch(EditorActions.setFieldVisibility({ field, visible }))
|
|
82
91
|
}
|
|
@@ -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
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
[disabled]="disabled"
|
|
21
21
|
(change)="resetAllFormFields()"
|
|
22
22
|
>
|
|
23
|
-
@for (protocolOption of
|
|
23
|
+
@for (protocolOption of availableProtocolOptions; track protocolOption) {
|
|
24
24
|
<mat-radio-button [value]="protocolOption.value">
|
|
25
25
|
{{ protocolOption.label | translate }}
|
|
26
26
|
</mat-radio-button>
|
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
}
|
|
46
46
|
</gn-ui-url-input>
|
|
47
47
|
|
|
48
|
+
@if (loading) {
|
|
49
|
+
<div class="flex justify-center w-full py-4">
|
|
50
|
+
<gn-ui-spinning-loader></gn-ui-spinning-loader>
|
|
51
|
+
</div>
|
|
52
|
+
}
|
|
48
53
|
@if (errorMessage) {
|
|
49
54
|
<p class="text-sm text-red-500 pl-4" translate>
|
|
50
55
|
editor.record.form.field.onlineResource.edit.identifier.error
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
TextInputComponent,
|
|
23
23
|
UrlInputComponent,
|
|
24
24
|
} from '../../../../../../../libs/ui/inputs/src'
|
|
25
|
+
import { SpinningLoaderComponent } from '../../../../../../../libs/ui/widgets/src'
|
|
25
26
|
import { createFuzzyFilter, getLayers } from '../../../../../../../libs/util/shared/src'
|
|
26
27
|
import {
|
|
27
28
|
NgIconComponent,
|
|
@@ -57,6 +58,7 @@ marker(
|
|
|
57
58
|
MatTooltipModule,
|
|
58
59
|
MatRadioModule,
|
|
59
60
|
NgIconComponent,
|
|
61
|
+
SpinningLoaderComponent,
|
|
60
62
|
TextInputComponent,
|
|
61
63
|
TranslateDirective,
|
|
62
64
|
TranslatePipe,
|
|
@@ -79,17 +81,19 @@ export class OnlineServiceResourceInputComponent {
|
|
|
79
81
|
@Input() protocolHint?: string
|
|
80
82
|
@Input() disabled? = false
|
|
81
83
|
@Input() modifyMode? = false
|
|
84
|
+
@Input() protocolOptions?: ServiceProtocol[]
|
|
82
85
|
@Output() serviceChange: EventEmitter<DatasetServiceDistribution> =
|
|
83
86
|
new EventEmitter()
|
|
84
87
|
|
|
85
88
|
errorMessage = false
|
|
89
|
+
loading = false
|
|
86
90
|
resetUrlOnChange = Math.random()
|
|
87
91
|
|
|
88
92
|
layersSubject = new BehaviorSubject<{ name?: string; title?: string }[]>([])
|
|
89
93
|
layers$: Observable<{ name?: string; title?: string }[]> =
|
|
90
94
|
this.layersSubject.asObservable()
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
allProtocolOptions: {
|
|
93
97
|
label: string
|
|
94
98
|
value: ServiceProtocol
|
|
95
99
|
}[] = [
|
|
@@ -123,6 +127,13 @@ export class OnlineServiceResourceInputComponent {
|
|
|
123
127
|
},
|
|
124
128
|
]
|
|
125
129
|
|
|
130
|
+
get availableProtocolOptions() {
|
|
131
|
+
if (!this.protocolOptions) return this.allProtocolOptions
|
|
132
|
+
return this.protocolOptions.flatMap(
|
|
133
|
+
(v) => this.allProtocolOptions.find((o) => o.value === v) ?? []
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
126
137
|
get activeLayerSuggestion() {
|
|
127
138
|
return !['wps', 'GPFDL', 'esriRest', 'other'].includes(
|
|
128
139
|
this._service.accessServiceProtocol
|
|
@@ -135,6 +146,8 @@ export class OnlineServiceResourceInputComponent {
|
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
async handleUploadClick(url: string) {
|
|
149
|
+
this.loading = true
|
|
150
|
+
this.cdr.detectChanges()
|
|
138
151
|
try {
|
|
139
152
|
const layers = await getLayers(url, this._service.accessServiceProtocol)
|
|
140
153
|
|
|
@@ -148,6 +161,7 @@ export class OnlineServiceResourceInputComponent {
|
|
|
148
161
|
this.layersSubject.next([])
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
this.loading = false
|
|
151
165
|
this.cdr.detectChanges()
|
|
152
166
|
}
|
|
153
167
|
|
|
@@ -159,6 +173,7 @@ export class OnlineServiceResourceInputComponent {
|
|
|
159
173
|
|
|
160
174
|
resetLayersSuggestion() {
|
|
161
175
|
this.errorMessage = false
|
|
176
|
+
this.loading = false
|
|
162
177
|
this.layersSubject.next([])
|
|
163
178
|
this._service.identifierInService = null
|
|
164
179
|
}
|
|
@@ -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 }>
|