geonetwork-ui 2.10.0-dev.de2fa8e42 → 2.10.0-dev.e2dc4fd62
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 +514 -5790
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +122 -67
- 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/atomic-operations.ts +48 -7
- 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 +5 -4
- package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
- package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
- package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +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 +54 -32
- 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 +13 -5
- package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
- 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/components/constraint-card/constraint-card.component.ts +0 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +6 -7
- 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 +0 -34
- 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 +35 -19
- package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
- 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/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/lib/contact-details/contact-details.component.html +48 -56
- package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
- 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/markdown-parser/markdown-parser.component.ts +2 -2
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +18 -8
- package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +29 -1
- package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
- package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
- 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/widgets/src/lib/popover/popover.component.ts +7 -1
- package/src/libs/util/app-config/src/lib/app-config.ts +35 -10
- package/src/libs/util/app-config/src/lib/model.ts +3 -0
- package/src/libs/util/app-config/src/lib/parse-utils.ts +27 -0
- package/src/libs/util/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/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
- package/style.css +0 -1
- package/tailwind.base.css +8 -0
- package/translations/de.json +23 -19
- package/translations/en.json +23 -19
- package/translations/es.json +23 -19
- package/translations/fr.json +23 -19
- package/translations/it.json +23 -19
- package/translations/nl.json +23 -19
- package/translations/pt.json +23 -19
- package/translations/sk.json +23 -19
package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts
CHANGED
|
@@ -128,7 +128,7 @@ export class OrganizationsFromMetadataService
|
|
|
128
128
|
) {}
|
|
129
129
|
|
|
130
130
|
private get langIndex() {
|
|
131
|
-
return `lang${toLang3(this.translateService.
|
|
131
|
+
return `lang${toLang3(this.translateService.getCurrentLang())}`
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
equalsNormalizedStrings(
|
|
@@ -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?:\/\//)) {
|
|
@@ -104,15 +104,12 @@ export interface Keyword {
|
|
|
104
104
|
type: KeywordType
|
|
105
105
|
thesaurus?: ThesaurusModel
|
|
106
106
|
bbox?: [number, number, number, number]
|
|
107
|
+
// thesaurus hierarchy path incl. the keyword as last element; unset for root-level keywords
|
|
108
|
+
hierarchyPath?: string[]
|
|
107
109
|
|
|
108
110
|
translations?: KeywordTranslations
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
export interface INSPIRE_topic {
|
|
112
|
-
value: string
|
|
113
|
-
label: string
|
|
114
|
-
}
|
|
115
|
-
|
|
116
113
|
export interface ResourceIdentifier {
|
|
117
114
|
code: string
|
|
118
115
|
codeSpace?: string
|
|
@@ -254,10 +251,20 @@ export interface DatasetTemporalExtent {
|
|
|
254
251
|
end?: Date
|
|
255
252
|
}
|
|
256
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Represents a source dataset referenced from a lineage entry.
|
|
256
|
+
*/
|
|
257
|
+
export interface SourceRecord {
|
|
258
|
+
uuid?: string
|
|
259
|
+
title?: string
|
|
260
|
+
href?: string
|
|
261
|
+
}
|
|
262
|
+
|
|
257
263
|
export interface DatasetRecord extends BaseRecord {
|
|
258
264
|
kind: 'dataset'
|
|
259
265
|
status: RecordStatus
|
|
260
266
|
lineage: string // Explanation of the origin of this record (e.g: how, why)"
|
|
267
|
+
sourceRecords: Array<SourceRecord>
|
|
261
268
|
onlineResources: Array<DatasetOnlineResource>
|
|
262
269
|
spatialExtents: Array<DatasetSpatialExtent>
|
|
263
270
|
temporalExtents: Array<DatasetTemporalExtent>
|
|
@@ -285,6 +292,7 @@ export interface ServiceRecord extends BaseRecord {
|
|
|
285
292
|
export interface ReuseRecord extends BaseRecord {
|
|
286
293
|
kind: 'reuse'
|
|
287
294
|
lineage: string // Explanation of the origin of this record (e.g: how, why)"
|
|
295
|
+
sourceRecords: Array<SourceRecord>
|
|
288
296
|
onlineResources: Array<DatasetOnlineResource>
|
|
289
297
|
reuseType: ReuseType
|
|
290
298
|
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
|
}
|
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,
|
package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts
CHANGED
|
@@ -10,7 +10,7 @@ export class FieldFocusDirective {
|
|
|
10
10
|
|
|
11
11
|
private el = inject(ElementRef)
|
|
12
12
|
|
|
13
|
-
public focusField() {
|
|
13
|
+
public focusField(focusInnerTarget = true) {
|
|
14
14
|
setTimeout(() => {
|
|
15
15
|
const host = this.el.nativeElement as HTMLElement
|
|
16
16
|
const glowClass = this.gnUiFieldFocusGlowClass
|
|
@@ -25,6 +25,9 @@ export class FieldFocusDirective {
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
host.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
28
|
+
if (!focusInnerTarget) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
28
31
|
const target =
|
|
29
32
|
host.querySelector<HTMLElement>(
|
|
30
33
|
'input, textarea, select, [contenteditable="true"]'
|
|
@@ -131,13 +131,12 @@ export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
|
|
|
131
131
|
map((c) => c.length > 0),
|
|
132
132
|
distinctUntilChanged()
|
|
133
133
|
)
|
|
134
|
-
combineLatest([
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
134
|
+
combineLatest([isConstraintNotEmpty$, this.anyToggleActivated$])
|
|
135
|
+
.pipe(takeUntil(this.onDestroy$))
|
|
136
|
+
.subscribe(([isNotEmpty, anyToggleActivated]) => {
|
|
137
|
+
const visible = isNotEmpty && !anyToggleActivated
|
|
138
|
+
this.editorFacade.setFieldVisibility({ model }, visible)
|
|
139
|
+
})
|
|
141
140
|
}
|
|
142
141
|
hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
|
|
143
142
|
hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
DropdownMultiselectComponent,
|
|
6
6
|
} from '../../../../../../../../../libs/ui/inputs/src'
|
|
7
7
|
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
|
|
8
|
-
import {
|
|
8
|
+
import { ISO_TOPICS } from '../../../../fields.config'
|
|
9
9
|
|
|
10
10
|
@Component({
|
|
11
11
|
selector: 'gn-ui-form-field-topics',
|
|
@@ -22,7 +22,7 @@ export class FormFieldTopicsComponent {
|
|
|
22
22
|
this.topics = topics
|
|
23
23
|
}
|
|
24
24
|
@Output() valueChange: EventEmitter<string[]> = new EventEmitter()
|
|
25
|
-
availableTopics =
|
|
25
|
+
availableTopics = ISO_TOPICS.map((topic) => {
|
|
26
26
|
return {
|
|
27
27
|
label: this.translateService.instant(topic.label),
|
|
28
28
|
value: topic.value,
|
package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css
CHANGED
|
@@ -1,37 +1,3 @@
|
|
|
1
1
|
:host {
|
|
2
2
|
scroll-margin-top: 90px;
|
|
3
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
|
-
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Glow applied by FieldFocusDirective (gnUiFieldFocus) to a focused field or,
|
|
2
|
+
when the field is hidden, to its whole section. */
|
|
3
|
+
.gn-ui-field-focus-glow {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.gn-ui-field-focus-glow::after {
|
|
8
|
+
content: '';
|
|
9
|
+
position: absolute;
|
|
10
|
+
inset: -8px;
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
animation: gn-ui-field-focus-glow 3s ease-out forwards;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gn-ui-field-focus-glow::before {
|
|
18
|
+
content: '';
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: -16px;
|
|
21
|
+
border: 2px dashed var(--color-primary);
|
|
22
|
+
border-radius: 10px;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
animation: gn-ui-field-focus-glow 3s ease-out forwards;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes gn-ui-field-focus-glow {
|
|
28
|
+
0%,
|
|
29
|
+
55% {
|
|
30
|
+
opacity: 1;
|
|
31
|
+
}
|
|
32
|
+
100% {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
track sectionTracker($index, section)
|
|
5
5
|
) {
|
|
6
6
|
@if (!section.hidden) {
|
|
7
|
-
<div
|
|
7
|
+
<div
|
|
8
|
+
class="flex flex-col gap-6 border p-8 rounded-[8px] shadow scroll-mt-[90px]"
|
|
9
|
+
gnUiFieldFocus
|
|
10
|
+
#sectionFocus="fieldFocus"
|
|
11
|
+
>
|
|
8
12
|
<div class="flex flex-col gap-2">
|
|
9
13
|
@if (section.labelKey) {
|
|
10
14
|
<div class="text-2xl font-title text-black" translate>
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@angular/core'
|
|
10
10
|
import { EditorFacade } from '../../+state/editor.facade'
|
|
11
11
|
import { EditorFieldValue } from '../../models'
|
|
12
|
-
import { FormFieldComponent } from './form-field'
|
|
12
|
+
import { FieldFocusDirective, FormFieldComponent } from './form-field'
|
|
13
13
|
import { TranslateDirective } from '@ngx-translate/core'
|
|
14
14
|
import {
|
|
15
15
|
EditorFieldWithValue,
|
|
@@ -25,7 +25,12 @@ import { switchMap } from 'rxjs/operators'
|
|
|
25
25
|
styleUrls: ['./record-form.component.css'],
|
|
26
26
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
27
27
|
standalone: true,
|
|
28
|
-
imports: [
|
|
28
|
+
imports: [
|
|
29
|
+
CommonModule,
|
|
30
|
+
FormFieldComponent,
|
|
31
|
+
FieldFocusDirective,
|
|
32
|
+
TranslateDirective,
|
|
33
|
+
],
|
|
29
34
|
})
|
|
30
35
|
export class RecordFormComponent implements OnInit, OnDestroy {
|
|
31
36
|
facade = inject(EditorFacade)
|
|
@@ -36,17 +41,22 @@ export class RecordFormComponent implements OnInit, OnDestroy {
|
|
|
36
41
|
)
|
|
37
42
|
|
|
38
43
|
focusFieldWithPage$ = this.facade.focusedField$.pipe(
|
|
39
|
-
switchMap(
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
switchMap(async (field) => {
|
|
45
|
+
const location = await this.getFieldLocation(field)
|
|
46
|
+
return [field, location?.page ?? null, location?.section ?? -1] as const
|
|
47
|
+
})
|
|
42
48
|
)
|
|
43
49
|
|
|
44
50
|
formFields = viewChildren(FormFieldComponent)
|
|
51
|
+
sectionFocusDirectives = viewChildren<FieldFocusDirective>('sectionFocus')
|
|
45
52
|
|
|
46
|
-
focusField(model: CatalogRecordKeys) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
focusField(model: CatalogRecordKeys, sectionIndex: number) {
|
|
54
|
+
const field = this.formFields().find((f) => f.model === model)
|
|
55
|
+
if (field) {
|
|
56
|
+
field.fieldFocus.focusField()
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
this.sectionFocusDirectives()[sectionIndex]?.focusField(false)
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
ngOnInit() {
|
|
@@ -55,15 +65,15 @@ export class RecordFormComponent implements OnInit, OnDestroy {
|
|
|
55
65
|
.pipe(
|
|
56
66
|
withLatestFrom(
|
|
57
67
|
this.facade.currentPage$,
|
|
58
|
-
([field, fieldPage], currentPage) =>
|
|
59
|
-
[field, fieldPage, currentPage] as const
|
|
68
|
+
([field, fieldPage, fieldSection], currentPage) =>
|
|
69
|
+
[field, fieldPage, fieldSection, currentPage] as const
|
|
60
70
|
)
|
|
61
71
|
)
|
|
62
|
-
.subscribe(([field, fieldPage, currentPage]) => {
|
|
72
|
+
.subscribe(([field, fieldPage, fieldSection, currentPage]) => {
|
|
63
73
|
if (fieldPage !== null && fieldPage !== currentPage) {
|
|
64
74
|
this.facade.setCurrentPage(fieldPage)
|
|
65
75
|
}
|
|
66
|
-
setTimeout(() => this.focusField(field))
|
|
76
|
+
setTimeout(() => this.focusField(field, fieldSection))
|
|
67
77
|
})
|
|
68
78
|
)
|
|
69
79
|
}
|
|
@@ -87,13 +97,19 @@ export class RecordFormComponent implements OnInit, OnDestroy {
|
|
|
87
97
|
return section.labelKey
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
async
|
|
100
|
+
async getFieldLocation(
|
|
101
|
+
model: CatalogRecordKeys
|
|
102
|
+
): Promise<{ page: number; section: number } | null> {
|
|
91
103
|
const config = await firstValueFrom(this.facade.editorConfig$)
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
section.fields.some((field) => field.model === model)
|
|
95
|
-
)
|
|
104
|
+
const page = config.pages.findIndex((p) =>
|
|
105
|
+
p.sections.some((s) => s.fields.some((f) => f.model === model))
|
|
96
106
|
)
|
|
97
|
-
|
|
107
|
+
if (page < 0) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
const section = config.pages[page].sections
|
|
111
|
+
.filter((s) => !s.hidden)
|
|
112
|
+
.findIndex((s) => s.fields.some((f) => f.model === model))
|
|
113
|
+
return { page, section }
|
|
98
114
|
}
|
|
99
115
|
}
|
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
EditorField,
|
|
5
5
|
EditorSection,
|
|
6
6
|
} from './models/editor-config.model'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import { Keyword } from '../../../../../libs/common/domain/src/lib/model/record'
|
|
8
|
+
|
|
9
|
+
export interface ISOTopic {
|
|
10
|
+
value: string
|
|
11
|
+
label: string
|
|
12
|
+
}
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* This file contains the configuration of the fields that will be displayed in the editor.
|
|
@@ -359,75 +361,86 @@ export const SPATIAL_SCOPES: Keyword[] = [
|
|
|
359
361
|
]
|
|
360
362
|
|
|
361
363
|
/************************************************************
|
|
362
|
-
***************
|
|
364
|
+
*************** ISO TOPICS **************
|
|
363
365
|
************************************************************
|
|
364
366
|
*/
|
|
365
367
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
+
// label keys mirror the ISO MD_TopicCategoryCode value for consistency
|
|
369
|
+
// TODO: correctly handle code lists; for instance, this code list is specific to ISO 19139
|
|
370
|
+
export const ISO_TOPICS: ISOTopic[] = [
|
|
371
|
+
{ value: 'biota', label: marker('editor.record.form.topics.iso.biota') },
|
|
368
372
|
{
|
|
369
373
|
value: 'boundaries',
|
|
370
|
-
label: 'editor.record.form.topics.
|
|
374
|
+
label: marker('editor.record.form.topics.iso.boundaries'),
|
|
371
375
|
},
|
|
372
376
|
{
|
|
373
377
|
value: 'climatologyMeteorologyAtmosphere',
|
|
374
|
-
label:
|
|
378
|
+
label: marker(
|
|
379
|
+
'editor.record.form.topics.iso.climatologyMeteorologyAtmosphere'
|
|
380
|
+
),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
value: 'economy',
|
|
384
|
+
label: marker('editor.record.form.topics.iso.economy'),
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
value: 'elevation',
|
|
388
|
+
label: marker('editor.record.form.topics.iso.elevation'),
|
|
375
389
|
},
|
|
376
|
-
{ value: 'economy', label: 'editor.record.form.topics.inspire.economy' },
|
|
377
|
-
{ value: 'elevation', label: 'editor.record.form.topics.inspire.elevation' },
|
|
378
390
|
{
|
|
379
391
|
value: 'environment',
|
|
380
|
-
label: 'editor.record.form.topics.
|
|
392
|
+
label: marker('editor.record.form.topics.iso.environment'),
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
value: 'farming',
|
|
396
|
+
label: marker('editor.record.form.topics.iso.farming'),
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
value: 'geoscientificInformation',
|
|
400
|
+
label: marker('editor.record.form.topics.iso.geoscientificInformation'),
|
|
381
401
|
},
|
|
382
|
-
{ value: 'farming', label: 'editor.record.form.topics.inspire.farming' },
|
|
383
402
|
{
|
|
384
|
-
value: '
|
|
385
|
-
label: 'editor.record.form.topics.
|
|
403
|
+
value: 'health',
|
|
404
|
+
label: marker('editor.record.form.topics.iso.health'),
|
|
386
405
|
},
|
|
387
|
-
{ value: 'health', label: 'editor.record.form.topics.inspire.health' },
|
|
388
406
|
{
|
|
389
407
|
value: 'imageryBaseMapsEarthCover',
|
|
390
|
-
label: 'editor.record.form.topics.
|
|
408
|
+
label: marker('editor.record.form.topics.iso.imageryBaseMapsEarthCover'),
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
value: 'inlandWaters',
|
|
412
|
+
label: marker('editor.record.form.topics.iso.inlandWaters'),
|
|
391
413
|
},
|
|
392
|
-
{ value: 'inlandWaters', label: 'editor.record.form.topics.inspire.waters' },
|
|
393
414
|
{
|
|
394
415
|
value: 'intelligenceMilitary',
|
|
395
|
-
label: 'editor.record.form.topics.
|
|
416
|
+
label: marker('editor.record.form.topics.iso.intelligenceMilitary'),
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
value: 'location',
|
|
420
|
+
label: marker('editor.record.form.topics.iso.location'),
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
value: 'oceans',
|
|
424
|
+
label: marker('editor.record.form.topics.iso.oceans'),
|
|
396
425
|
},
|
|
397
|
-
{ value: 'Location', label: 'editor.record.form.topics.inspire.location' },
|
|
398
|
-
{ value: 'Oceans', label: 'editor.record.form.topics.inspire.oceans' },
|
|
399
426
|
{
|
|
400
427
|
value: 'planningCadastre',
|
|
401
|
-
label: 'editor.record.form.topics.
|
|
428
|
+
label: marker('editor.record.form.topics.iso.planningCadastre'),
|
|
402
429
|
},
|
|
403
|
-
{ value: 'Society', label: 'editor.record.form.topics.inspire.society' },
|
|
404
|
-
{ value: 'Structure', label: 'editor.record.form.topics.inspire.structure' },
|
|
405
430
|
{
|
|
406
|
-
value: '
|
|
407
|
-
label: 'editor.record.form.topics.
|
|
431
|
+
value: 'society',
|
|
432
|
+
label: marker('editor.record.form.topics.iso.society'),
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
value: 'structure',
|
|
436
|
+
label: marker('editor.record.form.topics.iso.structure'),
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
value: 'transportation',
|
|
440
|
+
label: marker('editor.record.form.topics.iso.transportation'),
|
|
408
441
|
},
|
|
409
442
|
{
|
|
410
443
|
value: 'utilitiesCommunication',
|
|
411
|
-
label: 'editor.record.form.topics.
|
|
444
|
+
label: marker('editor.record.form.topics.iso.utilitiesCommunication'),
|
|
412
445
|
},
|
|
413
446
|
]
|
|
414
|
-
|
|
415
|
-
marker('editor.record.form.topics.inspire.biota')
|
|
416
|
-
marker('editor.record.form.topics.inspire.boundaries')
|
|
417
|
-
marker('editor.record.form.topics.inspire.climatology')
|
|
418
|
-
marker('editor.record.form.topics.inspire.economy')
|
|
419
|
-
marker('editor.record.form.topics.inspire.elevation')
|
|
420
|
-
marker('editor.record.form.topics.inspire.environnement')
|
|
421
|
-
marker('editor.record.form.topics.inspire.farming')
|
|
422
|
-
marker('editor.record.form.topics.inspire.geoscientific')
|
|
423
|
-
marker('editor.record.form.topics.inspire.health')
|
|
424
|
-
marker('editor.record.form.topics.inspire.imagery')
|
|
425
|
-
marker('editor.record.form.topics.inspire.intelligence')
|
|
426
|
-
marker('editor.record.form.topics.inspire.location')
|
|
427
|
-
marker('editor.record.form.topics.inspire.oceans')
|
|
428
|
-
marker('editor.record.form.topics.inspire.planning')
|
|
429
|
-
marker('editor.record.form.topics.inspire.society')
|
|
430
|
-
marker('editor.record.form.topics.inspire.structure')
|
|
431
|
-
marker('editor.record.form.topics.inspire.transportation')
|
|
432
|
-
marker('editor.record.form.topics.inspire.utilities')
|
|
433
|
-
marker('editor.record.form.topics.inspire.waters')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Injectable, inject } from '@angular/core'
|
|
2
|
-
import {
|
|
2
|
+
import { Observable, switchMap } from 'rxjs'
|
|
3
3
|
import { map, tap } from 'rxjs/operators'
|
|
4
4
|
import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
5
5
|
import { EditorConfig } from '../models/'
|