geonetwork-ui 2.11.0-dev.fb3fea50c → 2.11.0-dev.fb48cfeee
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 +1675 -1322
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +205 -111
- package/index.d.ts.map +1 -1
- package/package.json +6 -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 +395 -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 +17 -6
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +110 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +131 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +9 -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 +67 -10
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +83 -35
- package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -0
- package/src/libs/common/domain/src/lib/model/record/flags.model.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/index.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +14 -18
- 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 +12 -2
- package/src/libs/common/domain/src/lib/platform.service.interface.ts +1 -0
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +20 -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/editor/src/lib/components/record-form/form-field/form-field-constraints/form-field-constraints.component.ts +3 -5
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +2 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.html +18 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.ts +86 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +100 -88
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -4
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +18 -4
- package/src/libs/feature/editor/src/lib/expressions.ts +67 -0
- package/src/libs/feature/editor/src/lib/fields.config.ts +13 -0
- package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -4
- package/src/libs/feature/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- 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/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +7 -1
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +26 -4
- 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 +1 -0
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +58 -16
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +3 -3
- 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/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/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 +8 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -5
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
- package/src/libs/util/app-config/src/lib/model.ts +24 -1
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
- package/translations/de.json +12 -2
- package/translations/en.json +14 -4
- package/translations/es.json +12 -2
- package/translations/fr.json +19 -9
- package/translations/it.json +12 -2
- package/translations/nl.json +12 -2
- package/translations/pt.json +12 -2
- package/translations/sk.json +12 -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 → editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.css} +0 -0
package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts
CHANGED
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip'
|
|
13
13
|
import {
|
|
14
14
|
AssociatedRecord,
|
|
15
|
-
CatalogRecordKeys,
|
|
16
15
|
Constraint,
|
|
17
16
|
DatasetTemporalExtent,
|
|
18
17
|
GraphicOverview,
|
|
19
18
|
Individual,
|
|
20
19
|
Keyword,
|
|
21
20
|
OnlineResource,
|
|
21
|
+
RecordFlag,
|
|
22
22
|
RecordKind,
|
|
23
23
|
UpdateFrequency,
|
|
24
24
|
} from '../../../../../../../../libs/common/domain/src/lib/model/record'
|
|
@@ -36,9 +36,10 @@ import {
|
|
|
36
36
|
FormFieldTemporalExtentsComponent,
|
|
37
37
|
} from '.'
|
|
38
38
|
import {
|
|
39
|
-
FieldModelSpecifier,
|
|
40
39
|
FormFieldComponentName,
|
|
41
40
|
FormFieldConfig,
|
|
41
|
+
FormFieldModel,
|
|
42
|
+
FormFieldModelSpecifier,
|
|
42
43
|
} from '../../../models'
|
|
43
44
|
import { FormFieldAssociatedRecordsComponent } from './form-field-associated-records/form-field-associated-records.component'
|
|
44
45
|
import { FormFieldContactsForResourceComponent } from './form-field-contacts-for-resource/form-field-contacts-for-resource.component'
|
|
@@ -57,6 +58,7 @@ import { FormFieldConstraintsComponent } from './form-field-constraints/form-fie
|
|
|
57
58
|
import { TextFieldModule } from '@angular/cdk/text-field'
|
|
58
59
|
import { FormFieldSpatialToggleComponent } from './form-field-spatial-toggle/form-field-spatial-toggle.component'
|
|
59
60
|
import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.component'
|
|
61
|
+
import { FormFieldRecordFlagComponent } from './form-field-record-flags/form-field-record-flag-component'
|
|
60
62
|
|
|
61
63
|
@Component({
|
|
62
64
|
selector: 'gn-ui-form-field',
|
|
@@ -90,6 +92,7 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
|
|
|
90
92
|
FormFieldTopicsComponent,
|
|
91
93
|
TextFieldModule,
|
|
92
94
|
NgIconComponent,
|
|
95
|
+
FormFieldRecordFlagComponent,
|
|
93
96
|
],
|
|
94
97
|
providers: [provideIcons({ matEditOutline, matHelpOutline })],
|
|
95
98
|
hostDirectives: [FieldFocusDirective],
|
|
@@ -97,8 +100,8 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
|
|
|
97
100
|
export class FormFieldComponent {
|
|
98
101
|
@Input() uniqueIdentifier: string
|
|
99
102
|
@Input() recordKind: RecordKind
|
|
100
|
-
@Input() model:
|
|
101
|
-
@Input() modelSpecifier:
|
|
103
|
+
@Input() model: FormFieldModel
|
|
104
|
+
@Input() modelSpecifier: FormFieldModelSpecifier
|
|
102
105
|
@Input() componentName: FormFieldComponentName
|
|
103
106
|
|
|
104
107
|
@Input() config: FormFieldConfig
|
|
@@ -126,6 +129,7 @@ export class FormFieldComponent {
|
|
|
126
129
|
this.model === 'legalConstraints' ||
|
|
127
130
|
this.model === 'securityConstraints' ||
|
|
128
131
|
this.model === 'otherConstraints' ||
|
|
132
|
+
this.model === 'flags' ||
|
|
129
133
|
this.componentName === 'form-field-constraints-shortcuts'
|
|
130
134
|
)
|
|
131
135
|
}
|
|
@@ -172,6 +176,9 @@ export class FormFieldComponent {
|
|
|
172
176
|
}>
|
|
173
177
|
return identifiers?.[0]?.code || ''
|
|
174
178
|
}
|
|
179
|
+
get recordFlag() {
|
|
180
|
+
return this.modelSpecifier?.replace(/^flags:/, '') as RecordFlag
|
|
181
|
+
}
|
|
175
182
|
|
|
176
183
|
handleResourceIdentifierChange(code: string) {
|
|
177
184
|
const identifiers = this.value as Array<{
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
viewChildren,
|
|
12
12
|
} from '@angular/core'
|
|
13
13
|
import { EditorFacade } from '../../+state/editor.facade'
|
|
14
|
-
import { EditorFieldValue } from '../../models'
|
|
14
|
+
import { EditorFieldValue, FormFieldModel } from '../../models'
|
|
15
15
|
import { FieldFocusDirective, FormFieldComponent } from './form-field'
|
|
16
16
|
import { TranslateDirective } from '@ngx-translate/core'
|
|
17
17
|
import {
|
|
@@ -24,6 +24,7 @@ import { switchMap } from 'rxjs/operators'
|
|
|
24
24
|
import { toSignal } from '@angular/core/rxjs-interop'
|
|
25
25
|
import { evaluate, isExpression } from '../../expressions'
|
|
26
26
|
import { NotificationsService } from '../../../../../../../libs/feature/notifications/src'
|
|
27
|
+
import { PlatformServiceInterface } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
|
|
27
28
|
|
|
28
29
|
@Component({
|
|
29
30
|
selector: 'gn-ui-record-form',
|
|
@@ -41,6 +42,7 @@ import { NotificationsService } from '../../../../../../../libs/feature/notifica
|
|
|
41
42
|
export class RecordFormComponent implements OnInit, OnDestroy {
|
|
42
43
|
facade = inject(EditorFacade)
|
|
43
44
|
notifications = inject(NotificationsService)
|
|
45
|
+
platform = inject(PlatformServiceInterface)
|
|
44
46
|
subscription = new Subscription()
|
|
45
47
|
|
|
46
48
|
recordUniqueIdentifier$ = this.facade.record$.pipe(
|
|
@@ -90,8 +92,8 @@ export class RecordFormComponent implements OnInit, OnDestroy {
|
|
|
90
92
|
this.subscription.unsubscribe()
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
handleFieldValueChange(model:
|
|
94
|
-
if (!model) {
|
|
95
|
+
handleFieldValueChange(model: FormFieldModel, newValue: EditorFieldValue) {
|
|
96
|
+
if (!model || model === 'flags') {
|
|
95
97
|
return
|
|
96
98
|
}
|
|
97
99
|
this.facade.updateRecordField(model, newValue)
|
|
@@ -122,6 +124,8 @@ export class RecordFormComponent implements OnInit, OnDestroy {
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
private recordSignal = toSignal(this.facade.record$, { requireSync: true })
|
|
127
|
+
private userSignal = toSignal(this.platform.getMe())
|
|
128
|
+
private isAdminSignal = toSignal(this.platform.isAdministrator())
|
|
125
129
|
private expressionCache = new Map<string, Signal<EditorFieldValue>>()
|
|
126
130
|
|
|
127
131
|
// only compiles the expression into an evaluator the first time;
|
|
@@ -149,7 +153,17 @@ ${errors.map((err) => `> ${err}`).join('\n')}`)
|
|
|
149
153
|
}
|
|
150
154
|
this.expressionCache.set(
|
|
151
155
|
expression,
|
|
152
|
-
computed(() =>
|
|
156
|
+
computed(() =>
|
|
157
|
+
evaluator({
|
|
158
|
+
globals: {
|
|
159
|
+
record: this.recordSignal(),
|
|
160
|
+
user: {
|
|
161
|
+
...this.userSignal(),
|
|
162
|
+
isAdmin: this.isAdminSignal(),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
)
|
|
153
167
|
)
|
|
154
168
|
return this.expressionCache.get(expression)
|
|
155
169
|
}
|
|
@@ -17,10 +17,14 @@ const TOKEN_OPERATOR_LOGICAL = ['==', '!=']
|
|
|
17
17
|
const TOKEN_STRING_LITERAL = ["'", '"']
|
|
18
18
|
const TOKEN_GROUP_START = '('
|
|
19
19
|
const TOKEN_GROUP_END = ')'
|
|
20
|
+
const TOKEN_OPERATOR_BOOLEAN = ['&&', '||']
|
|
21
|
+
const TOKEN_OPERATOR_NOT = '!'
|
|
20
22
|
|
|
21
23
|
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
|
|
22
24
|
const OPERATORS_BY_PRECEDENCE: string[][] = [
|
|
23
25
|
[TOKEN_OPERATOR_TERNARY_FIRST, TOKEN_OPERATOR_TERNARY_SECOND],
|
|
26
|
+
TOKEN_OPERATOR_BOOLEAN,
|
|
27
|
+
[TOKEN_OPERATOR_NOT],
|
|
24
28
|
TOKEN_OPERATOR_LOGICAL,
|
|
25
29
|
]
|
|
26
30
|
const ALL_OPERATORS = OPERATORS_BY_PRECEDENCE.flat()
|
|
@@ -161,6 +165,18 @@ function evaluateInternal(
|
|
|
161
165
|
continue
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
// BOOLEAN LITERAL
|
|
169
|
+
const isBooleanLiteral =
|
|
170
|
+
readNext(expression, pos, 4) === 'true' ||
|
|
171
|
+
readNext(expression, pos, 5) === 'false'
|
|
172
|
+
if (isBooleanLiteral) {
|
|
173
|
+
const literal = readNext(expression, pos, 4) === 'true' ? 'true' : 'false'
|
|
174
|
+
pos += literal.length
|
|
175
|
+
const booleanLiteral = literal === 'true'
|
|
176
|
+
currentEvaluator = () => booleanLiteral
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
// SYMBOL (NAME)
|
|
165
181
|
const isSymbol = /[a-zA-Z]/.test(readNext(expression, pos))
|
|
166
182
|
if (isSymbol) {
|
|
@@ -291,6 +307,57 @@ function evaluateInternal(
|
|
|
291
307
|
continue
|
|
292
308
|
}
|
|
293
309
|
|
|
310
|
+
// BOOLEAN OPERATORS
|
|
311
|
+
const isBooleanOperator = TOKEN_OPERATOR_BOOLEAN.some(
|
|
312
|
+
(token) => readNext(expression, pos, token.length) === token
|
|
313
|
+
)
|
|
314
|
+
if (isBooleanOperator) {
|
|
315
|
+
const matchingOperator = TOKEN_OPERATOR_BOOLEAN.find(
|
|
316
|
+
(token) => readNext(expression, pos, token.length) === token
|
|
317
|
+
)
|
|
318
|
+
const isAnd = matchingOperator === '&&'
|
|
319
|
+
pos += matchingOperator.length
|
|
320
|
+
const leftEvaluator = currentEvaluator
|
|
321
|
+
const rightExpression = readUntilNextOperator(
|
|
322
|
+
expression,
|
|
323
|
+
pos,
|
|
324
|
+
matchingOperator
|
|
325
|
+
)
|
|
326
|
+
pos += rightExpression.length
|
|
327
|
+
const rightEvaluator = evaluateInternal(
|
|
328
|
+
rightExpression,
|
|
329
|
+
currentContext
|
|
330
|
+
).evaluator
|
|
331
|
+
|
|
332
|
+
currentEvaluator = (context) => {
|
|
333
|
+
return isAnd
|
|
334
|
+
? Boolean(leftEvaluator(context) && rightEvaluator(context))
|
|
335
|
+
: Boolean(leftEvaluator(context) || rightEvaluator(context))
|
|
336
|
+
}
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// PREFIX OPERATORS
|
|
341
|
+
const isNotOperator = readNext(expression, pos) === TOKEN_OPERATOR_NOT
|
|
342
|
+
if (isNotOperator) {
|
|
343
|
+
pos++
|
|
344
|
+
const operandExpression = readUntilNextOperator(
|
|
345
|
+
expression,
|
|
346
|
+
pos,
|
|
347
|
+
TOKEN_OPERATOR_NOT
|
|
348
|
+
)
|
|
349
|
+
pos += operandExpression.length
|
|
350
|
+
const operandEvaluator = evaluateInternal(
|
|
351
|
+
operandExpression,
|
|
352
|
+
currentContext
|
|
353
|
+
).evaluator
|
|
354
|
+
|
|
355
|
+
currentEvaluator = (context) => {
|
|
356
|
+
return !operandEvaluator(context)
|
|
357
|
+
}
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
|
|
294
361
|
// TERNARY OPERATOR
|
|
295
362
|
const isTernary = readNext(expression, pos) === TOKEN_OPERATOR_TERNARY_FIRST
|
|
296
363
|
if (isTernary) {
|
|
@@ -37,6 +37,19 @@ export const DEFAULT_CONFIGURATION: EditorConfig = {
|
|
|
37
37
|
labelKey: marker('editor.record.form.field.title'),
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
model: 'flags',
|
|
42
|
+
modelSpecifier: 'flags:IS_REFERENCE_DATASET',
|
|
43
|
+
formFieldConfig: {
|
|
44
|
+
labelKey: marker(
|
|
45
|
+
'editor.record.form.field.flags.IS_REFERENCE_DATASET'
|
|
46
|
+
),
|
|
47
|
+
hintKey: marker(
|
|
48
|
+
'editor.record.form.field.hint.flags.IS_REFERENCE_DATASET'
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
hidden: '${record.kind != "dataset" || !user.isAdmin}',
|
|
52
|
+
},
|
|
40
53
|
{
|
|
41
54
|
model: 'abstract',
|
|
42
55
|
formFieldConfig: {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CatalogRecordKeys,
|
|
3
|
+
RecordFlag,
|
|
4
|
+
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
2
5
|
|
|
3
6
|
// Expressions should be enclosed in `${}` to be recognized as such
|
|
4
7
|
// eg. ${dateNow()}
|
|
@@ -15,6 +18,8 @@ export interface FormFieldConfig {
|
|
|
15
18
|
invalidHintKey?: string
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
export type FormFieldModel = CatalogRecordKeys | 'flags'
|
|
22
|
+
|
|
18
23
|
// Specifiers let us use specific components
|
|
19
24
|
// This is used for instance to target only certain online resources in a field
|
|
20
25
|
type OnlineLinkResourceSpecifier = `onlineResourceType:link`
|
|
@@ -25,11 +30,12 @@ type OnlineSingleLinkResourceSpecifier = `onlineResourceType:singleLink`
|
|
|
25
30
|
// When set on the `contacts` field, contacts are rendered as editable detail
|
|
26
31
|
// fields (ContactDetailsFormComponent) instead of cards (ContactCardComponent)
|
|
27
32
|
type EditableContactDetailsSpecifier = `contact:editableDetails`
|
|
28
|
-
export type
|
|
33
|
+
export type FormFieldModelSpecifier =
|
|
29
34
|
| OnlineLinkResourceSpecifier
|
|
30
35
|
| DatasetDistributionsSpecifier
|
|
31
36
|
| OnlineSingleLinkResourceSpecifier
|
|
32
37
|
| EditableContactDetailsSpecifier
|
|
38
|
+
| `flags:${RecordFlag}`
|
|
33
39
|
|
|
34
40
|
export type FormFieldComponentName =
|
|
35
41
|
| 'form-field-constraints-shortcuts'
|
|
@@ -37,8 +43,8 @@ export type FormFieldComponentName =
|
|
|
37
43
|
|
|
38
44
|
export interface EditorFieldIdentification {
|
|
39
45
|
// name of the target field in the record; will not change the record directly if not defined
|
|
40
|
-
model?:
|
|
41
|
-
modelSpecifier?:
|
|
46
|
+
model?: FormFieldModel
|
|
47
|
+
modelSpecifier?: FormFieldModelSpecifier
|
|
42
48
|
|
|
43
49
|
// if no model is given, a component can be shown instead
|
|
44
50
|
componentName?: FormFieldComponentName
|
|
@@ -6,8 +6,6 @@ export * from './lib/feature-map.module'
|
|
|
6
6
|
export * from './lib/map-state-container/map-state-container.component'
|
|
7
7
|
export * from './lib/constant'
|
|
8
8
|
export * from './lib/utils'
|
|
9
|
-
export * from './lib/style'
|
|
10
9
|
export * from './lib/layers-panel/layers-panel.component'
|
|
11
10
|
export * from './lib/add-layer-from-catalog/add-layer-from-catalog.component'
|
|
12
11
|
export * from './lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component'
|
|
13
|
-
export * from './lib/geocoding/geocoding.component'
|
|
@@ -2,18 +2,11 @@ import { NgModule } from '@angular/core'
|
|
|
2
2
|
import { StoreModule } from '@ngrx/store'
|
|
3
3
|
import * as fromMap from './+state/map.reducer'
|
|
4
4
|
import { MapFacade } from './+state/map.facade'
|
|
5
|
-
import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
|
|
6
5
|
|
|
7
6
|
@NgModule({
|
|
8
7
|
imports: [
|
|
9
8
|
StoreModule.forFeature(fromMap.MAP_FEATURE_KEY, fromMap.mapReducer),
|
|
10
9
|
],
|
|
11
|
-
providers: [
|
|
12
|
-
MapFacade,
|
|
13
|
-
{
|
|
14
|
-
provide: GEOCODING_PROVIDER,
|
|
15
|
-
useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
|
|
16
|
-
},
|
|
17
|
-
],
|
|
10
|
+
providers: [MapFacade],
|
|
18
11
|
})
|
|
19
12
|
export class FeatureMapModule {}
|
package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts
CHANGED
|
@@ -33,7 +33,6 @@ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain
|
|
|
33
33
|
import { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
|
|
34
34
|
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
35
35
|
import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
|
|
36
|
-
import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
|
|
37
36
|
import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
|
|
38
37
|
import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
|
|
39
38
|
import { matCloseOutline } from '@ng-icons/material-icons/outline'
|
|
@@ -48,6 +47,10 @@ marker('notify.reuse.form.error.title')
|
|
|
48
47
|
marker('notify.reuse.form.error.body')
|
|
49
48
|
|
|
50
49
|
export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
|
|
50
|
+
// language assigned to the created reuse record; falls back to the current UI language
|
|
51
|
+
export const NEW_RECORD_DEFAULT_LANGUAGE = new InjectionToken<string>(
|
|
52
|
+
'newRecordDefaultLanguage'
|
|
53
|
+
)
|
|
51
54
|
|
|
52
55
|
@Component({
|
|
53
56
|
selector: 'gn-ui-notify-reuse-form',
|
|
@@ -86,6 +89,9 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
86
89
|
private readonly notificationsService = inject(NotificationsService)
|
|
87
90
|
private readonly platformServiceInterface = inject(PlatformServiceInterface)
|
|
88
91
|
reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
|
|
92
|
+
private newRecordDefaultLanguage = inject(NEW_RECORD_DEFAULT_LANGUAGE, {
|
|
93
|
+
optional: true,
|
|
94
|
+
})
|
|
89
95
|
private locationStrategy = inject(LocationStrategy)
|
|
90
96
|
|
|
91
97
|
me = toSignal(this.platformServiceInterface.getMe())
|
|
@@ -158,8 +164,7 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
158
164
|
submit() {
|
|
159
165
|
if (!this.isFormValid()) return
|
|
160
166
|
const defaultLang =
|
|
161
|
-
|
|
162
|
-
this.translate.currentLang
|
|
167
|
+
this.newRecordDefaultLanguage ?? this.translate.currentLang
|
|
163
168
|
const onlineResource: OnlineLinkResource = {
|
|
164
169
|
type: 'link',
|
|
165
170
|
url: new URL(this.url()),
|
|
@@ -40,7 +40,9 @@ export class MdViewEffects {
|
|
|
40
40
|
this.actions$.pipe(
|
|
41
41
|
ofType(MdViewActions.loadFullMetadataSuccess),
|
|
42
42
|
filter(({ full }) => full !== undefined),
|
|
43
|
-
switchMap(({ full }) =>
|
|
43
|
+
switchMap(({ full }) =>
|
|
44
|
+
this.recordsRepository.queryLinkedFeatureCatalog(full)
|
|
45
|
+
),
|
|
44
46
|
map((featureCatalog) =>
|
|
45
47
|
MdViewActions.loadFeatureCatalogSuccess({
|
|
46
48
|
datasetCatalog: featureCatalog,
|
|
@@ -162,7 +162,7 @@ export class MdViewFacade {
|
|
|
162
162
|
link.accessServiceProtocol === 'ogcFeatures'
|
|
163
163
|
) {
|
|
164
164
|
return from(
|
|
165
|
-
this.dataService.getItemsFromOgcApi(link.url.href, 1)
|
|
165
|
+
this.dataService.getItemsFromOgcApi(link.url.href, link.name, 1)
|
|
166
166
|
).pipe(
|
|
167
167
|
map((collectionRecords: OgcApiRecord[]) => {
|
|
168
168
|
return collectionRecords &&
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './lib/feature-search.module'
|
|
2
|
+
export * from './lib/geocoding/geocoding.service'
|
|
3
|
+
export * from './lib/location-search/location-search.component'
|
|
2
4
|
export * from './lib/filter-geometry.token'
|
|
3
5
|
export * from './lib/record-url.token'
|
|
4
6
|
export * from './lib/state/actions'
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
(bboxChange)="onBboxChange($event)"
|
|
14
14
|
(errorChange)="onSpatialExtentError($event)"
|
|
15
15
|
[attr.data-cy-field]="fieldName"
|
|
16
|
-
|
|
16
|
+
>
|
|
17
|
+
<gn-ui-location-search
|
|
18
|
+
[placeholder]="'search.filters.spatialExtent.searchLocation' | translate"
|
|
19
|
+
(bboxSelected)="onLocationSelected($event)"
|
|
20
|
+
data-cy="locationSearch"
|
|
21
|
+
></gn-ui-location-search>
|
|
22
|
+
</gn-ui-spatial-extent-dropdown>
|
|
17
23
|
} @else {
|
|
18
24
|
<gn-ui-dropdown-multiselect
|
|
19
25
|
class="w-full"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
|
+
InjectionToken,
|
|
4
5
|
Input,
|
|
5
6
|
OnInit,
|
|
7
|
+
ViewChild,
|
|
6
8
|
inject,
|
|
7
9
|
} from '@angular/core'
|
|
8
10
|
import {
|
|
@@ -26,8 +28,20 @@ import { DateRange } from '../../../../../../libs/api/repository/src'
|
|
|
26
28
|
import { CommonModule } from '@angular/common'
|
|
27
29
|
import { BoundingBox } from '../../../../../../libs/util/shared/src'
|
|
28
30
|
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
29
|
-
import { TranslateService } from '@ngx-translate/core'
|
|
30
|
-
import {
|
|
31
|
+
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
|
|
32
|
+
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
33
|
+
import {
|
|
34
|
+
LocationBbox,
|
|
35
|
+
LocationSearchComponent,
|
|
36
|
+
} from '../location-search/location-search.component'
|
|
37
|
+
|
|
38
|
+
marker('search.filters.spatialExtent.searchLocation')
|
|
39
|
+
|
|
40
|
+
// in MB, null means no limit
|
|
41
|
+
export const SPATIAL_EXTENT_MAX_FILE_SIZE = new InjectionToken<number | null>(
|
|
42
|
+
'spatialExtentMaxFileSize',
|
|
43
|
+
{ factory: () => null }
|
|
44
|
+
)
|
|
31
45
|
|
|
32
46
|
@Component({
|
|
33
47
|
selector: 'gn-ui-filter-dropdown',
|
|
@@ -40,6 +54,8 @@ import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/
|
|
|
40
54
|
DateRangeDropdownComponent,
|
|
41
55
|
DropdownMultiselectComponent,
|
|
42
56
|
SpatialExtentDropdownComponent,
|
|
57
|
+
LocationSearchComponent,
|
|
58
|
+
TranslatePipe,
|
|
43
59
|
],
|
|
44
60
|
})
|
|
45
61
|
export class FilterDropdownComponent implements OnInit {
|
|
@@ -52,8 +68,10 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
52
68
|
@Input() fieldName: string
|
|
53
69
|
@Input() title: string
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
@ViewChild(SpatialExtentDropdownComponent)
|
|
72
|
+
spatialExtentDropdown: SpatialExtentDropdownComponent
|
|
73
|
+
|
|
74
|
+
spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
|
|
57
75
|
|
|
58
76
|
fieldType: FieldType
|
|
59
77
|
choices$: Observable<Choice[]>
|
|
@@ -96,6 +114,10 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
96
114
|
this.clearSpatialExtentErrorNotification()
|
|
97
115
|
}
|
|
98
116
|
|
|
117
|
+
onLocationSelected({ bbox, label }: LocationBbox) {
|
|
118
|
+
this.spatialExtentDropdown.setLocationBbox(bbox, label)
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
onSpatialExtentError(error: SpatialExtentDropdownError) {
|
|
100
122
|
this.clearSpatialExtentErrorNotification()
|
|
101
123
|
this.spatialExtentErrorNotificationId =
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Injectable, InjectionToken, inject } from '@angular/core'
|
|
2
|
+
import {
|
|
3
|
+
queryGeoadmin,
|
|
4
|
+
GeoadminOptions,
|
|
5
|
+
GeocodingResult,
|
|
6
|
+
queryGeonames,
|
|
7
|
+
GeonamesOptions,
|
|
8
|
+
GeoplateformeOptions,
|
|
9
|
+
queryGeoplateforme,
|
|
10
|
+
} from '@geospatial-sdk/geocoding'
|
|
11
|
+
import { from, Observable, throwError } from 'rxjs'
|
|
12
|
+
import { catchError } from 'rxjs/operators'
|
|
13
|
+
|
|
14
|
+
type GeoadminGeocodingProvider = ['geoadmin', GeoadminOptions]
|
|
15
|
+
type GeonamesGeocodingProvider = ['geonames', GeonamesOptions]
|
|
16
|
+
type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
|
|
17
|
+
export type GeocodingProvider =
|
|
18
|
+
| GeoadminGeocodingProvider
|
|
19
|
+
| GeonamesGeocodingProvider
|
|
20
|
+
| GeoplateformeGeocodingProvider
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_GEOCODING_PROVIDER = [
|
|
23
|
+
'geonames',
|
|
24
|
+
{ maxRows: 5 },
|
|
25
|
+
] as GeocodingProvider
|
|
26
|
+
|
|
27
|
+
export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
|
|
28
|
+
'geocoding-provider',
|
|
29
|
+
{
|
|
30
|
+
providedIn: 'root',
|
|
31
|
+
factory: (): GeocodingProvider => DEFAULT_GEOCODING_PROVIDER,
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// these options are arrays in the providers, but they can be configured
|
|
36
|
+
// with a single value for simplicity
|
|
37
|
+
const GEOCODING_PROVIDER_ARRAY_OPTIONS: {
|
|
38
|
+
geoadmin: (keyof GeoadminOptions)[]
|
|
39
|
+
geoplateforme: (keyof GeoplateformeOptions)[]
|
|
40
|
+
} = {
|
|
41
|
+
geoadmin: ['origins', 'features'],
|
|
42
|
+
geoplateforme: [
|
|
43
|
+
'index',
|
|
44
|
+
'category',
|
|
45
|
+
'type',
|
|
46
|
+
'postCode',
|
|
47
|
+
'cityCode',
|
|
48
|
+
'depCode',
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function wrapArrayOptions<T extends object>(options: T, keys: (keyof T)[]): T {
|
|
53
|
+
const wrapped = { ...options }
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (typeof wrapped[key] === 'string') {
|
|
56
|
+
wrapped[key] = [wrapped[key]] as T[keyof T]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return wrapped
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Injectable({
|
|
63
|
+
providedIn: 'root',
|
|
64
|
+
})
|
|
65
|
+
export class GeocodingService {
|
|
66
|
+
private provider = inject<GeocodingProvider>(GEOCODING_PROVIDER)
|
|
67
|
+
|
|
68
|
+
query(text: string): Observable<GeocodingResult[]> {
|
|
69
|
+
let queryObservable: Observable<GeocodingResult[]>
|
|
70
|
+
switch (this.provider[0]) {
|
|
71
|
+
case 'geoadmin':
|
|
72
|
+
queryObservable = from(
|
|
73
|
+
queryGeoadmin(
|
|
74
|
+
text,
|
|
75
|
+
wrapArrayOptions(
|
|
76
|
+
this.provider[1] as GeoadminOptions,
|
|
77
|
+
GEOCODING_PROVIDER_ARRAY_OPTIONS.geoadmin
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
break
|
|
82
|
+
case 'geonames':
|
|
83
|
+
queryObservable = from(
|
|
84
|
+
queryGeonames(text, this.provider[1] as GeonamesOptions)
|
|
85
|
+
)
|
|
86
|
+
break
|
|
87
|
+
case 'geoplateforme':
|
|
88
|
+
queryObservable = from(
|
|
89
|
+
queryGeoplateforme(text, {
|
|
90
|
+
...wrapArrayOptions(
|
|
91
|
+
this.provider[1] as GeoplateformeOptions,
|
|
92
|
+
GEOCODING_PROVIDER_ARRAY_OPTIONS.geoplateforme
|
|
93
|
+
),
|
|
94
|
+
returnTrueGeometry: true,
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
break
|
|
98
|
+
default:
|
|
99
|
+
return throwError(
|
|
100
|
+
() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`)
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
return queryObservable.pipe(catchError((error) => throwError(error)))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<gn-ui-autocomplete
|
|
2
|
+
[action]="searchAction"
|
|
3
|
+
[displayWithFn]="displayWithFn"
|
|
4
|
+
[placeholder]="placeholder"
|
|
5
|
+
[displayWithTemplate]="defaultItemTpl"
|
|
6
|
+
[clearOnSelection]="true"
|
|
7
|
+
(itemSelected)="handleItemSelected($event)"
|
|
8
|
+
>
|
|
9
|
+
</gn-ui-autocomplete>
|
|
10
|
+
|
|
11
|
+
<ng-template #defaultItemTpl let-result>
|
|
12
|
+
<div class="py-2">
|
|
13
|
+
@if (getSecondaryLabel(result)) {
|
|
14
|
+
<div class="gn-ui-card-detail">{{ getSecondaryLabel(result) }}</div>
|
|
15
|
+
}
|
|
16
|
+
<div class="gn-ui-card-title">{{ getDisplayLabel(result) }}</div>
|
|
17
|
+
</div>
|
|
18
|
+
</ng-template>
|