geonetwork-ui 2.9.0-dev.6da5cd143 → 2.9.0-dev.af124b7cd

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 (21) hide show
  1. package/fesm2022/geonetwork-ui.mjs +101 -28
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +11 -2
  4. package/index.d.ts.map +1 -1
  5. package/package.json +6 -6
  6. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.html +8 -10
  7. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +13 -3
  8. package/src/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts +2 -0
  9. package/src/libs/feature/record/src/lib/map-view/map-view.component.html +1 -0
  10. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +35 -4
  11. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
  12. package/src/libs/feature/router/src/lib/default/services/router-search.service.ts +15 -3
  13. package/src/libs/feature/router/src/lib/default/state/router.effects.ts +40 -4
  14. package/src/libs/feature/router/src/lib/default/state/router.facade.ts +2 -0
  15. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.ts +1 -0
  16. package/src/libs/feature/search/src/lib/state/reducer.ts +4 -0
  17. package/src/libs/feature/search/src/lib/utils/service/search.service.ts +2 -0
  18. package/src/libs/ui/elements/src/lib/image-input/image-input.component.html +5 -2
  19. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +63 -56
  20. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.scss +5 -5
  21. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.html +19 -16
@@ -231,7 +231,8 @@ export class MapViewComponent implements AfterViewInit {
231
231
  return compatibleLinks[0]
232
232
  }
233
233
  }
234
- })
234
+ }),
235
+ shareReplay(1)
235
236
  )
236
237
 
