geonetwork-ui 2.9.0-dev.2757b04b7 → 2.9.0-dev.27de12f92

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.
Files changed (56) hide show
  1. package/fesm2022/geonetwork-ui.mjs +701 -566
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +254 -162
  4. package/index.d.ts.map +1 -1
  5. package/package.json +7 -8
  6. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +5 -1
  7. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +1 -0
  8. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.html +10 -12
  9. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +33 -11
  10. package/src/libs/feature/editor/src/index.ts +8 -7
  11. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -1
  12. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  13. package/src/libs/feature/editor/src/lib/+state/editor.reducer.ts +4 -0
  14. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +2 -2
  15. package/src/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts +2 -0
  16. package/src/libs/feature/record/src/lib/gpf-api-dl/gpf-api-dl.component.html +4 -4
  17. package/src/libs/feature/record/src/lib/gpf-api-dl/gpf-api-dl.component.ts +3 -4
  18. package/src/libs/feature/record/src/lib/map-view/map-view.component.html +10 -18
  19. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +96 -9
  20. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
  21. package/src/libs/feature/router/src/lib/default/router.module.ts +3 -3
  22. package/src/libs/feature/router/src/lib/default/router.service.ts +2 -2
  23. package/src/libs/feature/router/src/lib/default/services/router-search.service.ts +12 -3
  24. package/src/libs/feature/router/src/lib/default/state/router.effects.ts +34 -4
  25. package/src/libs/feature/search/src/lib/state/reducer.ts +3 -0
  26. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -3
  27. package/src/libs/feature/search/src/lib/utils/service/fields.ts +5 -5
  28. package/src/libs/ui/elements/src/lib/api-card/api-card.component.html +2 -2
  29. package/src/libs/ui/elements/src/lib/application-banner/application-banner.component.ts +2 -3
  30. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts +5 -3
  31. package/src/libs/ui/elements/src/lib/image-input/image-input.component.html +5 -2
  32. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +63 -56
  33. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.scss +5 -5
  34. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html +11 -8
  35. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +3 -3
  36. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +1 -1
  37. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.html +1 -1
  38. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +7 -5
  39. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.ts +1 -1
  40. package/src/libs/ui/map/src/lib/map-utils.ts +1 -1
  41. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.html +19 -16
  42. package/src/libs/util/app-config/src/lib/app-config.ts +10 -2
  43. package/src/libs/util/app-config/src/lib/fixtures.ts +1 -0
  44. package/src/libs/util/app-config/src/lib/model.ts +2 -0
  45. package/src/libs/util/shared/src/lib/gn-ui-version.ts +5 -4
  46. package/src/libs/util/shared/src/lib/links/link-utils.ts +8 -4
  47. package/src/libs/util/shared/src/lib/services/theme.service.ts +9 -23
  48. package/tailwind.base.config.js +3 -2
  49. package/translations/de.json +6 -3
  50. package/translations/en.json +8 -5
  51. package/translations/es.json +3 -0
  52. package/translations/fr.json +8 -5
  53. package/translations/it.json +8 -5
  54. package/translations/nl.json +3 -0
  55. package/translations/pt.json +3 -0
  56. package/translations/sk.json +4 -1
@@ -2,8 +2,8 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  ChangeDetectorRef,
4
4
  Component,
5
- Input,
6
5
  inject,
6
+ Input,
7
7
  } from '@angular/core'
8
8
  import { TranslateDirective, TranslateService } from '@ngx-translate/core'
