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
|
@@ -2,6 +2,23 @@ import { Injectable } from '@angular/core'
|
|
|
2
2
|
import { extend } from 'ol/extent.js'
|
|
3
3
|
import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
4
4
|
import { BoundingBox, getGeometryBoundingBox } from '../../../../../../libs/util/shared/src'
|
|
5
|
+
import { MapContextLayer } from '@geospatial-sdk/core'
|
|
6
|
+
import {
|
|
7
|
+
createSpatialExtentLayer,
|
|
8
|
+
SpatialExtentLayerStyle,
|
|
9
|
+
} from '../../../../../../libs/ui/map/src'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Style of the extent overlay drawn on top of the previewed data: a dashed
|
|
13
|
+
* black outline over a very light fill, so the extent stays readable without
|
|
14
|
+
* hiding the data underneath.
|
|
15
|
+
*/
|
|
16
|
+
const RECORD_EXTENT_OVERLAY_STYLE: SpatialExtentLayerStyle = {
|
|
17
|
+
'stroke-color': 'rgba(0, 0, 0, 0.6)',
|
|
18
|
+
'stroke-width': 2,
|
|
19
|
+
'stroke-line-dash': [8, 6],
|
|
20
|
+
'fill-color': 'rgba(0, 0, 0, 0.03)',
|
|
21
|
+
}
|
|
5
22
|
|
|
6
23
|
@Injectable({
|
|
7
24
|
providedIn: 'root',
|
|
@@ -26,4 +43,20 @@ export class MapUtilsService {
|
|
|
26
43
|
[Infinity, Infinity, -Infinity, -Infinity]
|
|
27
44
|
)
|
|
28
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Builds a non-interactive overlay layer drawing the spatial extent(s)
|
|
49
|
+
* declared in the record's metadata (bounding boxes and/or geometries).
|
|
50
|
+
* Returns null when the record has no usable spatial extent.
|
|
51
|
+
*
|
|
52
|
+
* This is purely for display and is independent from the map's initial view,
|
|
53
|
+
* which is derived separately (see {@link getRecordExtent}).
|
|
54
|
+
*/
|
|
55
|
+
getRecordExtentLayer(record: Partial<CatalogRecord>): MapContextLayer | null {
|
|
56
|
+
return createSpatialExtentLayer(record.spatialExtents ?? [], {
|
|
57
|
+
label: 'Spatial extent',
|
|
58
|
+
clickable: false,
|
|
59
|
+
style: RECORD_EXTENT_OVERLAY_STYLE,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
29
62
|
}
|
|
@@ -431,11 +431,19 @@ export class MapViewComponent implements AfterViewInit {
|
|
|
431
431
|
}),
|
|
432
432
|
withLatestFrom(this.mdViewFacade.metadata$),
|
|
433
433
|
map(([context, metadata]) => {
|
|
434
|
-
|
|
434
|
+
// overlay the record's declared spatial extent on top of the data layers
|
|
435
|
+
const extentLayer = this.mapUtils.getRecordExtentLayer(metadata)
|
|
436
|
+
const layers = extentLayer
|
|
437
|
+
? [...context.layers, extentLayer]
|
|
438
|
+
: context.layers
|
|
439
|
+
// the view (initial zoom) is derived from the data layer or, as a
|
|
440
|
+
// fallback, from the record extent — independently from the overlay above
|
|
441
|
+
if (context.view) return { ...context, layers }
|
|
435
442
|
const extent = this.mapUtils.getRecordExtent(metadata)
|
|
436
443
|
const view = extent ? { extent } : null
|
|
437
444
|
return {
|
|
438
445
|
...context,
|
|
446
|
+
layers,
|
|
439
447
|
view,
|
|
440
448
|
}
|
|
441
449
|
}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component,
|
|
1
|
+
import { Component, inject, InjectionToken } from '@angular/core'
|
|
2
2
|
import { LANGUAGE_STORAGE_KEY } from '../../../../../../libs/util/i18n/src'
|
|
3
3
|
import { TranslateService } from '@ngx-translate/core'
|
|
4
4
|
import { DropdownSelectorComponent } from '../../../../../../libs/ui/inputs/src'
|
|
@@ -26,7 +26,7 @@ export class LanguageSwitcherComponent {
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
get currentLang() {
|
|
29
|
-
return this.translate.
|
|
29
|
+
return this.translate.getCurrentLang()
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
changeLanguage(value: unknown) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<div
|
|
2
|
-
class="bg-gray-50 rounded border border-gray-200 shadow-md p-4 flex flex-col gap-
|
|
2
|
+
class="bg-gray-50 text-black rounded border border-gray-200 shadow-md p-4 flex flex-col gap-2 w-full"
|
|
3
3
|
data-test="contact-details"
|
|
4
4
|
>
|
|
5
5
|
@if (displayName) {
|
|
6
|
-
<div class="flex items-center gap-
|
|
6
|
+
<div class="flex items-center gap-2.5">
|
|
7
7
|
@if (organization?.logoUrl?.href) {
|
|
8
8
|
<div
|
|
9
|
-
class="flex items-center justify-center rounded-
|
|
9
|
+
class="flex items-center justify-center rounded-lg bg-white w-14 h-14 shrink-0 overflow-hidden"
|
|
10
10
|
>
|
|
11
11
|
<gn-ui-thumbnail
|
|
12
12
|
class="relative h-full w-full"
|
|
@@ -23,74 +23,66 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
[class.opacity-30]="!contact.email"
|
|
32
|
-
[class.opacity-75]="!!contact.email"
|
|
33
|
-
name="matMailOutline"
|
|
34
|
-
></ng-icon>
|
|
26
|
+
@if (contact.email || contact.phone) {
|
|
27
|
+
<div
|
|
28
|
+
class="grid gap-1 items-start"
|
|
29
|
+
[class.grid-cols-2]="contact.email && contact.phone"
|
|
30
|
+
>
|
|
35
31
|
@if (contact.email) {
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
|
|
32
|
+
<div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
|
|
33
|
+
<ng-icon
|
|
34
|
+
class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
|
|
35
|
+
name="matMailOutline"
|
|
36
|
+
></ng-icon>
|
|
37
|
+
<a
|
|
38
|
+
[href]="'mailto:' + contact.email"
|
|
39
|
+
class="text-sm break-all hover:underline"
|
|
40
|
+
data-test="contact-details-email"
|
|
41
|
+
>{{ contact.email }}</a
|
|
42
|
+
>
|
|
43
|
+
</div>
|
|
42
44
|
}
|
|
43
|
-
</div>
|
|
44
|
-
<div class="bg-gray-100 flex items-center gap-2 px-3 py-2">
|
|
45
|
-
<ng-icon
|
|
46
|
-
class="!w-5 !h-5 !text-[20px] shrink-0"
|
|
47
|
-
[class.opacity-30]="!contact.phone"
|
|
48
|
-
[class.opacity-75]="!!contact.phone"
|
|
49
|
-
name="matCallOutline"
|
|
50
|
-
></ng-icon>
|
|
51
45
|
@if (contact.phone) {
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
<div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
|
|
47
|
+
<ng-icon
|
|
48
|
+
class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
|
|
49
|
+
name="matCallOutline"
|
|
50
|
+
></ng-icon>
|
|
51
|
+
<span class="text-sm" data-test="contact-details-phone">{{
|
|
52
|
+
contact.phone
|
|
53
|
+
}}</span>
|
|
54
|
+
</div>
|
|
55
55
|
}
|
|
56
56
|
</div>
|
|
57
|
-
|
|
57
|
+
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
name="matLocationOnOutline"
|
|
66
|
-
></ng-icon>
|
|
67
|
-
@if (addressLines.length) {
|
|
59
|
+
@if (addressLines.length) {
|
|
60
|
+
<div class="bg-gray-100 rounded-lg flex items-start gap-2 px-3 py-2">
|
|
61
|
+
<ng-icon
|
|
62
|
+
class="!w-5 !h-5 !text-[20px] shrink-0 mt-0.5 opacity-30"
|
|
63
|
+
name="matLocationOnOutline"
|
|
64
|
+
></ng-icon>
|
|
68
65
|
<div class="flex flex-col" data-test="contact-details-address">
|
|
69
66
|
@for (line of addressLines; track line) {
|
|
70
67
|
<p class="text-sm m-0">{{ line }}</p>
|
|
71
68
|
}
|
|
72
69
|
</div>
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<div class="bg-gray-100 rounded-md flex items-center gap-2 px-3 py-2">
|
|
73
|
+
@if (organization?.website) {
|
|
74
|
+
<div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
|
|
79
75
|
<ng-icon
|
|
80
|
-
class="!w-5 !h-5 !text-[20px] shrink-0"
|
|
81
|
-
[class.opacity-30]="!organization.website"
|
|
82
|
-
[class.opacity-75]="!!organization.website"
|
|
76
|
+
class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
|
|
83
77
|
name="matOpenInNew"
|
|
84
78
|
></ng-icon>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>
|
|
93
|
-
}
|
|
79
|
+
<a
|
|
80
|
+
[href]="organization.website.href"
|
|
81
|
+
target="_blank"
|
|
82
|
+
class="text-sm break-all hover:underline"
|
|
83
|
+
data-test="contact-details-website"
|
|
84
|
+
>{{ organization.website.href }}</a
|
|
85
|
+
>
|
|
94
86
|
</div>
|
|
95
87
|
}
|
|
96
88
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<gn-ui-button
|
|
2
|
-
[type]="overlayOpen ? '
|
|
2
|
+
[type]="overlayOpen ? 'gray-light' : 'primary-light'"
|
|
3
3
|
extraClass="group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded"
|
|
4
4
|
data-test="contact-pill"
|
|
5
5
|
(buttonClick)="toggleOverlay()"
|
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
#overlayOrigin="cdkOverlayOrigin"
|
|
8
8
|
>
|
|
9
9
|
<span
|
|
10
|
-
class="font-title font-medium text-base leading-tight truncate
|
|
10
|
+
class="font-title font-medium text-base leading-tight truncate"
|
|
11
|
+
[class]="!overlayOpen ? 'text-primary-black group-hover:text-white' : ''"
|
|
11
12
|
[title]="displayName"
|
|
12
13
|
>{{ displayName }}</span
|
|
13
14
|
>
|
|
14
15
|
<div
|
|
15
|
-
class="gn-ui-card-icon items-center justify-center w-10 h-8
|
|
16
|
+
class="gn-ui-card-icon items-center justify-center w-10 h-8"
|
|
17
|
+
[class]="
|
|
18
|
+
!overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''
|
|
19
|
+
"
|
|
16
20
|
>
|
|
17
21
|
@if (overlayOpen) {
|
|
18
22
|
<ng-icon class="!w-6 !h-6 !text-[24px]" name="matClose"></ng-icon>
|
package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** the animation values are made to be identical to the cdk overlay values */
|
|
2
|
+
.lightbox-image {
|
|
3
|
+
animation: show 400ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
|
|
4
|
+
opacity: 0;
|
|
5
|
+
transform: scale(0.9);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@keyframes show {
|
|
9
|
+
100% {
|
|
10
|
+
opacity: 1;
|
|
11
|
+
transform: none;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<gn-ui-content-ghost
|
|
2
|
-
[showContent]="imageUrl !== undefined"
|
|
2
|
+
[showContent]="imageUrl() !== undefined"
|
|
3
3
|
ghostClass="h-full w-full"
|
|
4
4
|
>
|
|
5
5
|
@if (imageUrl) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
<gn-ui-thumbnail
|
|
11
11
|
class="relative h-full w-full"
|
|
12
|
-
[thumbnailUrl]="imageUrl"
|
|
12
|
+
[thumbnailUrl]="imageUrl()"
|
|
13
13
|
fit="cover"
|
|
14
14
|
(placeholderShown)="isPlaceholderShown.emit($event)"
|
|
15
15
|
></gn-ui-thumbnail>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
class="absolute bottom-0 right-0 z-10 mr-2 mb-2"
|
|
19
19
|
[type]="'outline'"
|
|
20
20
|
[extraClass]="'!py-2 !px-0'"
|
|
21
|
-
(buttonClick)="openLightbox(
|
|
21
|
+
(buttonClick)="openLightbox()"
|
|
22
22
|
>
|
|
23
23
|
<ng-icon name="matZoomOutMap"></ng-icon>
|
|
24
24
|
</gn-ui-button>
|
|
@@ -26,3 +26,13 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
}
|
|
28
28
|
</gn-ui-content-ghost>
|
|
29
|
+
|
|
30
|
+
<ng-template #imageTemplate>
|
|
31
|
+
<img
|
|
32
|
+
data-test="lightbox-image"
|
|
33
|
+
class="lightbox-image bg-white max-h-[90vh] max-w-[90vw]"
|
|
34
|
+
[src]="imageUrl()"
|
|
35
|
+
alt="Preview image"
|
|
36
|
+
(click)="closeLightbox()"
|
|
37
|
+
/>
|
|
38
|
+
</ng-template>
|
package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
inject,
|
|
4
|
+
input,
|
|
5
|
+
output,
|
|
6
|
+
TemplateRef,
|
|
7
|
+
viewChild,
|
|
8
|
+
ViewContainerRef,
|
|
9
|
+
} from '@angular/core'
|
|
3
10
|
import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
|
|
4
11
|
import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
|
|
5
12
|
import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
|
|
6
13
|
import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
|
|
7
14
|
import { matZoomOutMap } from '@ng-icons/material-icons/baseline'
|
|
15
|
+
import { Overlay, OverlayRef } from '@angular/cdk/overlay'
|
|
16
|
+
import { TemplatePortal } from '@angular/cdk/portal'
|
|
8
17
|
|
|
9
18
|
@Component({
|
|
10
19
|
selector: 'gn-ui-image-overlay-preview',
|
|
@@ -20,9 +29,37 @@ import { matZoomOutMap } from '@ng-icons/material-icons/baseline'
|
|
|
20
29
|
],
|
|
21
30
|
})
|
|
22
31
|
export class ImageOverlayPreviewComponent {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
imageUrl = input<string>()
|
|
33
|
+
isPlaceholderShown = output<boolean>({})
|
|
34
|
+
|
|
35
|
+
private imageTemplate = viewChild<TemplateRef<HTMLElement>>('imageTemplate')
|
|
36
|
+
private overlay = inject(Overlay)
|
|
37
|
+
private viewContainerRef = inject(ViewContainerRef)
|
|
38
|
+
private overlayRef: OverlayRef
|
|
39
|
+
|
|
40
|
+
openLightbox() {
|
|
41
|
+
const positionStrategy = this.overlay
|
|
42
|
+
.position()
|
|
43
|
+
.global()
|
|
44
|
+
.centerHorizontally()
|
|
45
|
+
.centerVertically()
|
|
46
|
+
const scrollStrategy = this.overlay.scrollStrategies.block()
|
|
47
|
+
|
|
48
|
+
this.overlayRef = this.overlay.create({
|
|
49
|
+
hasBackdrop: true,
|
|
50
|
+
backdropClass: 'bg-black/80',
|
|
51
|
+
positionStrategy,
|
|
52
|
+
scrollStrategy,
|
|
53
|
+
})
|
|
54
|
+
this.overlayRef.backdropClick().subscribe(() => this.overlayRef.dispose())
|
|
55
|
+
const portal = new TemplatePortal(
|
|
56
|
+
this.imageTemplate(),
|
|
57
|
+
this.viewContainerRef
|
|
58
|
+
)
|
|
59
|
+
this.overlayRef.attach(portal)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
closeLightbox() {
|
|
63
|
+
this.overlayRef?.dispose()
|
|
27
64
|
}
|
|
28
65
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
2
|
-
import {
|
|
2
|
+
import { parse } from 'marked'
|
|
3
3
|
|
|
4
4
|
@Component({
|
|
5
5
|
selector: 'gn-ui-markdown-parser',
|
|
@@ -14,6 +14,6 @@ export class MarkdownParserComponent {
|
|
|
14
14
|
@Input() whitoutStyles?: boolean
|
|
15
15
|
|
|
16
16
|
get parsedMarkdown() {
|
|
17
|
-
return
|
|
17
|
+
return parse(this.textContent ?? '')
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -28,13 +28,23 @@
|
|
|
28
28
|
<gn-ui-max-lines [maxLines]="7">
|
|
29
29
|
<div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
|
|
30
30
|
@for (keyword of metadata.keywords; track keyword) {
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
<ng-template #keywordTooltip>
|
|
32
|
+
@for (segment of keywordTooltipSegments(keyword); track $index) {
|
|
33
|
+
<span [class.font-bold]="$last">{{ segment }}</span>
|
|
34
|
+
@if (!$last) {
|
|
35
|
+
<span> > </span>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</ng-template>
|
|
39
|
+
<gn-ui-popover [content]="keywordTooltip" theme="dark">
|
|
40
|
+
<gn-ui-button
|
|
41
|
+
type="primary-light"
|
|
42
|
+
class="inline-block opacity-70"
|
|
43
|
+
extraClass="lowercase text-sm py-1 px-2"
|
|
44
|
+
(buttonClick)="onKeywordClick(keyword)"
|
|
45
|
+
>{{ keyword.label }}</gn-ui-button
|
|
46
|
+
>
|
|
47
|
+
</gn-ui-popover>
|
|
38
48
|
}
|
|
39
49
|
</div>
|
|
40
50
|
</gn-ui-max-lines>
|
|
@@ -113,7 +123,7 @@
|
|
|
113
123
|
>
|
|
114
124
|
<div class="flex flex-col gap-1 pt-3 pb-4">
|
|
115
125
|
@for (group of contactGroups; track group.role) {
|
|
116
|
-
<div class="flex flex-col gap-1 rounded
|
|
126
|
+
<div class="flex flex-col gap-1 rounded py-4 px-2">
|
|
117
127
|
<p class="text-xs font-normal text-black">
|
|
118
128
|
{{ group.roleLabel | translate }}
|
|
119
129
|
</p>
|
|
@@ -19,12 +19,18 @@ import {
|
|
|
19
19
|
ExpandablePanelComponent,
|
|
20
20
|
MaxLinesComponent,
|
|
21
21
|
} from '../../../../../../libs/ui/layout/src'
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
TranslateDirective,
|
|
24
|
+
TranslatePipe,
|
|
25
|
+
TranslateService,
|
|
26
|
+
} from '@ngx-translate/core'
|
|
27
|
+
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
23
28
|
import {
|
|
24
29
|
BadgeComponent,
|
|
25
30
|
ButtonComponent,
|
|
26
31
|
CopyTextButtonComponent,
|
|
27
32
|
} from '../../../../../../libs/ui/inputs/src'
|
|
33
|
+
import { PopoverComponent } from '../../../../../../libs/ui/widgets/src'
|
|
28
34
|
import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
|
|
29
35
|
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
30
36
|
import { matOpenInNew } from '@ng-icons/material-icons/baseline'
|
|
@@ -35,6 +41,11 @@ import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/sr
|
|
|
35
41
|
import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
|
|
36
42
|
import { ContactPillComponent } from '../contact-pill/contact-pill.component'
|
|
37
43
|
|
|
44
|
+
marker('domain.record.keywordType.theme')
|
|
45
|
+
marker('domain.record.keywordType.place')
|
|
46
|
+
marker('domain.record.keywordType.temporal')
|
|
47
|
+
marker('domain.record.keywordType.other')
|
|
48
|
+
|
|
38
49
|
@Component({
|
|
39
50
|
selector: 'gn-ui-metadata-info',
|
|
40
51
|
templateUrl: './metadata-info.component.html',
|
|
@@ -56,6 +67,7 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
|
|
|
56
67
|
GnUiHumanizeDateDirective,
|
|
57
68
|
SpatialExtentComponent,
|
|
58
69
|
ContactPillComponent,
|
|
70
|
+
PopoverComponent,
|
|
59
71
|
],
|
|
60
72
|
viewProviders: [
|
|
61
73
|
provideIcons({
|
|
@@ -66,6 +78,7 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
|
|
|
66
78
|
})
|
|
67
79
|
export class MetadataInfoComponent {
|
|
68
80
|
private dateService = inject(DateService)
|
|
81
|
+
private translateService = inject(TranslateService)
|
|
69
82
|
|
|
70
83
|
@Input() metadata: Partial<CatalogRecord>
|
|
71
84
|
@Input() incomplete: boolean
|
|
@@ -167,4 +180,19 @@ export class MetadataInfoComponent {
|
|
|
167
180
|
onKeywordClick(keyword: Keyword) {
|
|
168
181
|
this.keyword.emit(keyword)
|
|
169
182
|
}
|
|
183
|
+
|
|
184
|
+
keywordTooltipSegments(keyword: Keyword): string[] {
|
|
185
|
+
if (keyword.hierarchyPath?.length) {
|
|
186
|
+
return keyword.hierarchyPath
|
|
187
|
+
}
|
|
188
|
+
if (keyword.thesaurus?.name) {
|
|
189
|
+
return [keyword.thesaurus.name, keyword.label]
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
this.translateService.instant(
|
|
193
|
+
`domain.record.keywordType.${keyword.type}`
|
|
194
|
+
),
|
|
195
|
+
keyword.label,
|
|
196
|
+
]
|
|
197
|
+
}
|
|
170
198
|
}
|
|
@@ -25,6 +25,7 @@ export class ButtonComponent {
|
|
|
25
25
|
| 'outline'
|
|
26
26
|
| 'light'
|
|
27
27
|
| 'gray'
|
|
28
|
+
| 'gray-light'
|
|
28
29
|
| 'black'
|
|
29
30
|
| 'primary-light'
|
|
30
31
|
) {
|
|
@@ -45,6 +46,9 @@ export class ButtonComponent {
|
|
|
45
46
|
case 'gray':
|
|
46
47
|
this.btnClass = 'gn-ui-btn-gray'
|
|
47
48
|
break
|
|
49
|
+
case 'gray-light':
|
|
50
|
+
this.btnClass = 'gn-ui-btn-gray-light'
|
|
51
|
+
break
|
|
48
52
|
case 'black':
|
|
49
53
|
this.btnClass = 'gn-ui-btn-black'
|
|
50
54
|
break
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
4
|
EventEmitter,
|
|
5
|
+
inject,
|
|
5
6
|
Input,
|
|
6
7
|
Output,
|
|
7
|
-
inject,
|
|
8
8
|
} from '@angular/core'
|
|
9
9
|
import {
|
|
10
10
|
DateAdapter,
|
|
@@ -52,6 +52,6 @@ export class DatePickerComponent {
|
|
|
52
52
|
@Output() dateChange = new EventEmitter<Date>()
|
|
53
53
|
|
|
54
54
|
constructor() {
|
|
55
|
-
this.dateAdapter.setLocale(this.translate.
|
|
55
|
+
this.dateAdapter.setLocale(this.translate.getCurrentLang())
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'
|
|
2
2
|
import { CommonModule } from '@angular/common'
|
|
3
|
-
import {
|
|
4
|
-
import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON.js'
|
|
5
|
-
import GeoJSON from 'ol/format/GeoJSON.js'
|
|
6
|
-
import { Polygon } from 'ol/geom.js'
|
|
7
|
-
import {
|
|
8
|
-
createViewFromLayer,
|
|
9
|
-
MapContext,
|
|
10
|
-
MapContextLayer,
|
|
11
|
-
} from '@geospatial-sdk/core'
|
|
3
|
+
import { createViewFromLayer, MapContext } from '@geospatial-sdk/core'
|
|
12
4
|
import { MapContainerComponent } from '../map-container/map-container.component'
|
|
13
5
|
import { BehaviorSubject, from, Observable, of } from 'rxjs'
|
|
14
6
|
import { map, switchMap, tap } from 'rxjs/operators'
|
|
15
7
|
import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/src/lib/model/record'
|
|
8
|
+
import { createSpatialExtentLayer } from '../../map-utils'
|
|
16
9
|
|
|
17
10
|
@Component({
|
|
18
11
|
selector: 'gn-ui-spatial-extent',
|
|
@@ -30,39 +23,10 @@ export class SpatialExtentComponent {
|
|
|
30
23
|
spatialExtents$ = new BehaviorSubject<DatasetSpatialExtent[]>([])
|
|
31
24
|
mapContext$: Observable<MapContext> = this.spatialExtents$.pipe(
|
|
32
25
|
switchMap((extents) => {
|
|
33
|
-
|
|
26
|
+
const layer = createSpatialExtentLayer(extents)
|
|
27
|
+
if (!layer) {
|
|
34
28
|
return of(null)
|
|
35
29
|
}
|
|
36
|
-
const featureCollection: GeoJSONFeatureCollection = {
|
|
37
|
-
type: 'FeatureCollection',
|
|
38
|
-
features: [],
|
|
39
|
-
}
|
|
40
|
-
extents.forEach((extent) => {
|
|
41
|
-
if (extent.geometry) {
|
|
42
|
-
featureCollection.features.push({
|
|
43
|
-
type: 'Feature',
|
|
44
|
-
properties: {},
|
|
45
|
-
geometry: extent.geometry,
|
|
46
|
-
})
|
|
47
|
-
} else if (extent.bbox?.length >= 0) {
|
|
48
|
-
featureCollection.features.push({
|
|
49
|
-
type: 'Feature',
|
|
50
|
-
properties: {},
|
|
51
|
-
geometry: this.bboxCoordsToGeometry(extent.bbox),
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const layer: MapContextLayer = {
|
|
57
|
-
type: 'geojson',
|
|
58
|
-
data: featureCollection,
|
|
59
|
-
label: 'Spatial extents',
|
|
60
|
-
style: {
|
|
61
|
-
'stroke-color': 'black',
|
|
62
|
-
'stroke-width': 2,
|
|
63
|
-
'fill-color': 'rgba(153, 153, 153, 0.3)',
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
30
|
return from(createViewFromLayer(layer)).pipe(
|
|
67
31
|
map((view) => ({ view, layers: [layer] }) as MapContext),
|
|
68
32
|
tap(() => this._cdr.markForCheck())
|
|
@@ -71,18 +35,4 @@ export class SpatialExtentComponent {
|
|
|
71
35
|
)
|
|
72
36
|
|
|
73
37
|
error = ''
|
|
74
|
-
|
|
75
|
-
bboxCoordsToGeometry(bbox: [number, number, number, number]): Geometry {
|
|
76
|
-
const geometry = new Polygon([
|
|
77
|
-
[
|
|
78
|
-
[bbox[0], bbox[1]],
|
|
79
|
-
[bbox[0], bbox[3]],
|
|
80
|
-
[bbox[2], bbox[3]],
|
|
81
|
-
[bbox[2], bbox[1]],
|
|
82
|
-
[bbox[0], bbox[1]],
|
|
83
|
-
],
|
|
84
|
-
])
|
|
85
|
-
|
|
86
|
-
return new GeoJSON().writeGeometryObject(geometry)
|
|
87
|
-
}
|
|
88
38
|
}
|
|
@@ -12,6 +12,54 @@ import {
|
|
|
12
12
|
MouseWheelZoom,
|
|
13
13
|
} from 'ol/interaction.js'
|
|
14
14
|
import MapBrowserEvent from 'ol/MapBrowserEvent.js'
|
|
15
|
+
import type { DatasetSpatialExtent } from '../../../../../libs/common/domain/src/lib/model/record'
|
|
16
|
+
import type {
|
|
17
|
+
MapContextLayer,
|
|
18
|
+
MapContextLayerGeojson,
|
|
19
|
+
} from '@geospatial-sdk/core'
|
|
20
|
+
import { spatialExtentsToFeatureCollection } from '../../../../../libs/util/shared/src'
|
|
21
|
+
|
|
22
|
+
export type SpatialExtentLayerStyle = NonNullable<
|
|
23
|
+
MapContextLayerGeojson['style']
|
|
24
|
+
>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default style for a spatial-extent layer: a solid black outline over a
|
|
28
|
+
* translucent grey fill.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_SPATIAL_EXTENT_STYLE: SpatialExtentLayerStyle = {
|
|
31
|
+
'stroke-color': 'black',
|
|
32
|
+
'stroke-width': 2,
|
|
33
|
+
'fill-color': 'rgba(153, 153, 153, 0.3)',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Builds a GeoJSON map layer drawing the given spatial extents: each extent is
|
|
38
|
+
* rendered from its own geometry or, when only a bounding box is available,
|
|
39
|
+
* from a polygon derived from that box.
|
|
40
|
+
*
|
|
41
|
+
* @returns the layer, or `null` when none of the extents can be represented.
|
|
42
|
+
*/
|
|
43
|
+
export function createSpatialExtentLayer(
|
|
44
|
+
extents: DatasetSpatialExtent[],
|
|
45
|
+
overrides?: {
|
|
46
|
+
label?: string
|
|
47
|
+
clickable?: boolean
|
|
48
|
+
style?: SpatialExtentLayerStyle
|
|
49
|
+
}
|
|
50
|
+
): MapContextLayer | null {
|
|
51
|
+
const data = spatialExtentsToFeatureCollection(extents)
|
|
52
|
+
if (data.features.length === 0) {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
type: 'geojson',
|
|
57
|
+
data,
|
|
58
|
+
label: 'Spatial extents',
|
|
59
|
+
style: DEFAULT_SPATIAL_EXTENT_STYLE,
|
|
60
|
+
...overrides,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
15
63
|
|
|
16
64
|
export function prioritizePageScroll(interactions: Collection<Interaction>) {
|
|
17
65
|
interactions.clear()
|