geonetwork-ui 2.10.0-dev.e97ea6950 → 2.10.0-dev.fb2893f44
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 +1622 -6598
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +196 -87
- package/index.d.ts.map +1 -1
- package/material-styles.css +483 -0
- package/package.json +25 -35
- 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/gn4/gn4.field.mapper.ts +2 -2
- package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
- 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 +8 -4
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +68 -34
- package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
- package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
- package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
- package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +11 -5
- package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
- package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
- package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
- package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
- package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
- 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/constraint-card/constraint-card.component.ts +0 -2
- 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/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/field-focus.directive.ts +41 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +32 -34
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -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/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.css +0 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.html +5 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.ts +89 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -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 +35 -0
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -1
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +77 -5
- package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
- 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/feature/map/src/lib/utils/map-utils.service.ts +33 -0
- package/src/libs/feature/notify-reuse/src/index.ts +1 -0
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
- package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
- package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
- package/src/libs/ui/dataviz/src/index.ts +0 -1
- package/src/libs/ui/elements/src/index.ts +2 -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 +41 -0
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
- package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
- package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
- package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
- 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/markdown-parser/markdown-parser.component.ts +2 -2
- 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.css +0 -4
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +28 -67
- 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 +8 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +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/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 +15 -64
- package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
- package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
- package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
- package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
- package/src/libs/util/app-config/src/lib/model.ts +7 -0
- package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
- package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
- package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
- package/src/libs/util/i18n/src/index.ts +0 -1
- package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
- package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
- package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
- package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
- package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
- package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
- package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
- package/style.css +0 -1
- package/tailwind.base.css +19 -2
- package/translations/de.json +29 -20
- package/translations/en.json +29 -20
- package/translations/es.json +29 -20
- package/translations/fr.json +29 -20
- package/translations/it.json +29 -20
- package/translations/nl.json +29 -20
- package/translations/pt.json +29 -20
- package/translations/sk.json +29 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpClient, HttpEventType } from '@angular/common/http'
|
|
2
|
-
import { Injectable, InjectionToken
|
|
2
|
+
import { inject, Injectable, InjectionToken } from '@angular/core'
|
|
3
3
|
import {
|
|
4
4
|
KeywordApiResponse,
|
|
5
5
|
ThesaurusApiResponse,
|
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
} from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
13
13
|
import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
|
|
14
14
|
import { UserModel } from '../../../../../../../libs/common/domain/src/lib/model/user/user.model'
|
|
15
|
+
import { GroupModel } from '../../../../../../../libs/common/domain/src/lib/model/user/group.model'
|
|
15
16
|
import {
|
|
16
17
|
PlatformServiceInterface,
|
|
17
18
|
UploadEvent,
|
|
18
19
|
} from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
|
|
19
20
|
import {
|
|
21
|
+
GroupsApiService,
|
|
20
22
|
MeApiService,
|
|
21
23
|
RecordsApiService,
|
|
22
24
|
RegistriesApiService,
|
|
@@ -57,6 +59,7 @@ export const DISABLE_AUTH = new InjectionToken<boolean>('gnDisableAuth', {
|
|
|
57
59
|
export class Gn4PlatformService implements PlatformServiceInterface {
|
|
58
60
|
private meApi = inject(MeApiService)
|
|
59
61
|
private usersApi = inject(UsersApiService)
|
|
62
|
+
private groupsApi = inject(GroupsApiService)
|
|
60
63
|
private mapper = inject(Gn4PlatformMapper)
|
|
61
64
|
private toolsApiService = inject(ToolsApiService)
|
|
62
65
|
private registriesApiService = inject(RegistriesApiService)
|
|
@@ -97,7 +100,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
97
100
|
private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
|
|
98
101
|
|
|
99
102
|
private get lang3() {
|
|
100
|
-
return toLang3(this.translateService.
|
|
103
|
+
return toLang3(this.translateService.getCurrentLang())
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
constructor() {
|
|
@@ -155,6 +158,49 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
155
158
|
return this.users$
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
getUserPermissionsByGroup(): Observable<GroupModel[]> {
|
|
162
|
+
if (this.disableAuth) return of([])
|
|
163
|
+
return combineLatest([this.meApi.getMe(), this.groupsApi.getGroups()]).pipe(
|
|
164
|
+
map(([meResponse, groups]) => {
|
|
165
|
+
if (!meResponse) return []
|
|
166
|
+
if (meResponse.admin) {
|
|
167
|
+
return groups.map((group) => ({
|
|
168
|
+
groupId: group.id,
|
|
169
|
+
groupName: group.name,
|
|
170
|
+
isMember: true,
|
|
171
|
+
canEdit: true,
|
|
172
|
+
canApprove: true,
|
|
173
|
+
canAdministrate: true,
|
|
174
|
+
}))
|
|
175
|
+
}
|
|
176
|
+
const reviewerIds = meResponse.groupsWithReviewer ?? []
|
|
177
|
+
const editorIds = meResponse.groupsWithEditor ?? []
|
|
178
|
+
const memberIds = meResponse.groupsWithRegisteredUser ?? []
|
|
179
|
+
const adminIds = meResponse.groupsWithUserAdmin ?? []
|
|
180
|
+
const groupsById = new Map(groups.map((g) => [g.id, g]))
|
|
181
|
+
return [
|
|
182
|
+
...new Set([
|
|
183
|
+
...reviewerIds,
|
|
184
|
+
...editorIds,
|
|
185
|
+
...groups.map((g) => g.id),
|
|
186
|
+
]),
|
|
187
|
+
]
|
|
188
|
+
.filter((id) => groupsById.has(id))
|
|
189
|
+
.map((id) => {
|
|
190
|
+
const group = groupsById.get(id)
|
|
191
|
+
return {
|
|
192
|
+
groupId: group.id,
|
|
193
|
+
groupName: group.name,
|
|
194
|
+
isMember: memberIds.includes(id),
|
|
195
|
+
canEdit: editorIds.includes(id),
|
|
196
|
+
canApprove: reviewerIds.includes(id),
|
|
197
|
+
canAdministrate: adminIds.includes(id),
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
158
204
|
translateKey(key: string): Observable<string> {
|
|
159
205
|
// if the key is a URI, use the registries API to look for the translation
|
|
160
206
|
if (key.match(/^https?:\/\//)) {
|
|
@@ -108,11 +108,6 @@ export interface Keyword {
|
|
|
108
108
|
translations?: KeywordTranslations
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export interface INSPIRE_topic {
|
|
112
|
-
value: string
|
|
113
|
-
label: string
|
|
114
|
-
}
|
|
115
|
-
|
|
116
111
|
export interface ResourceIdentifier {
|
|
117
112
|
code: string
|
|
118
113
|
codeSpace?: string
|
|
@@ -254,10 +249,20 @@ export interface DatasetTemporalExtent {
|
|
|
254
249
|
end?: Date
|
|
255
250
|
}
|
|
256
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Represents a source dataset referenced from a lineage entry.
|
|
254
|
+
*/
|
|
255
|
+
export interface SourceRecord {
|
|
256
|
+
uuid?: string
|
|
257
|
+
title?: string
|
|
258
|
+
href?: string
|
|
259
|
+
}
|
|
260
|
+
|
|
257
261
|
export interface DatasetRecord extends BaseRecord {
|
|
258
262
|
kind: 'dataset'
|
|
259
263
|
status: RecordStatus
|
|
260
264
|
lineage: string // Explanation of the origin of this record (e.g: how, why)"
|
|
265
|
+
sourceRecords: Array<SourceRecord>
|
|
261
266
|
onlineResources: Array<DatasetOnlineResource>
|
|
262
267
|
spatialExtents: Array<DatasetSpatialExtent>
|
|
263
268
|
temporalExtents: Array<DatasetTemporalExtent>
|
|
@@ -285,6 +290,7 @@ export interface ServiceRecord extends BaseRecord {
|
|
|
285
290
|
export interface ReuseRecord extends BaseRecord {
|
|
286
291
|
kind: 'reuse'
|
|
287
292
|
lineage: string // Explanation of the origin of this record (e.g: how, why)"
|
|
293
|
+
sourceRecords: Array<SourceRecord>
|
|
288
294
|
onlineResources: Array<DatasetOnlineResource>
|
|
289
295
|
reuseType: ReuseType
|
|
290
296
|
spatialExtents: Array<DatasetSpatialExtent>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Observable } from 'rxjs'
|
|
2
2
|
import type { UserModel } from './model/user/user.model'
|
|
3
|
+
import type { GroupModel } from './model/user/group.model'
|
|
3
4
|
import type { Organization } from './model/record/organization.model'
|
|
4
5
|
import { CatalogRecord, Keyword, UserFeedback } from './model/record'
|
|
5
6
|
import { KeywordType } from './model/thesaurus'
|
|
@@ -28,6 +29,7 @@ export abstract class PlatformServiceInterface {
|
|
|
28
29
|
abstract getMe(): Observable<UserModel>
|
|
29
30
|
abstract isAnonymous(): Observable<boolean>
|
|
30
31
|
abstract getUsers(): Observable<UserModel[]>
|
|
32
|
+
abstract getUserPermissionsByGroup(): Observable<GroupModel[]>
|
|
31
33
|
abstract getUsersByOrganization(
|
|
32
34
|
organisation: Organization
|
|
33
35
|
): Observable<UserModel[]>
|
|
@@ -134,6 +134,7 @@ Cette section contient des *caractères internationaux* (ainsi que des "caractè
|
|
|
134
134
|
lineage: `This record was edited manually to test the conversion processes
|
|
135
135
|
|
|
136
136
|
As such, **it is not very interesting at all.**`,
|
|
137
|
+
sourceRecords: [],
|
|
137
138
|
licenses: [
|
|
138
139
|
{
|
|
139
140
|
text: 'Licence ODbL mai 2013 (basée sur ODbL 1.0)',
|
|
@@ -310,6 +311,7 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
|
|
|
310
311
|
],
|
|
311
312
|
lineage: `Document d’urbanisme numérisé conformément aux prescriptions nationales du CNIG par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.
|
|
312
313
|
Ce lot de données produit en 2019, a été numérisé à partir du PCI Vecteur de 2019 et contrôlé par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.`,
|
|
314
|
+
sourceRecords: [],
|
|
313
315
|
legalConstraints: [],
|
|
314
316
|
securityConstraints: [],
|
|
315
317
|
otherConstraints: [],
|
|
@@ -361,6 +363,7 @@ export const simpleDatasetRecordFixture = (): DatasetRecord => ({
|
|
|
361
363
|
overviews: [],
|
|
362
364
|
spatialExtents: [],
|
|
363
365
|
temporalExtents: [],
|
|
366
|
+
sourceRecords: [],
|
|
364
367
|
onlineResources: [
|
|
365
368
|
{
|
|
366
369
|
type: 'download',
|
|
@@ -413,6 +416,7 @@ export const simpleDatasetRecordWithFcatsFixture = (): DatasetRecord => ({
|
|
|
413
416
|
overviews: [],
|
|
414
417
|
spatialExtents: [],
|
|
415
418
|
temporalExtents: [],
|
|
419
|
+
sourceRecords: [],
|
|
416
420
|
onlineResources: [],
|
|
417
421
|
updateFrequency: { per: 'month', updatedTimes: 3 },
|
|
418
422
|
translations: {},
|
|
@@ -978,6 +982,7 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
|
|
|
978
982
|
title: 'English Title',
|
|
979
983
|
abstract: 'English Abstract',
|
|
980
984
|
lineage: 'English Lineage',
|
|
985
|
+
sourceRecords: [],
|
|
981
986
|
translations: {
|
|
982
987
|
title: { fr: 'Titre Français', de: 'Titel DE' },
|
|
983
988
|
abstract: { fr: 'Résumé Français', de: 'Beschreibung DE' },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inject, Injectable } from '@angular/core'
|
|
2
2
|
import { SourcesApiService } from '../../../../../../libs/data-access/gn4/src'
|
|
3
3
|
import { Observable } from 'rxjs'
|
|
4
4
|
import { filter, map, shareReplay } from 'rxjs/operators'
|
|
@@ -21,7 +21,10 @@ export class SourcesService {
|
|
|
21
21
|
return this.sources$.pipe(
|
|
22
22
|
map((sources) => sources.filter((source) => source.uuid === uuid)[0]),
|
|
23
23
|
filter((source) => !!source),
|
|
24
|
-
map(
|
|
24
|
+
map(
|
|
25
|
+
(source) =>
|
|
26
|
+
source.label[toLang3(this.translateService.getCurrentLang())]
|
|
27
|
+
)
|
|
25
28
|
)
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAction, props } from '@ngrx/store'
|
|
2
2
|
import {
|
|
3
3
|
CatalogRecord,
|
|
4
|
+
CatalogRecordKeys,
|
|
4
5
|
LanguageCode,
|
|
5
6
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
6
7
|
import { SaveRecordError } from './editor.models'
|
|
@@ -73,3 +74,8 @@ export const canEditRecord = createAction(
|
|
|
73
74
|
'[Editor] User can edit record',
|
|
74
75
|
props<{ canEditRecord: boolean }>()
|
|
75
76
|
)
|
|
77
|
+
|
|
78
|
+
export const setFocusedField = createAction(
|
|
79
|
+
'[Editor] Set focused field',
|
|
80
|
+
props<{ model: CatalogRecordKeys }>()
|
|
81
|
+
)
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from './editor.selectors'
|
|
13
13
|
import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
|
|
14
14
|
import { PlatformServiceInterface } from '../../../../../../libs/common/domain/src/lib/platform.service.interface'
|
|
15
|
-
|
|
16
15
|
@Injectable()
|
|
17
16
|
export class EditorEffects {
|
|
18
17
|
private actions$ = inject(Actions)
|
|
@@ -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
|
}
|
package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { FormFieldRichComponent } from '../record-form/form-field/form-field-rich/form-field-rich.component'
|
|
10
10
|
import { ButtonComponent, UrlInputComponent } from '../../../../../../../libs/ui/inputs/src'
|
|
11
|
-
import { MatIconModule } from '@angular/material/icon'
|
|
12
11
|
import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
|
|
13
12
|
import { Constraint } from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
14
13
|
import {
|
|
@@ -26,7 +25,6 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
|
26
25
|
FormFieldRichComponent,
|
|
27
26
|
UrlInputComponent,
|
|
28
27
|
ButtonComponent,
|
|
29
|
-
MatIconModule,
|
|
30
28
|
TranslateDirective,
|
|
31
29
|
TranslatePipe,
|
|
32
30
|
NgIconComponent,
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<div class="flex flex-col gap-7">
|
|
2
|
+
<div class="grid grid-cols-2 gap-4">
|
|
3
|
+
<div class="min-w-0">
|
|
4
|
+
<h3 class="text-[16px] font-bold text-main mb-[12px]" translate>
|
|
5
|
+
editor.record.form.field.contactDetails.lastName
|
|
6
|
+
</h3>
|
|
7
|
+
<gn-ui-text-input
|
|
8
|
+
class="block w-full"
|
|
9
|
+
extraClass="w-full"
|
|
10
|
+
[(value)]="contact.lastName"
|
|
11
|
+
(valueChange)="emitContactChange()"
|
|
12
|
+
[placeholder]="
|
|
13
|
+
'editor.record.form.field.contactDetails.lastName.placeholder'
|
|
14
|
+
| translate
|
|
15
|
+
"
|
|
16
|
+
data-test="contactDetailsLastName"
|
|
17
|
+
></gn-ui-text-input>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="min-w-0">
|
|
20
|
+
<h3 class="text-[16px] font-bold text-main mb-[12px]" translate>
|
|
21
|
+
editor.record.form.field.contactDetails.firstName
|
|
22
|
+
</h3>
|
|
23
|
+
<gn-ui-text-input
|
|
24
|
+
class="block w-full"
|
|
25
|
+
extraClass="w-full"
|
|
26
|
+
[(value)]="contact.firstName"
|
|
27
|
+
(valueChange)="emitContactChange()"
|
|
28
|
+
[placeholder]="
|
|
29
|
+
'editor.record.form.field.contactDetails.firstName.placeholder'
|
|
30
|
+
| translate
|
|
31
|
+
"
|
|
32
|
+
data-test="contactDetailsFirstName"
|
|
33
|
+
></gn-ui-text-input>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div>
|
|
37
|
+
<h3 class="text-[16px] font-bold text-main mb-[12px]" translate>
|
|
38
|
+
editor.record.form.field.contactDetails.email
|
|
39
|
+
</h3>
|
|
40
|
+
<gn-ui-text-input
|
|
41
|
+
class="block w-full"
|
|
42
|
+
extraClass="w-full"
|
|
43
|
+
[value]="contact.organization?.email ?? ''"
|
|
44
|
+
(valueChange)="handleOrganizationChange({ email: $event })"
|
|
45
|
+
[placeholder]="
|
|
46
|
+
'editor.record.form.field.contactDetails.email.placeholder' | translate
|
|
47
|
+
"
|
|
48
|
+
data-test="contactDetailsEmail"
|
|
49
|
+
></gn-ui-text-input>
|
|
50
|
+
</div>
|
|
51
|
+
<div>
|
|
52
|
+
<h3 class="text-[16px] font-bold text-main mb-[12px]" translate>
|
|
53
|
+
editor.record.form.field.contactDetails.organization
|
|
54
|
+
</h3>
|
|
55
|
+
<gn-ui-text-input
|
|
56
|
+
class="block w-full"
|
|
57
|
+
extraClass="w-full"
|
|
58
|
+
[value]="contact.organization?.name ?? ''"
|
|
59
|
+
(valueChange)="handleOrganizationChange({ name: $event })"
|
|
60
|
+
[placeholder]="
|
|
61
|
+
'editor.record.form.field.contactDetails.organization.placeholder'
|
|
62
|
+
| translate
|
|
63
|
+
"
|
|
64
|
+
data-test="contactDetailsOrganization"
|
|
65
|
+
></gn-ui-text-input>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
EventEmitter,
|
|
5
|
+
Input,
|
|
6
|
+
Output,
|
|
7
|
+
} from '@angular/core'
|
|
8
|
+
import {
|
|
9
|
+
Individual,
|
|
10
|
+
Organization,
|
|
11
|
+
} from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
12
|
+
import { TextInputComponent } from '../../../../../../../libs/ui/inputs/src'
|
|
13
|
+
import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'gn-ui-contact-details-form',
|
|
17
|
+
templateUrl: './contact-details-form.component.html',
|
|
18
|
+
styleUrls: ['./contact-details-form.component.css'],
|
|
19
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
+
standalone: true,
|
|
21
|
+
imports: [TextInputComponent, TranslateDirective, TranslatePipe],
|
|
22
|
+
})
|
|
23
|
+
export class ContactDetailsFormComponent {
|
|
24
|
+
@Input() contact: Individual
|
|
25
|
+
@Output() contactChange = new EventEmitter<Individual>()
|
|
26
|
+
|
|
27
|
+
emitContactChange() {
|
|
28
|
+
this.contactChange.emit(this.contact)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handleOrganizationChange(change: Partial<Organization>) {
|
|
32
|
+
this.contact.organization = {
|
|
33
|
+
...(this.contact.organization ?? ({} as Organization)),
|
|
34
|
+
...change,
|
|
35
|
+
} as Organization
|
|
36
|
+
|
|
37
|
+
this.emitContactChange()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -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
|
}
|
package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Directive, ElementRef, inject, Input } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[gnUiFieldFocus]',
|
|
5
|
+
standalone: true,
|
|
6
|
+
exportAs: 'fieldFocus',
|
|
7
|
+
})
|
|
8
|
+
export class FieldFocusDirective {
|
|
9
|
+
@Input() gnUiFieldFocusGlowClass = 'gn-ui-field-focus-glow'
|
|
10
|
+
|
|
11
|
+
private el = inject(ElementRef)
|
|
12
|
+
|
|
13
|
+
public focusField(focusInnerTarget = true) {
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
const host = this.el.nativeElement as HTMLElement
|
|
16
|
+
const glowClass = this.gnUiFieldFocusGlowClass
|
|
17
|
+
|
|
18
|
+
host.classList.remove(glowClass)
|
|
19
|
+
void host.offsetWidth
|
|
20
|
+
host.classList.add(glowClass)
|
|
21
|
+
host.addEventListener(
|
|
22
|
+
'animationend',
|
|
23
|
+
() => host.classList.remove(glowClass),
|
|
24
|
+
{ once: true }
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
host.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
28
|
+
if (!focusInnerTarget) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
const target =
|
|
32
|
+
host.querySelector<HTMLElement>(
|
|
33
|
+
'input, textarea, select, [contenteditable="true"]'
|
|
34
|
+
) ??
|
|
35
|
+
host.querySelector<HTMLElement>(
|
|
36
|
+
'button:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
37
|
+
)
|
|
38
|
+
target?.focus({ preventScroll: true })
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|