geonetwork-ui 2.11.0-dev.c2c8dac56 → 2.11.0-dev.c830efcd7
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 +2052 -1468
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +303 -124
- package/index.d.ts.map +1 -1
- package/package.json +7 -5
- package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/mel.records.dataset+featurecatalog.ts +317 -0
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +35 -8
- package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +19 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +98 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +7 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +134 -35
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -34
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +6 -13
- package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +1 -1
- package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
- package/src/libs/feature/dataviz/src/lib/service/data.service.ts +32 -19
- package/src/libs/feature/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
- package/src/libs/feature/record/src/lib/state/mdview.effects.ts +3 -1
- package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
- package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +18 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +80 -19
- package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +64 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +2 -2
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/index.ts +2 -0
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
- package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
- package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
- package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +244 -0
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
- package/src/libs/util/app-config/src/lib/app-config.ts +37 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
- package/src/libs/util/app-config/src/lib/model.ts +25 -1
- package/src/libs/util/shared/src/index.ts +1 -0
- package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
- package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +24 -4
- package/translations/en.json +23 -3
- package/translations/es.json +22 -2
- package/translations/fr.json +28 -8
- package/translations/it.json +22 -2
- package/translations/nl.json +22 -2
- package/translations/pt.json +22 -2
- package/translations/sk.json +22 -2
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
- package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
- package/src/libs/feature/map/src/lib/style/index.ts +0 -2
- package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
- package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
- /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Injectable,
|
|
1
|
+
import { inject, Injectable, InjectionToken, Injector } from '@angular/core'
|
|
2
2
|
import {
|
|
3
3
|
AbstractSearchField,
|
|
4
4
|
AvailableServicesField,
|
|
5
|
+
BoundingBoxSearchField,
|
|
5
6
|
DateRangeSearchField,
|
|
6
7
|
FieldValue,
|
|
7
8
|
FullTextSearchField,
|
|
@@ -10,10 +11,11 @@ import {
|
|
|
10
11
|
MultilingualSearchField,
|
|
11
12
|
OrganizationSearchField,
|
|
12
13
|
OwnerSearchField,
|
|
14
|
+
RecordKindField,
|
|
15
|
+
ResourceCreationRevisionDateSearchField,
|
|
13
16
|
ResourceTypeLegacyField,
|
|
14
17
|
SimpleSearchField,
|
|
15
18
|
TranslatedSearchField,
|
|
16
|
-
RecordKindField,
|
|
17
19
|
UserSearchField,
|
|
18
20
|
} from './fields'
|
|
19
21
|
import { forkJoin, Observable, of } from 'rxjs'
|
|
@@ -42,14 +44,30 @@ marker('search.filters.producerOrg')
|
|
|
42
44
|
marker('search.filters.publisherOrg')
|
|
43
45
|
marker('search.filters.user')
|
|
44
46
|
marker('search.filters.changeDate')
|
|
47
|
+
marker('search.filters.resourceCreationRevisionDate')
|
|
48
|
+
marker('search.filters.temporalExtent')
|
|
49
|
+
marker('search.filters.spatialExtent')
|
|
50
|
+
marker('search.filters.documentStandard')
|
|
51
|
+
|
|
52
|
+
export interface CustomSearchField {
|
|
53
|
+
name: string
|
|
54
|
+
baseFilter: string
|
|
55
|
+
excludeValues?: string[]
|
|
56
|
+
includeValues?: string[]
|
|
57
|
+
}
|
|
45
58
|
|
|
59
|
+
export const CUSTOM_FIELDS = new InjectionToken<CustomSearchField[]>(
|
|
60
|
+
'custom-fields'
|
|
61
|
+
)
|
|
46
62
|
@Injectable({
|
|
47
63
|
providedIn: 'root',
|
|
48
64
|
})
|
|
49
65
|
export class FieldsService {
|
|
50
66
|
protected injector = inject(Injector)
|
|
67
|
+
private customFields =
|
|
68
|
+
inject<CustomSearchField[]>(CUSTOM_FIELDS, { optional: true }) ?? []
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
private baseFields: Record<string, AbstractSearchField> = {
|
|
53
71
|
organization: new OrganizationSearchField(this.injector),
|
|
54
72
|
format: new SimpleSearchField('format', this.injector, 'asc'),
|
|
55
73
|
resourceType: new ResourceTypeLegacyField(this.injector), // Deprecated, use `recordKind` instead
|
|
@@ -94,8 +112,50 @@ export class FieldsService {
|
|
|
94
112
|
),
|
|
95
113
|
user: new UserSearchField(this.injector),
|
|
96
114
|
changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
|
|
115
|
+
resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
|
|
116
|
+
this.injector,
|
|
117
|
+
'desc'
|
|
118
|
+
),
|
|
119
|
+
temporalExtent: new DateRangeSearchField(
|
|
120
|
+
'resourceTemporalExtentDateRange',
|
|
121
|
+
this.injector,
|
|
122
|
+
'desc'
|
|
123
|
+
),
|
|
97
124
|
availableServices: new AvailableServicesField(this.injector),
|
|
98
|
-
|
|
125
|
+
spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected fields: Record<string, AbstractSearchField>
|
|
129
|
+
|
|
130
|
+
constructor() {
|
|
131
|
+
this.fields = { ...this.baseFields }
|
|
132
|
+
for (const customField of this.customFields) {
|
|
133
|
+
const baseField = this.baseFields[customField.baseFilter]
|
|
134
|
+
if (!(baseField instanceof SimpleSearchField)) {
|
|
135
|
+
console.warn(
|
|
136
|
+
`The custom field '${customField.name}' relies on a base field '${
|
|
137
|
+
customField.baseFilter
|
|
138
|
+
}' that is not supported. This field will be ignored.`
|
|
139
|
+
)
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
if (!customField.name.includes(':')) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`The custom field '${customField.name}' is not scoped with a prefix. Custom fields must have their names prefixed, for instance "myOrg:myKeywords". This field will be ignored.`
|
|
145
|
+
)
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
const newField = baseField.clone()
|
|
149
|
+
newField.setFieldIdentifier(customField.name)
|
|
150
|
+
if (customField.includeValues) {
|
|
151
|
+
newField.includeValues = customField.includeValues
|
|
152
|
+
}
|
|
153
|
+
if (customField.excludeValues) {
|
|
154
|
+
newField.excludeValues = customField.excludeValues
|
|
155
|
+
}
|
|
156
|
+
this.fields[customField.name] = newField
|
|
157
|
+
}
|
|
158
|
+
}
|
|
99
159
|
|
|
100
160
|
get supportedFields() {
|
|
101
161
|
return Object.keys(this.fields)
|
|
@@ -20,10 +20,14 @@ import {
|
|
|
20
20
|
isDateRange,
|
|
21
21
|
METADATA_LANGUAGE,
|
|
22
22
|
} from '../../../../../../../libs/api/repository/src'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
BoundingBox,
|
|
25
|
+
formatUserInfo,
|
|
26
|
+
isBoundingBox,
|
|
27
|
+
} from '../../../../../../../libs/util/shared/src'
|
|
24
28
|
import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
|
|
25
29
|
|
|
26
|
-
export type FieldType = 'values' | 'dateRange'
|
|
30
|
+
export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
|
|
27
31
|
|
|
28
32
|
export type FieldValue = string | number
|
|
29
33
|
export interface FieldAvailableValue {
|
|
@@ -49,6 +53,15 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
49
53
|
// FIXME: this is required to register runtime fields; abstract this as well
|
|
50
54
|
protected esService = this.injector.get(ElasticsearchService)
|
|
51
55
|
|
|
56
|
+
private customIdentifier?: string
|
|
57
|
+
|
|
58
|
+
protected get fieldIdentifier(): string {
|
|
59
|
+
return this.customIdentifier ?? this.esFieldName
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public includeValues: string[] = []
|
|
63
|
+
public excludeValues: string[] = []
|
|
64
|
+
|
|
52
65
|
constructor(
|
|
53
66
|
protected esFieldName: string,
|
|
54
67
|
protected injector: Injector,
|
|
@@ -56,13 +69,24 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
56
69
|
protected orderType: 'key' | 'count' = 'key'
|
|
57
70
|
) {}
|
|
58
71
|
|
|
72
|
+
public setFieldIdentifier(fieldIdentifier: string) {
|
|
73
|
+
this.customIdentifier = fieldIdentifier
|
|
74
|
+
this.esService.registerFieldAlias(fieldIdentifier, this.esFieldName)
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
protected getAggregations(): AggregationsParams {
|
|
60
78
|
return {
|
|
61
|
-
[this.
|
|
79
|
+
[this.fieldIdentifier]: {
|
|
62
80
|
type: 'terms',
|
|
63
81
|
field: this.esFieldName,
|
|
64
82
|
limit: 1000,
|
|
65
83
|
sort: [this.order, this.orderType],
|
|
84
|
+
...(this.includeValues?.length && {
|
|
85
|
+
includeValues: this.includeValues,
|
|
86
|
+
}),
|
|
87
|
+
...(this.excludeValues?.length && {
|
|
88
|
+
excludeValues: this.excludeValues,
|
|
89
|
+
}),
|
|
66
90
|
},
|
|
67
91
|
}
|
|
68
92
|
}
|
|
@@ -75,7 +99,7 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
75
99
|
return this.repository.aggregate(this.getAggregations()).pipe(
|
|
76
100
|
map(
|
|
77
101
|
(response) =>
|
|
78
|
-
(response[this.
|
|
102
|
+
(response[this.fieldIdentifier] as AggregationBuckets).buckets || []
|
|
79
103
|
),
|
|
80
104
|
switchMap((buckets: TermBucket[]) => {
|
|
81
105
|
const bucketPromises = buckets.map(async (bucket) => ({
|
|
@@ -93,7 +117,7 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
93
117
|
// FieldValue[]
|
|
94
118
|
if (this.getType() === 'values') {
|
|
95
119
|
return of({
|
|
96
|
-
[this.
|
|
120
|
+
[this.fieldIdentifier]: (values as FieldValue[]).reduce((acc, val) => {
|
|
97
121
|
const value = val.toString()
|
|
98
122
|
if (value !== '') {
|
|
99
123
|
return { ...acc, [value]: true }
|
|
@@ -104,13 +128,13 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
104
128
|
}
|
|
105
129
|
// DateRange
|
|
106
130
|
return of({
|
|
107
|
-
[this.
|
|
131
|
+
[this.fieldIdentifier]: values[0] !== '' ? values[0] : {},
|
|
108
132
|
})
|
|
109
133
|
}
|
|
110
134
|
getValuesForFilter(
|
|
111
135
|
filters: FieldFilters
|
|
112
136
|
): Observable<FieldValue[] | FieldValue | DateRange> {
|
|
113
|
-
const filter = filters[this.
|
|
137
|
+
const filter = filters[this.fieldIdentifier]
|
|
114
138
|
if (!filter) return of([])
|
|
115
139
|
// filter by expression
|
|
116
140
|
if (typeof filter === 'string') {
|
|
@@ -128,6 +152,10 @@ export class SimpleSearchField implements AbstractSearchField {
|
|
|
128
152
|
getType(): FieldType {
|
|
129
153
|
return 'values'
|
|
130
154
|
}
|
|
155
|
+
|
|
156
|
+
clone(): typeof this {
|
|
157
|
+
return Object.create(this)
|
|
158
|
+
}
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
export class TranslatedSearchField extends SimpleSearchField {
|
|
@@ -431,6 +459,61 @@ export class DateRangeSearchField extends SimpleSearchField {
|
|
|
431
459
|
}
|
|
432
460
|
}
|
|
433
461
|
|
|
462
|
+
// treats a resource's creation/revision dates as an interval (its min and max), so a record matches
|
|
463
|
+
// when that interval intersects the filter range, not just when a single date falls inside it
|
|
464
|
+
export class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
|
|
465
|
+
constructor(injector: Injector, order: 'asc' | 'desc' = 'desc') {
|
|
466
|
+
super('resourceCreationRevisionDate', injector, order)
|
|
467
|
+
const dateSourceFields = [
|
|
468
|
+
'creationDateForResource',
|
|
469
|
+
'revisionDateForResource',
|
|
470
|
+
]
|
|
471
|
+
const collectSortedDates = dateSourceFields
|
|
472
|
+
.map(
|
|
473
|
+
(field) => `if (doc.containsKey('${field}')) {
|
|
474
|
+
for (def date : doc['${field}']) { dates.add(date.millis); }
|
|
475
|
+
}`
|
|
476
|
+
)
|
|
477
|
+
.join('\n')
|
|
478
|
+
|
|
479
|
+
this.esService.registerRuntimeField(
|
|
480
|
+
'resourceCreationRevisionDateMin',
|
|
481
|
+
`def dates = [];
|
|
482
|
+
${collectSortedDates}
|
|
483
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`,
|
|
484
|
+
'date'
|
|
485
|
+
)
|
|
486
|
+
this.esService.registerRuntimeField(
|
|
487
|
+
'resourceCreationRevisionDateMax',
|
|
488
|
+
`def dates = [];
|
|
489
|
+
${collectSortedDates}
|
|
490
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`,
|
|
491
|
+
'date'
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export class BoundingBoxSearchField extends SimpleSearchField {
|
|
497
|
+
getAvailableValues(): Observable<FieldAvailableValue[]> {
|
|
498
|
+
return of([])
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
|
|
502
|
+
return of({
|
|
503
|
+
[this.fieldIdentifier]: values.map(Number) as BoundingBox,
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
|
|
508
|
+
const filter = filters[this.fieldIdentifier]
|
|
509
|
+
return of(isBoundingBox(filter) ? filter : [])
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getType(): FieldType {
|
|
513
|
+
return 'spatialExtent'
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
434
517
|
marker('search.filters.availableServices.view')
|
|
435
518
|
marker('search.filters.availableServices.download')
|
|
436
519
|
|
|
@@ -540,7 +623,7 @@ export class RecordKindField extends SimpleSearchField {
|
|
|
540
623
|
|
|
541
624
|
getFiltersForValues(values: FieldValue[]): Observable<FieldFilters | string> {
|
|
542
625
|
const filters: FieldFilters = {
|
|
543
|
-
[this.
|
|
626
|
+
[this.fieldIdentifier]: values.reduce((acc, value) => {
|
|
544
627
|
if (value === '') return { ...acc, [value]: true }
|
|
545
628
|
|
|
546
629
|
const keysToAdd = this.TYPE_MAPPING[value] || [value]
|
|
@@ -567,7 +650,7 @@ export class RecordKindField extends SimpleSearchField {
|
|
|
567
650
|
}
|
|
568
651
|
|
|
569
652
|
getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
|
|
570
|
-
const filter = filters[this.
|
|
653
|
+
const filter = filters[this.fieldIdentifier]
|
|
571
654
|
if (!filter) return of([])
|
|
572
655
|
|
|
573
656
|
const activeValues = Object.keys(filter).filter((v) => filter[v])
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
<ng-template #titleTemplate>
|
|
18
18
|
<div class="px-2">
|
|
19
19
|
<div class="text-lg font-bold">{{ featureType.name }}</div>
|
|
20
|
-
@if (featureType.
|
|
20
|
+
@if (featureType.description) {
|
|
21
21
|
<div class="text-sm">
|
|
22
|
-
{{ featureType.
|
|
22
|
+
{{ featureType.description }}
|
|
23
23
|
</div>
|
|
24
24
|
}
|
|
25
25
|
</div>
|
|
@@ -19,7 +19,7 @@ import { iconoirList } from '@ng-icons/iconoir'
|
|
|
19
19
|
marker('feature.catalog.attribute.type')
|
|
20
20
|
marker('feature.catalog.attribute.name')
|
|
21
21
|
marker('feature.catalog.attribute.code')
|
|
22
|
-
marker('feature.catalog.attribute.
|
|
22
|
+
marker('feature.catalog.attribute.description')
|
|
23
23
|
marker('feature.catalog.attribute.values')
|
|
24
24
|
|
|
25
25
|
interface ColumnDefinition {
|
|
@@ -56,7 +56,7 @@ export class FeatureCatalogListComponent {
|
|
|
56
56
|
readonly COLUMNS_DEFAULT: ColumnDefinition[] = [
|
|
57
57
|
{ key: 'type', width: '25%' },
|
|
58
58
|
{ key: 'name', width: '40%' },
|
|
59
|
-
{ key: '
|
|
59
|
+
{ key: 'description', width: 'minmax(0px, 1fr)' },
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
readonly COLUMN_VALUES: ColumnDefinition = {
|
|
@@ -128,7 +128,8 @@ export class ServiceCapabilitiesComponent implements OnInit {
|
|
|
128
128
|
this.loading = true
|
|
129
129
|
this.availableLayers = await getLayers(
|
|
130
130
|
this.apiLinks[0].url.href,
|
|
131
|
-
this.apiLinks[0].accessServiceProtocol
|
|
131
|
+
this.apiLinks[0].accessServiceProtocol,
|
|
132
|
+
true
|
|
132
133
|
)
|
|
133
134
|
this.loading = false
|
|
134
135
|
this.cdr.detectChanges()
|
|
@@ -4,6 +4,7 @@ export * from './lib/button/button.component'
|
|
|
4
4
|
export * from './lib/check-toggle/check-toggle.component'
|
|
5
5
|
export * from './lib/checkbox/checkbox.component'
|
|
6
6
|
export * from './lib/copy-text-button/copy-text-button.component'
|
|
7
|
+
export * from './lib/date-adapter.providers'
|
|
7
8
|
export * from './lib/date-picker/date-picker.component'
|
|
8
9
|
export * from './lib/date-range-dropdown/date-range-dropdown.component'
|
|
9
10
|
export * from './lib/date-range-picker/date-range-picker.component'
|
|
@@ -25,3 +26,4 @@ export * from './lib/url-input/url-input.component'
|
|
|
25
26
|
export * from './lib/viewport-intersector/viewport-intersector.component'
|
|
26
27
|
export * from './lib/search-feature-catalog/search-feature-catalog.component'
|
|
27
28
|
export * from './lib/date-range-inputs/date-range-inputs.component'
|
|
29
|
+
export * from './lib/spatial-extent-dropdown/spatial-extent-dropdown.component'
|
|
@@ -16,10 +16,6 @@ gn-ui-button {
|
|
|
16
16
|
--gn-ui-button-padding: 0;
|
|
17
17
|
font-size: 1em;
|
|
18
18
|
}
|
|
19
|
-
ng-icon.search {
|
|
20
|
-
margin-top: 0.6rem;
|
|
21
|
-
margin-left: 0.6rem;
|
|
22
|
-
}
|
|
23
19
|
gn-ui-button:last-of-type {
|
|
24
20
|
--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, 0.25em)
|
|
25
21
|
var(--gn-ui-text-input-rounded, 0.25em) 0;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<span class="w-full inline-block relative">
|
|
2
2
|
@if (!allowSubmit) {
|
|
3
3
|
<div
|
|
4
|
-
class="absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none"
|
|
4
|
+
class="absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none flex items-center justify-center"
|
|
5
5
|
>
|
|
6
|
-
<ng-icon name="iconoirSearch" class="text-primary
|
|
6
|
+
<ng-icon name="iconoirSearch" class="text-primary"></ng-icon>
|
|
7
7
|
</div>
|
|
8
8
|
}
|
|
9
9
|
<div class="flex flex-row">
|
|
@@ -83,7 +83,16 @@
|
|
|
83
83
|
>
|
|
84
84
|
@for (suggestion of suggestions$ | async; track suggestion) {
|
|
85
85
|
<mat-option [value]="suggestion" class="p-2 suggestion text-start">
|
|
86
|
-
|
|
86
|
+
@if (displayWithTemplate) {
|
|
87
|
+
<ng-container
|
|
88
|
+
*ngTemplateOutlet="
|
|
89
|
+
displayWithTemplate;
|
|
90
|
+
context: { $implicit: suggestion }
|
|
91
|
+
"
|
|
92
|
+
></ng-container>
|
|
93
|
+
} @else {
|
|
94
|
+
{{ displayWithFnInternal(suggestion) }}
|
|
95
|
+
}
|
|
87
96
|
</mat-option>
|
|
88
97
|
}
|
|
89
98
|
</mat-autocomplete>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
OnInit,
|
|
12
12
|
Output,
|
|
13
13
|
SimpleChanges,
|
|
14
|
+
TemplateRef,
|
|
14
15
|
ViewChild,
|
|
15
16
|
inject,
|
|
16
17
|
} from '@angular/core'
|
|
@@ -97,6 +98,13 @@ export class AutocompleteComponent
|
|
|
97
98
|
@ViewChild(MatAutocompleteTrigger) triggerRef: MatAutocompleteTrigger
|
|
98
99
|
@ViewChild(MatAutocomplete) autocomplete: MatAutocomplete
|
|
99
100
|
@ViewChild('searchInput') inputRef: ElementRef<HTMLInputElement>
|
|
101
|
+
/**
|
|
102
|
+
* Custom template to render each suggestion. Takes precedence over
|
|
103
|
+
* `displayWithFn` when set.
|
|
104
|
+
*/
|
|
105
|
+
@Input() displayWithTemplate?: TemplateRef<{
|
|
106
|
+
$implicit: AutocompleteItem
|
|
107
|
+
}>
|
|
100
108
|
|
|
101
109
|
searching: boolean
|
|
102
110
|
control = new UntypedFormControl()
|
|
@@ -126,13 +134,13 @@ export class AutocompleteComponent
|
|
|
126
134
|
getExtraClass(): string {
|
|
127
135
|
if (this.allowSubmit) {
|
|
128
136
|
if (this.enterButton) {
|
|
129
|
-
return 'border rounded-lg absolute w-8 h-8 right-[calc(var(--icon-width)+var(--icon-padding))]
|
|
137
|
+
return 'border rounded-lg absolute w-8 h-8 right-[calc(var(--icon-width)+var(--icon-padding))] top-1/2 -translate-y-1/2'
|
|
130
138
|
} else {
|
|
131
|
-
return 'border rounded-lg absolute w-8 h-8 right-[calc(var(--icon-width)+0.25*var(--icon-width))]
|
|
139
|
+
return 'border rounded-lg absolute w-8 h-8 right-[calc(var(--icon-width)+0.25*var(--icon-width))] top-1/2 -translate-y-1/2'
|
|
132
140
|
}
|
|
133
141
|
} else {
|
|
134
142
|
if (!this.enterButton) {
|
|
135
|
-
return 'border rounded-lg absolute w-8 h-8 right-2
|
|
143
|
+
return 'border rounded-lg absolute w-8 h-8 right-2 top-1/2 -translate-y-1/2'
|
|
136
144
|
}
|
|
137
145
|
}
|
|
138
146
|
return 'border rounded-lg absolute w-8 h-8'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { inject, Provider } from '@angular/core'
|
|
2
|
+
import {
|
|
3
|
+
DateAdapter,
|
|
4
|
+
MAT_DATE_FORMATS,
|
|
5
|
+
MAT_DATE_LOCALE,
|
|
6
|
+
} from '@angular/material/core'
|
|
7
|
+
import {
|
|
8
|
+
DateFnsAdapter,
|
|
9
|
+
MAT_DATE_FNS_FORMATS,
|
|
10
|
+
} from '@angular/material-date-fns-adapter'
|
|
11
|
+
import { enUS } from 'date-fns/locale/en-US'
|
|
12
|
+
import { DateService } from '../../../../../libs/util/shared/src'
|
|
13
|
+
|
|
14
|
+
export function provideLocalizedDateAdapter(): Provider[] {
|
|
15
|
+
return [
|
|
16
|
+
{ provide: MAT_DATE_LOCALE, useValue: enUS },
|
|
17
|
+
{ provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
|
|
18
|
+
{
|
|
19
|
+
provide: DateAdapter,
|
|
20
|
+
useFactory: () => {
|
|
21
|
+
const dateService = inject(DateService)
|
|
22
|
+
const adapter = new DateFnsAdapter()
|
|
23
|
+
dateService
|
|
24
|
+
.getDateFnsLocale()
|
|
25
|
+
.then((locale) => adapter.setLocale(locale))
|
|
26
|
+
return adapter
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -2,15 +2,9 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
4
|
EventEmitter,
|
|
5
|
-
inject,
|
|
6
5
|
Input,
|
|
7
6
|
Output,
|
|
8
7
|
} from '@angular/core'
|
|
9
|
-
import {
|
|
10
|
-
DateAdapter,
|
|
11
|
-
MAT_DATE_LOCALE,
|
|
12
|
-
MatNativeDateModule,
|
|
13
|
-
} from '@angular/material/core'
|
|
14
8
|
import { MatDatepickerModule } from '@angular/material/datepicker'
|
|
15
9
|
import { ButtonComponent } from '../button/button.component'
|
|
16
10
|
import {
|
|
@@ -19,7 +13,7 @@ import {
|
|
|
19
13
|
provideNgIconsConfig,
|
|
20
14
|
} from '@ng-icons/core'
|
|
21
15
|
import { iconoirCalendar } from '@ng-icons/iconoir'
|
|
22
|
-
import {
|
|
16
|
+
import { provideLocalizedDateAdapter } from '../date-adapter.providers'
|
|
23
17
|
|
|
24
18
|
@Component({
|
|
25
19
|
selector: 'gn-ui-date-picker',
|
|
@@ -27,31 +21,16 @@ import { TranslateService } from '@ngx-translate/core'
|
|
|
27
21
|
styleUrls: ['./date-picker.component.css'],
|
|
28
22
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
29
23
|
standalone: true,
|
|
30
|
-
imports: [
|
|
31
|
-
MatNativeDateModule,
|
|
32
|
-
MatDatepickerModule,
|
|
33
|
-
ButtonComponent,
|
|
34
|
-
NgIconComponent,
|
|
35
|
-
],
|
|
24
|
+
imports: [MatDatepickerModule, ButtonComponent, NgIconComponent],
|
|
36
25
|
providers: [
|
|
37
26
|
provideIcons({ iconoirCalendar }),
|
|
38
27
|
provideNgIconsConfig({
|
|
39
28
|
size: '1.5rem',
|
|
40
29
|
}),
|
|
41
|
-
|
|
42
|
-
provide: MAT_DATE_LOCALE,
|
|
43
|
-
useFactory: (locale: string) => locale,
|
|
44
|
-
},
|
|
30
|
+
provideLocalizedDateAdapter(),
|
|
45
31
|
],
|
|
46
32
|
})
|
|
47
33
|
export class DatePickerComponent {
|
|
48
|
-
private dateAdapter = inject<DateAdapter<Date>>(DateAdapter)
|
|
49
|
-
private translate = inject(TranslateService)
|
|
50
|
-
|
|
51
34
|
@Input() date: Date
|
|
52
35
|
@Output() dateChange = new EventEmitter<Date>()
|
|
53
|
-
|
|
54
|
-
constructor() {
|
|
55
|
-
this.dateAdapter.setLocale(this.translate.getCurrentLang())
|
|
56
|
-
}
|
|
57
36
|
}
|
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
mat-calendar {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.calendar {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-rows: 1fr;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.calendar > mat-calendar {
|
|
11
|
+
min-height: 0;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.calendar.expand {
|
|
16
|
+
animation: open 200ms ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.calendar.collapse {
|
|
20
|
+
animation: open 200ms ease-in reverse;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes open {
|
|
24
|
+
from {
|
|
25
|
+
grid-template-rows: 0fr;
|
|
26
|
+
opacity: 0;
|
|
27
|
+
}
|
|
28
|
+
to {
|
|
29
|
+
grid-template-rows: 1fr;
|
|
30
|
+
opacity: 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media (prefers-reduced-motion: reduce) {
|
|
35
|
+
.calendar.expand,
|
|
36
|
+
.calendar.collapse {
|
|
37
|
+
animation-duration: 1ms;
|
|
4
38
|
}
|
|
5
39
|
}
|