237
238
  isWmsStyleMode$ = this.selectedSourceLink$.pipe(
@@ -346,12 +347,38 @@ export class MapViewComponent implements AfterViewInit {
346
347
  shareReplay(1)
347
348
  )
348
349
 
350
+ wmsMimeType$ = this.selectedSourceLink$.pipe(
351
+ switchMap((link) => {
352
+ if (link?.type === 'service' && link?.accessServiceProtocol === 'wms') {
353
+ return from(
354
+ new WmsEndpoint(link.url.toString())
355
+ .isReady()
356
+ .then((endpoint) => {
357
+ return endpoint.describeLayer(link.name).then((description) => {
358
+ if (description) {
359
+ return description.owsType === 'wfs'
360
+ ? 'image/png'
361
+ : 'image/jpeg'
362
+ }
363
+ const layer = endpoint.getLayerByName(link.name)
364
+ return layer?.opaque ? 'image/jpeg' : 'image/png'
365
+ })
366
+ })
367
+ .catch(() => 'image/png')
368
+ )
369
+ }
370
+ return of('')
371
+ }),
372
+ shareReplay(1)
373
+ )
374
+
349
375
  currentLayers$ = combineLatest([
350
376
  this.selectedLink$,
351
377
  this.excludeWfs$,
352
378
  this.selectedWmsStyleName$,
379
+ this.wmsMimeType$,
353
380
  ]).pipe(
354
- switchMap(([link, excludeWfs, wmsStyleName]) => {
381
+ switchMap(([link, excludeWfs, wmsStyleName, wmsMimeType]) => {
355
382
  if (!link) {
356
383
  return of([])
357
384
  }
@@ -367,8 +394,12 @@ export class MapViewComponent implements AfterViewInit {
367
394
  }
368
395
  return this.getLayerFromLink(link).pipe(
369
396
  map((layer) =>
370
- wmsStyleName && layer.type === 'wms'
371
- ? { ...layer, style: wmsStyleName }
397
+ layer.type === 'wms'
398
+ ? {
399
+ ...layer,
400
+ ...(wmsStyleName && { style: wmsStyleName }),
401
+ ...(wmsMimeType && { format: wmsMimeType }),
402
+ }
372
403
  : layer
373
404
  ),
374
405
  map((layer) => [layer]),
@@ -164,7 +164,7 @@ export class MdViewFacade {
164
164
  link.accessServiceProtocol === 'ogcFeatures'
165
165
  ) {
166
166
  return from(
167
- this.dataService.getItemsFromOgcApi(link.url.href)
167
+ this.dataService.getItemsFromOgcApi(link.url.href, 1)
168
168
  ).pipe(
169
169
  map((collectionRecords: OgcApiRecord[]) => {
170
170
  return collectionRecords &&
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import {
3
3
  FieldsService,
4
4
  SearchFacade,
@@ -6,6 +6,7 @@ import {
6
6
  } from '../../../../../../../libs/feature/search/src'
7
7
  import {
8
8
  FieldFilters,
9
+ SortByEnum,
9
10
  SortByField,
10
11
  } from '../../../../../../../libs/common/domain/src/lib/model/search'
11
12
  import { ROUTE_PARAMS, SearchRouteParams } from '../constants'
@@ -20,6 +21,7 @@ export class RouterSearchService implements SearchServiceI {
20
21
  private fieldsService = inject(FieldsService)
21
22
 
22
23
  setSortAndFilters(filters: FieldFilters, sortBy: SortByField) {
24
+ console.log('RouterSearchService - setSortAndFilters', { filters, sortBy })
23
25
  this.fieldsService
24
26
  .readFieldValuesFromFilters(filters)
25
27
  .subscribe((values) => {
@@ -31,10 +33,12 @@ export class RouterSearchService implements SearchServiceI {
31
33
  }
32
34
 
33
35
  async setFilters(newFilters: FieldFilters) {
34
- const sortBy = await firstValueFrom(this.searchFacade.sortBy$)
36
+ let sortBy = await firstValueFrom(this.searchFacade.sortBy$)
35
37
  const fieldSearchParams = await firstValueFrom(
36
38
  this.fieldsService.readFieldValuesFromFilters(newFilters)
37
39
  )
40
+ // apply relevancy sort if a full text criteria is given
41
+ sortBy = newFilters['any'] ? SortByEnum.RELEVANCY : sortBy
38
42
  this.facade.setSearch({
39
43
  ...fieldSearchParams,
40
44
  [ROUTE_PARAMS.SORT]: sortBy ? sortByToString(sortBy) : undefined,
@@ -42,13 +46,21 @@ export class RouterSearchService implements SearchServiceI {
42
46
  }
43
47
 
44
48
  async updateFilters(newFilters: FieldFilters) {
49
+ console.log('RouterSearchService - updateFilters', newFilters)
45
50
  const currentFilters = await firstValueFrom(
46
51
  this.searchFacade.searchFilters$
47
52
  )
48
53
  const updatedFilters = { ...currentFilters, ...newFilters }
49
- const newParams = await firstValueFrom(
54
+ let newParams = await firstValueFrom(
50
55
  this.fieldsService.readFieldValuesFromFilters(updatedFilters)
51
56
  )
57
+ if (newFilters['any']) {
58
+ newParams = {
59
+ ...newParams,
60
+ [ROUTE_PARAMS.SORT]: sortByToString(SortByEnum.RELEVANCY),
61
+ }
62
+ }
63
+ console.log('RouterSearchService - updateFilters - newParams', newParams)
52
64
  this.facade.updateSearch(newParams as SearchRouteParams)
53
65
  }
54
66
 
@@ -1,5 +1,5 @@
1
1
  import { Location } from '@angular/common'
2
- import { Injectable, inject } from '@angular/core'
2
+ import { inject, Injectable } from '@angular/core'
3
3
  import { ActivatedRouteSnapshot, Router } from '@angular/router'
4
4
  import { MdViewActions } from '../../../../../../../libs/feature/record/src'
5
5
  import {
@@ -9,15 +9,18 @@ import {
9
9
  SetFilters,
10
10
  SetSortBy,
11
11
  } from '../../../../../../../libs/feature/search/src'
12
- import { FieldFilters } from '../../../../../../../libs/common/domain/src/lib/model/search'
12
+ import {
13
+ FieldFilters,
14
+ SortByEnum,
15
+ } from '../../../../../../../libs/common/domain/src/lib/model/search'
13
16
  import { Actions, createEffect, ofType } from '@ngrx/effects'
14
17
  import { navigation } from '@ngrx/router-store/data-persistence'
15
18
  import { of, pairwise, startWith } from 'rxjs'
16
- import { map, mergeMap, tap } from 'rxjs/operators'
19
+ import { map, mergeMap, take, tap } from 'rxjs/operators'
17
20
  import * as RouterActions from './router.actions'
18
21
  import { RouterFacade } from './router.facade'
19
22
  import { ROUTE_PARAMS } from '../constants'
20
- import { sortByFromString } from '../../../../../../../libs/util/shared/src'
23
+ import { sortByFromString, sortByToString } from '../../../../../../../libs/util/shared/src'
21
24
  import { ROUTER_CONFIG, RouterConfigModel } from '../router.config'
22
25
  import { RouterService } from '../router.service'
23
26
 
@@ -55,6 +58,12 @@ export class RouterEffects {
55
58
  startWith([null, {}] as [Record<string, string>, FieldFilters]),
56
59
  pairwise(),
57
60
  map(([[oldParams, oldFilters], [newParams, newFilters]]) => {
61
+ console.log('RouterEffects - syncSearchState', {
62
+ oldParams,
63
+ newParams,
64
+ oldFilters,
65
+ newFilters,
66
+ })
58
67
  let sortBy =
59
68
  ROUTE_PARAMS.SORT in newParams
60
69
  ? sortByFromString(newParams[ROUTE_PARAMS.SORT])
@@ -103,6 +112,33 @@ export class RouterEffects {
103
112
  )
104
113
  )
105
114
 
115
+ /**
116
+ * This effect is needed because on the page load, the search params from the URL are
117
+ * directly applied to the underlying search facade; this means that it doesn't go
118
+ * through the RouterSearchService which makes sure that a relevancy sort is applied
119
+ * whenever there's a full text search criteria set.
120
+ */
121
+ applyInitialRelevancySort$ = createEffect(
122
+ () =>
123
+ this.facade.searchParams$.pipe(
124
+ take(1),
125
+ tap((filters) => {
126
+ const relevancySort = sortByToString(SortByEnum.RELEVANCY)
127
+ if (
128
+ filters['q'] &&
129
+ (!Array.isArray(filters['q']) || filters['q'].length > 0) &&
130
+ !filters[ROUTE_PARAMS.SORT]
131
+ ) {
132
+ this.facade.updateSearch({
133
+ ...filters,
134
+ [ROUTE_PARAMS.SORT]: relevancySort,
135
+ })
136
+ }
137
+ })
138
+ ),
139
+ { dispatch: false }
140
+ )
141
+
106
142
  /**
107
143
  * This effect will load the metadata when a navigation to
108
144
  * a metadata record happens
@@ -70,6 +70,7 @@ export class RouterFacade {
70
70
  }
71
71
 
72
72
  updateSearch(query?: SearchRouteParams) {
73
+ console.log('RouterFacade - updateSearch', query)
73
74
  this.go({
74
75
  path: this.routerService.getSearchRoute(),
75
76
  ...(query && { query: flattenQueryParams(query) }),
@@ -78,6 +79,7 @@ export class RouterFacade {
78
79
  }
79
80
 
80
81
  setSearch(query?: SearchRouteParams) {
82
+ console.log('RouterFacade - setSearch', query)
81
83
  this.go({
82
84
  path: this.routerService.getSearchRoute(),
83
85
  ...(query && { query: flattenQueryParams(query) }),
@@ -79,6 +79,7 @@ export class FuzzySearchComponent implements OnInit {
79
79
  if (this.inputSubmitted.observers.length > 0) {
80
80
  this.inputSubmitted.emit(any)
81
81
  } else {
82
+ console.log('FuzzySearchComponent - handleInputSubmission', any)
82
83
  this.searchService.updateFilters({ any })
83
84
  }
84
85
  }
@@ -111,7 +111,10 @@ export function reducerSearch(
111
111
  },
112
112
  }
113
113
  }
114
+ // From router.effects
115
+ // From home - fuzzy-search - search.service
114
116
  case fromActions.SET_FILTERS: {
117
+ console.log('reducerSearch - SET_FILTERS', action.payload)
115
118
  return {
116
119
  ...state,
117
120
  params: {
@@ -132,6 +135,7 @@ export function reducerSearch(
132
135
  },
133
136
  }
134
137
  }
138
+ // From results WC
135
139
  case fromActions.SET_SEARCH: {
136
140
  return {
137
141
  ...state,
@@ -19,11 +19,13 @@ export class SearchService implements SearchServiceI {
19
19
  private facade = inject(SearchFacade)
20
20
 
21
21
  setSortAndFilters(filters: FieldFilters, sort: SortByField) {
22
+ console.log('SearchService - setSortAndFilters', { filters, sort })
22
23
  this.setFilters(filters)
23
24
  this.setSortBy(sort)
24
25
  }
25
26
 
26
27
  updateFilters(params: FieldFilters) {
28
+ console.log('SearchService - updateFilters', params)
27
29
  this.facade.searchFilters$
28
30
  .pipe(
29
31
  first(),
@@ -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 & {
@@ -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) {