9
9
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
@@ -67,13 +67,15 @@ export class DownloadsListComponent {
67
67
  ): DatasetDownloadDistribution[] {
68
68
  const preferredLinks = new Map<string, DatasetDownloadDistribution>()
69
69
 
70
- links.forEach((link) => {
70
+ links.forEach((link, index) => {
71
71
  const format = getFileFormat(link)
72
72
  const withoutNameSpace = (link.name || link.description || '').replace(
73
73
  /^.*?:/,
74
74
  ''
75
75
  )
76
- const uniqueKey = `${format}-${withoutNameSpace}`
76
+ const uniqueKey = link.accessServiceProtocol
77
+ ? `${format}-${withoutNameSpace}`
78
+ : index.toString() // direct download links should not be deduplicated
77
79
  if (!preferredLinks.has(uniqueKey)) {
78
80
  preferredLinks.set(uniqueKey, link)
79
81
  } else {
@@ -1,6 +1,9 @@
1
1
  @if (previewUrl) {
2
- <div class="w-80 h-full flex flex-col gap-2">
3
- <gn-ui-image-overlay-preview class="h-48" [imageUrl]="previewUrl">
2
+ <div class="w-[314px] h-full flex flex-col gap-2">
3
+ <gn-ui-image-overlay-preview
4
+ class="w-[314px] h-[314px]"
5
+ [imageUrl]="previewUrl"
6
+ >
4
7
  </gn-ui-image-overlay-preview>
5
8
  @if (showAltTextInput) {
6
9
  <gn-ui-text-input
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  }
16
16
  <div class="grow pt-1" [ngClass]="shouldShowThumbnail ? 'sm:w-0' : ''">
17
- <div class="flex flex-col gap-2 h-full">
17
+ <div class="flex flex-col gap-2" [class.h-full]="size !== 'M'">
18
18
  <h4
19
19
  class="record-card__title"
20
20
  data-cy="recordTitle"
@@ -32,63 +32,70 @@
32
32
  [title]="abstract"
33
33
  ></gn-ui-markdown-parser>
34
34
  </div>
35
- <div class="record-card__footer">
36
- @if (record.ownerOrganization?.name) {
37
- <div
38
- data-cy="recordOrg"
39
- class="grow flex flex-row gap-1 items-center text-primary-lighter"
40
- [ngClass]="displayContactIconOnly ? 'justify-center' : ''"
35
+ @if (size !== 'M') {
36
+ <ng-container [ngTemplateOutlet]="footerTpl"></ng-container>
37
+ }
38
+ </div>
39
+ </div>
40
+ @if (size === 'M') {
41
+ <ng-container [ngTemplateOutlet]="footerTpl"></ng-container>
42
+ }
43
+ </a>
44
+
45
+ <ng-template #footerTpl>
46
+ <div class="record-card__footer">
47
+ @if (record.ownerOrganization?.name) {
48
+ <div
49
+ data-cy="recordOrg"
50
+ class="grow flex flex-row gap-1 items-center text-primary-lighter"
51
+ [ngClass]="displayContactIconOnly ? 'justify-center' : ''"
52
+ >
53
+ <ng-icon
54
+ name="iconoirBank"
55
+ class="text-primary -translate-y-[0.5px] shrink-0"
56
+ [title]="record.ownerOrganization.name"
57
+ ></ng-icon>
58
+ @if (!displayContactIconOnly) {
59
+ <span
60
+ data-cy="recordOrgName"
61
+ class="line-clamp-1"
62
+ [title]="record.ownerOrganization.name"
63
+ >{{ record.ownerOrganization.name }}</span
41
64
  >
42
- <ng-icon
43
- name="iconoirBank"
44
- class="text-primary -translate-y-[0.5px] shrink-0"
45
- [title]="record.ownerOrganization.name"
46
- ></ng-icon>
47
- @if (!displayContactIconOnly) {
48
- <span
49
- data-cy="recordOrgName"
50
- class="line-clamp-1"
51
- [title]="record.ownerOrganization.name"
52
- >{{ record.ownerOrganization.name }}</span
53
- >
54
- }
55
- </div>
56
65
  }
57
- <div class="record-card__footer__other">
58
- <div
59
- class="xs:border-r last:border-r-0 flex grow gap-4 px-4 last:pr-0"
60
- >
61
- <gn-ui-kind-badge
62
- [extraClass]="'text-[1.2em]'"
63
- [styling]="'gray'"
64
- [kind]="record?.kind"
65
- [contentTemplate]="customTemplate"
66
- class="pt-1"
67
- >
68
- <ng-template #customTemplate></ng-template
69
- ></gn-ui-kind-badge>
70
- @if (metadataQualityDisplay) {
71
- <gn-ui-metadata-quality
72
- class="flex items-center min-w-[113px]"
73
- [smaller]="true"
74
- [metadata]="record"
75
- [metadataQualityDisplay]="metadataQualityDisplay"
76
- [popoverDisplay]="true"
77
- ></gn-ui-metadata-quality>
78
- }
79
- </div>
80
- <div
81
- class="flex justify-center"
82
- data-cy="recordFav"
83
- [ngClass]="displayContactIconOnly ? 'px-1' : 'px-4'"
84
- >
85
- <ng-container
86
- [ngTemplateOutlet]="favoriteTemplate"
87
- [ngTemplateOutletContext]="{ $implicit: record }"
88
- ></ng-container>
89
- </div>
90
- </div>
66
+ </div>
67
+ }
68
+ <div class="record-card__footer__other">
69
+ <div class="xs:border-r last:border-r-0 flex grow gap-4 px-4 last:pr-0">
70
+ <gn-ui-kind-badge
71
+ [extraClass]="'text-[1.2em]'"
72
+ [styling]="'gray'"
73
+ [kind]="record?.kind"
74
+ [contentTemplate]="customTemplate"
75
+ class="pt-1"
76
+ >
77
+ <ng-template #customTemplate></ng-template
78
+ ></gn-ui-kind-badge>
79
+ @if (metadataQualityDisplay) {
80
+ <gn-ui-metadata-quality
81
+ class="flex items-center min-w-[113px]"
82
+ [smaller]="true"
83
+ [metadata]="record"
84
+ [metadataQualityDisplay]="metadataQualityDisplay"
85
+ [popoverDisplay]="true"
86
+ ></gn-ui-metadata-quality>
87
+ }
88
+ </div>
89
+ <div
90
+ class="flex justify-center"
91
+ data-cy="recordFav"
92
+ [ngClass]="displayContactIconOnly ? 'px-1' : 'px-4'"
93
+ >
94
+ <ng-container
95
+ [ngTemplateOutlet]="favoriteTemplate"
96
+ [ngTemplateOutletContext]="{ $implicit: record }"
97
+ ></ng-container>
91
98
  </div>
92
99
  </div>
93
100
  </div>
94
- </a>
101
+ </ng-template>
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  &.size-M {
9
- @apply max-w-[940px] md:h-[231px] gap-4;
9
+ @apply max-w-[940px] gap-4 flex-wrap;
10
10
  }
11
11
 
12
12
  &.size-S {
@@ -28,11 +28,11 @@
28
28
  @apply border rounded-lg overflow-hidden shrink-0;
29
29
 
30
30
  .size-L & {
31
- @apply w-full w-[190px] h-[184px];
31
+ @apply w-[184px] h-[184px];
32
32
  }
33
33
 
34
34
  .size-M & {
35
- @apply w-full w-[138px] h-[207px];
35
+ @apply w-[138px] h-[138px];
36
36
  }
37
37
  }
38
38
 
@@ -44,7 +44,7 @@
44
44
  }
45
45
 
46
46
  .size-M & {
47
- @apply line-clamp-2 ml-2;
47
+ @apply line-clamp-2;
48
48
  }
49
49
 
50
50
  .size-S & {
@@ -64,7 +64,7 @@
64
64
  }
65
65
 
66
66
  .size-M & {
67
- @apply line-clamp-3 ml-2;
67
+ @apply line-clamp-3;
68
68
  }
69
69
 
70
70
  .size-S & {
@@ -29,16 +29,19 @@
29
29
  </div>
30
30
  @if (shownOrganization?.website) {
31
31
  <div>
32
- <a
33
- [href]="shownOrganization.website"
34
- target="_blank"
35
- class="contact-website text-sm cursor-pointer hover:underline transition-all"
36
- >{{ shownOrganization.website }}
32
+ <div class="flex items-center mt-1">
37
33
  <ng-icon
38
- class="!w-[12px] !h-[12px] opacity-75 shrink-0"
34
+ class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
39
35
  name="matOpenInNew"
40
36
  ></ng-icon>
41
- </a>
37
+ <a
38
+ [href]="shownOrganization.website"
39
+ target="_blank"
40
+ class="contact-website text-sm cursor-pointer hover:underline transition-all ml-2"
41
+ >
42
+ {{ shownOrganization.website }}
43
+ </a>
44
+ </div>
42
45
  </div>
43
46
  }
44
47
  </div>
@@ -82,7 +85,7 @@
82
85
  class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
83
86
  name="matPersonOutline"
84
87
  ></ng-icon>
85
- <div class="flex flex-col ml-2">
88
+ <div class="flex flex-col ml-2" data-cy="contact-full-name">
86
89
  <p class="text-sm">
87
90
  {{ contacts[0]?.firstName || '' }}
88
91
  {{ contacts[0]?.lastName || '' }}
@@ -52,9 +52,9 @@ export class MetadataContactComponent {
52
52
 
53
53
  get contacts() {
54
54
  return (
55
- (this.metadata.kind === 'dataset'
56
- ? this.metadata.contactsForResource
57
- : this.metadata.contacts) || []
55
+ (this.metadata.kind === 'service'
56
+ ? this.metadata.contacts
57
+ : this.metadata.contactsForResource) || []
58
58
  )
59
59
  }
60
60
 
@@ -6,7 +6,7 @@
6
6
  </div>
7
7
  <button
8
8
  (click)="resetUrl()"
9
- class="bg-primary-opacity-50 inline-flex items-center justify-center px-2 py-1 text-13 font-medium leading-none text-white rounded capitalize text-primary-lightest hover:bg-primary transition-colors"
9
+ class="bg-primary/50 inline-flex items-center justify-center px-2 py-1 text-13 font-medium leading-none text-white rounded capitalize text-primary-lightest hover:bg-primary transition-colors"
10
10
  >
11
11
  <p class="text-[13px] uppercase" translate>
12
12
  record.metadata.api.form.reset
@@ -1,6 +1,6 @@
1
1
  @if (apiLinks.length > 0) {
2
2
  <div style="height: 652px" id="preview">
3
- <div class="bg-primary-opacity-10 overflow-visible" style="height: 420px">
3
+ <div class="bg-primary/10 overflow-visible" style="height: 420px">
4
4
  <div class="container-lg px-4 lg:mx-auto">
5
5
  <div>
6
6
  <div class="gn-ui-section-title mb-6" translate>
@@ -29,6 +29,7 @@ import {
29
29
  filter,
30
30
  finalize,
31
31
  map,
32
+ shareReplay,
32
33
  switchMap,
33
34
  take,
34
35
  tap,
@@ -139,9 +140,8 @@ export class AutocompleteComponent
139
140
  ngOnChanges(changes: SimpleChanges): void {
140
141
  const { value } = changes
141
142
  if (value) {
142
- const previousTextValue = this.displayWithFnInternal(value.previousValue)
143
143
  const currentTextValue = this.displayWithFnInternal(value.currentValue)
144
- if (previousTextValue !== currentTextValue) {
144
+ if (currentTextValue !== this.control.value) {
145
145
  if (currentTextValue) {
146
146
  this.searchActive = true
147
147
  this.isSearchActive.emit(true)
@@ -160,8 +160,8 @@ export class AutocompleteComponent
160
160
  this.inputCleared.pipe(map(() => '')),
161
161
  this.control.valueChanges.pipe(
162
162
  filter((value) => typeof value === 'string'),
163
- distinctUntilChanged(),
164
- debounceTime(400)
163
+ debounceTime(400),
164
+ distinctUntilChanged()
165
165
  )
166
166
  )
167
167
 
@@ -181,7 +181,8 @@ export class AutocompleteComponent
181
181
  this.searching = true
182
182
  this.error = null
183
183
  }),
184
- switchMap((value) => this.action(value)),
184
+ switchMap((value) => this.action(value)), // this can trigger http requests
185
+ shareReplay(1), // share the loaded suggestions to avoid multiple requests
185
186
  tap((suggestions) => {
186
187
  // forcing the panel to open if there are suggestions
187
188
  if (suggestions.length > 0 && !this.searchActive) {
@@ -288,6 +289,7 @@ export class AutocompleteComponent
288
289
 
289
290
  clear(): void {
290
291
  this.inputRef.nativeElement.value = ''
292
+ this.control.setValue('')
291
293
  this.searchActive = false
292
294
  this.isSearchActive.emit(false)
293
295
  this.inputCleared.emit()
@@ -102,7 +102,7 @@ export class DropdownSelectorComponent implements OnInit {
102
102
  }
103
103
 
104
104
  getChoiceLabel(): string {
105
- return this.selectedChoice?.label
105
+ return this.selectedChoice?.label ?? '\u00A0'
106
106
  }
107
107
 
108
108
  ngOnInit(): void {
@@ -50,7 +50,7 @@ export function dragPanCondition(
50
50
 
51
51
  export function mouseWheelZoomCondition(
52
52
  this: MouseWheelZoom,
53
- event: MapBrowserEvent<UIEvent>
53
+ event: MapBrowserEvent<WheelEvent>
54
54
  ) {
55
55
  if (!platformModifierKeyOnly(event) && event.type === 'wheel') {
56
56
  this.getMap().dispatchEvent('mapmuted')
@@ -30,7 +30,7 @@
30
30
  ></gn-ui-thumbnail>
31
31
  }
32
32
  </div>
33
- <div class="flex flex-col overflow-hidden items-start">
33
+ <div class="flex flex-col overflow-hidden items-start grow">
34
34
  @if (hasOrganization) {
35
35
  <span
36
36
  class="font-bold transition duration-200 text-primary truncate max-w-full"
@@ -51,32 +51,35 @@
51
51
  >
52
52
  </p>
53
53
  </div>
54
- </div>
55
- <div class="pt-5 pb-5 px-10 relative">
56
- <div class="absolute top-[0.85em] right-[0.85em]">
54
+ <div class="ml-3 shrink-0">
57
55
  <ng-container
58
56
  [ngTemplateOutlet]="favoriteTemplate"
59
57
  [ngTemplateOutletContext]="{ $implicit: record }"
60
58
  ></ng-container>
61
59
  </div>
62
- <h1
63
- class="font-title text-black text-[21px] font-medium mb-3 pr-8"
64
- data-cy="recordTitle"
65
- >
66
- {{ record.title }}
67
- </h1>
60
+ </div>
61
+ <div class="pt-5 pb-5 px-10 relative">
62
+ <div class="flex flex-col sm:flex-row gap-4 mb-3">
63
+ @if (record.overviews?.[0]) {
64
+ <gn-ui-thumbnail
65
+ class="block shrink-0 w-[120px] h-[120px] border border-gray-100 rounded-lg overflow-hidden"
66
+ [thumbnailUrl]="record.overviews?.[0]?.url.toString()"
67
+ [fit]="'cover'"
68
+ ></gn-ui-thumbnail>
69
+ }
70
+ <h1
71
+ class="font-title text-black text-[21px] font-medium"
72
+ data-cy="recordTitle"
73
+ >
74
+ {{ record.title }}
75
+ </h1>
76
+ </div>
68
77
  <p class="line-clamp-3">
69
78
  <gn-ui-markdown-parser
70
79
  [textContent]="abstract"
71
80
  [whitoutStyles]="true"
72
81
  />
73
82
  </p>
74
- @if (record.overviews?.[0]) {
75
- <gn-ui-thumbnail
76
- class="block mt-3 w-full h-[136px] border border-gray-100 rounded-lg overflow-hidden"
77
- [thumbnailUrl]="record.overviews?.[0]?.url.toString()"
78
- ></gn-ui-thumbnail>
79
- }
80
83
  @if (isDownloadable || isViewable) {
81
84
  <div class="flex flex-row mt-3">
82
85
  @if (isDownloadable) {
@@ -70,8 +70,11 @@ export function getCustomTranslations(langCode: string): CustomTranslations {
70
70
 
71
71
  let appConfigLoaded = false
72
72
 
73
- export function loadAppConfig() {
74
- return fetch('assets/configuration/default.toml')
73
+ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
74
+ console.log(
75
+ `[geonetwork-ui] Loading application configuration from ${configUrl}`
76
+ )
77
+ return fetch(configUrl)
75
78
  .then((resp) => {
76
79
  if (!resp.ok) throw new Error('Configuration file could not be loaded')
77
80
  return resp.text()
@@ -94,6 +97,7 @@ export function loadAppConfig() {
94
97
  ['geonetwork4_api_url'],
95
98
  [
96
99
  'datahub_url',
100
+ 'edit_url_template',
97
101
  'proxy_path',
98
102
  'metadata_language',
99
103
  'login_url',
@@ -119,6 +123,7 @@ export function loadAppConfig() {
119
123
  : ({
120
124
  GN4_API_URL: parsedGlobalSection.geonetwork4_api_url,
121
125
  DATAHUB_URL: parsedGlobalSection.datahub_url,
126
+ EDIT_URL_TEMPLATE: parsedGlobalSection.edit_url_template,
122
127
  PROXY_PATH: parsedGlobalSection.proxy_path,
123
128
  METADATA_LANGUAGE: parsedGlobalSection.metadata_language
124
129
  ? (
@@ -229,6 +234,7 @@ export function loadAppConfig() {
229
234
  'record_kind_quick_filter',
230
235
  'filter_geometry_data',
231
236
  'filter_geometry_url',
237
+ 'do_not_use_default_search_preset',
232
238
  'search_preset',
233
239
  'advanced_filters',
234
240
  'limit',
@@ -252,6 +258,8 @@ export function loadAppConfig() {
252
258
  parsedSearchSection.record_kind_quick_filter,
253
259
  FILTER_GEOMETRY_DATA: parsedSearchSection.filter_geometry_data,
254
260
  FILTER_GEOMETRY_URL: parsedSearchSection.filter_geometry_url,
261
+ DO_NOT_USE_DEFAULT_SEARCH_PRESET:
262
+ !!parsedSearchSection.do_not_use_default_search_preset,
255
263
  SEARCH_PRESET: parsedSearchParams.map((param) => ({
256
264
  sort: param.sort,
257
265
  name: param.name,
@@ -40,6 +40,7 @@ fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans"
40
40
  [search]
41
41
  record_kind_quick_filter = false
42
42
  filter_geometry_url = 'https://my.domain.org/geom.json'
43
+ do_not_use_default_search_preset = false
43
44
  advanced_filters = ['publicationYear', 'documentStandard', 'inspireKeyword', 'topic', 'license']
44
45
 
45
46
  [[search_preset]]
@@ -3,6 +3,7 @@ import { Geometry } from 'geojson'
3
3
  export interface GlobalConfig {
4
4
  GN4_API_URL: string
5
5
  DATAHUB_URL?: string
6
+ EDIT_URL_TEMPLATE?: string
6
7
  PROXY_PATH?: string
7
8
  METADATA_LANGUAGE?: string
8
9
  LOGIN_URL?: string
@@ -58,6 +59,7 @@ export interface SearchConfig {
58
59
  RECORD_KIND_QUICK_FILTER?: boolean
59
60
  FILTER_GEOMETRY_URL?: string
60
61
  FILTER_GEOMETRY_DATA?: string
62
+ DO_NOT_USE_DEFAULT_SEARCH_PRESET?: boolean
61
63
  SEARCH_PRESET?: SearchPreset[]
62
64
  ADVANCED_FILTERS?: []
63
65
  LIMIT?: number
@@ -2,7 +2,8 @@ import packageJson from '../../../../../package.json'
2
2
 
3
3
  export const GEONETWORK_UI_VERSION = packageJson.version
4
4
 
5
- export const GEONETWORK_UI_TAG_NAME =
6
- GEONETWORK_UI_VERSION.split('-')[1] === 'dev'
7
- ? 'main'
8
- : `v${packageJson.version}`
5
+ export const GEONETWORK_UI_TAG_NAME = GEONETWORK_UI_VERSION.split(
6
+ '-'
7
+ )[1]?.startsWith('dev')
8
+ ? 'main'
9
+ : `v${packageJson.version}`
@@ -331,11 +331,15 @@ export async function getLayers(url: string, serviceProtocol: ServiceProtocol) {
331
331
  case 'wfs': {
332
332
  const endpointWfs = await new WfsEndpoint(url).isReady()
333
333
  const featureTypes = await endpointWfs.getFeatureTypes()
334
- const layers = await Promise.all(
335
- featureTypes.map(async (collection) => {
336
- return await endpointWfs.getFeatureTypeFull(collection.name)
337
- })
334
+ const layers = (
335
+ await Promise.allSettled(
336
+ featureTypes.map((collection) => {
337
+ return endpointWfs.getFeatureTypeFull(collection.name)
338
+ })
339
+ )
338
340
  )
341
+ .filter((settled) => settled.status === 'fulfilled')
342
+ .map((fulfilled) => fulfilled.value)
339
343
  return layers
340
344
  }
341
345
  case 'wms': {
@@ -10,26 +10,6 @@ export class ThemeService {
10
10
  return document.documentElement.style.getPropertyValue(`--color-${name}`)
11
11
  }
12
12
 
13
- static generateBgOpacityClasses(
14
- colorName,
15
- colorValue,
16
- opacities = [0, 10, 25, 50, 75]
17
- ) {
18
- const color = chroma(colorValue)
19
- const styleElement = document.createElement('style')
20
- styleElement.innerHTML = opacities.reduce((cssRules, opacity) => {
21
- cssRules += `.bg-${colorName}-opacity-${opacity}{background-color:${color
22
- .alpha(opacity / 100)
23
- .css()};}`
24
-
25
- cssRules += `.hover-bg-${colorName}-opacity-${opacity}:hover {background-color:${color
26
- .alpha(opacity / 100)
27
- .css()};}`
28
- return cssRules
29
- }, '')
30
- document.getElementsByTagName('head')[0].appendChild(styleElement)
31
- }
32
-
33
13
  static applyCssVariables(
34
14
  primaryColor: string,
35
15
  secondaryColor: string,
@@ -39,13 +19,19 @@ export class ThemeService {
39
19
  titleFont?: string,
40
20
  fontsStylesheetUrl?: string
41
21
  ) {
42
- const applyColor = (name: string, color) => {
22
+ const applyColor = (name: string, color, includeRawValues?: boolean) => {
43
23
  document.documentElement.style.setProperty(`--color-${name}`, color.css())
24
+ if (includeRawValues) {
25
+ document.documentElement.style.setProperty(
26
+ `--color-raw-${name}`,
27
+ color.css().replace(/^rgba?\((.*)\)/, '$1')
28
+ )
29
+ }
44
30
  }
45
31
 
46
32
  const black = chroma('black')
47
33
  const white = chroma('white')
48
- applyColor('primary', chroma(primaryColor))
34
+ applyColor('primary', chroma(primaryColor), true)
49
35
  applyColor(
50
36
  'primary-lighter',
51
37
  chroma.scale([primaryColor, white]).mode('lab')(0.3)
@@ -70,7 +56,7 @@ export class ThemeService {
70
56
  'primary-black',
71
57
  chroma.scale([primaryColor, black]).mode('lab')(0.85)
72
58
  )
73
- applyColor('secondary', chroma(secondaryColor))
59
+ applyColor('secondary', chroma(secondaryColor), true)
74
60
  applyColor(
75
61
  'secondary-lighter',
76
62
  chroma.scale([secondaryColor, white]).mode('lab')(0.3)
@@ -1,3 +1,4 @@
1
+ /** @type {import('tailwindcss').Config} */
1
2
  module.exports = {
2
3
  theme: {
3
4
  extend: {
@@ -8,14 +9,14 @@ module.exports = {
8
9
  'primary-white': 'var(--color-primary-white)',
9
10
  'primary-lightest': 'var(--color-primary-lightest)',
10
11
  'primary-lighter': 'var(--color-primary-lighter)',
11
- primary: 'var(--color-primary)',
12
+ primary: 'rgb(var(--color-raw-primary) / <alpha-value>)', // supports opacity
12
13
  'primary-darker': 'var(--color-primary-darker)',
13
14
  'primary-darkest': 'var(--color-primary-darkest)',
14
15
  'primary-black': 'var(--color-primary-black)',
15
16
  'secondary-white': 'var(--color-secondary-white)',
16
17
  'secondary-lightest': 'var(--color-secondary-lightest)',
17
18
  'secondary-lighter': 'var(--color-secondary-lighter)',
18
- secondary: 'var(--color-secondary)',
19
+ secondary: 'rgb(var(--color-raw-secondary) / <alpha-value>)', // supports opacity
19
20
  'secondary-darker': 'var(--color-secondary-darker)',
20
21
  'secondary-darkest': 'var(--color-secondary-darkest)',
21
22
  'secondary-black': 'var(--color-secondary-black)',
@@ -38,6 +38,7 @@
38
38
  "dashboard.records.username": "Benutzername",
39
39
  "dashboard.records.users": "{count, plural, =1{Benutzer} other{Benutzer}}",
40
40
  "datahub.header.datasets": "Katalog",
41
+ "datahub.header.edit.url.open": "",
41
42
  "datahub.header.lastRecords": "Die neuesten",
42
43
  "datahub.header.myfavorites": "Meine Favoriten",
43
44
  "datahub.header.news": "Startseite",
@@ -382,11 +383,13 @@
382
383
  "map.ogc.urlInput.hint": "Die URL des OGC API-Dienstes eingeben",
383
384
  "map.select.layer": "Datenquelle",
384
385
  "map.select.style": "Style",
386
+ "map.style.default": "Standard",
385
387
  "map.wfs.urlInput.hint": "Die WFS URL eingeben",
386
388
  "map.wms.urlInput.hint": "Die WMS URL eingeben",
387
389
  "multiselect.filter.placeholder": "Suche",
388
390
  "nav.back": "Zurück",
389
391
  "navbar.mobile.menuTitle": "Schnellzugriff",
392
+ "ogc.geojson.notsupported": "Dieser OGC API-Dienst unterstützt das GeoJSON-Format nicht.",
390
393
  "ogc.unreachable.unknown": "Der Dienst konnte nicht erreicht werden",
391
394
  "organisation.filter.placeholder": "Ergebnisse filtern",
392
395
  "organisation.sort.sortBy": "Sortieren nach:",
@@ -472,7 +475,7 @@
472
475
  "record.metadata.download": "Herunterladen",
473
476
  "record.metadata.feature.catalog": "Attributkatalog",
474
477
  "record.metadata.formats": "Formate",
475
- "record.metadata.isGeographical": "Geographischer Datensatz",
478
+ "record.metadata.isGeographical": "geographischer Datensatz",
476
479
  "record.metadata.keywords": "Stichworte",
477
480
  "record.metadata.languages": "Sprachen",
478
481
  "record.metadata.link.postgis.table": "Tabelle:",
@@ -603,6 +606,7 @@
603
606
  "search.filters.producerOrg": "Herausgeber",
604
607
  "search.filters.publicationYear": "Veröffentlichungsjahr",
605
608
  "search.filters.publisherOrg": "Vertreiber",
609
+ "search.filters.recordKind": "",
606
610
  "search.filters.recordKind.all": "Alle",
607
611
  "search.filters.recordKind.dataset": "Datensätze",
608
612
  "search.filters.recordKind.reuse": "Wiederverwendungen",
@@ -641,7 +645,6 @@
641
645
  "service.metadata.spatialExtent": "Räumliche Ausdehnung",
642
646
  "share.tab.permalink": "Teilen",
643
647
  "share.tab.webComponent": "Integrieren",
644
- "stac.filter.reset": "Filter zurücksetzen",
645
648
  "stac.filter.enable": "",
646
649
  "stac.filter.reset": "",
647
650
  "stac.results.noResults": "Ihre Suchfilter lieferten keine Ergebnisse",
@@ -662,7 +665,7 @@
662
665
  "wfs.feature.limit": "Zu viele Features, um den WFS-Layer anzuzeigen!",
663
666
  "wfs.featuretype.notfound": "Kein passender Feature-Typ wurde im Dienst gefunden",
664
667
  "wfs.geojsongml.notsupported": "Dieser Dienst unterstützt das GeoJSON- oder GML-Format nicht",
665
- "wfs.unreachable.cors": "Der Dienst konnte aufgrund von CORS-Beschränkungen nicht erreicht werden",
668
+ "wfs.unreachable.cors": "Der Remote-Dienst ist nicht für den Datenempfang (CORS) konfiguriert",
666
669
  "wfs.unreachable.http": "Der Dienst hat einen HTTP-Fehler zurückgegeben",
667
670
  "wfs.unreachable.unknown": "Der Dienst konnte nicht erreicht werden"
668
671
  }