geonetwork-ui 2.2.0-dev.ca028047 → 2.2.0-dev.cbcafed5

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 (153) hide show
  1. package/esm2022/libs/api/repository/src/lib/gn4/selection/selection.service.mjs +13 -26
  2. package/esm2022/libs/common/domain/src/lib/model/search/aggregation.model.mjs +1 -1
  3. package/esm2022/libs/common/domain/src/lib/model/search/field.model.mjs +2 -0
  4. package/esm2022/libs/common/domain/src/lib/model/search/filter.model.mjs +1 -1
  5. package/esm2022/libs/common/domain/src/lib/model/search/index.mjs +2 -1
  6. package/esm2022/libs/common/domain/src/lib/model/search/search.model.mjs +1 -1
  7. package/esm2022/libs/feature/catalog/src/lib/my-org/my-org.service.mjs +4 -10
  8. package/esm2022/libs/feature/record/src/lib/state/mdview.reducer.mjs +2 -2
  9. package/esm2022/libs/feature/search/src/index.mjs +4 -1
  10. package/esm2022/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.mjs +1 -1
  11. package/esm2022/libs/feature/search/src/lib/feature-search.module.mjs +2 -6
  12. package/esm2022/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.mjs +1 -1
  13. package/esm2022/libs/feature/search/src/lib/filter-geometry.token.mjs +4 -0
  14. package/esm2022/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.mjs +7 -3
  15. package/esm2022/libs/feature/search/src/lib/record-url.token.mjs +4 -0
  16. package/esm2022/libs/feature/search/src/lib/results-list/results-list.container.component.mjs +2 -2
  17. package/esm2022/libs/feature/search/src/lib/results-table/results-table.component.mjs +128 -0
  18. package/esm2022/libs/feature/search/src/lib/state/effects.mjs +6 -4
  19. package/esm2022/libs/feature/search/src/lib/state/search.facade.mjs +3 -2
  20. package/esm2022/libs/ui/elements/src/lib/record-api-form/record-api-form.component.mjs +9 -6
  21. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  22. package/esm2022/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.mjs +3 -3
  23. package/esm2022/libs/ui/inputs/src/lib/checkbox/checkbox.component.mjs +3 -3
  24. package/esm2022/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.mjs +3 -3
  25. package/esm2022/libs/ui/layout/src/index.mjs +3 -1
  26. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.mjs +36 -0
  27. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table.component.mjs +37 -0
  28. package/esm2022/libs/ui/search/src/index.mjs +1 -2
  29. package/esm2022/libs/ui/search/src/lib/ui-search.module.mjs +9 -10
  30. package/esm2022/libs/util/i18n/src/index.mjs +1 -2
  31. package/esm2022/translations/de.json +4 -3
  32. package/esm2022/translations/en.json +5 -4
  33. package/esm2022/translations/es.json +4 -3
  34. package/esm2022/translations/fr.json +5 -4
  35. package/esm2022/translations/it.json +4 -3
  36. package/esm2022/translations/nl.json +4 -3
  37. package/esm2022/translations/pt.json +4 -3
  38. package/fesm2022/geonetwork-ui.mjs +255 -351
  39. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  40. package/libs/api/repository/src/lib/gn4/selection/selection.service.d.ts +4 -5
  41. package/libs/api/repository/src/lib/gn4/selection/selection.service.d.ts.map +1 -1
  42. package/libs/common/domain/src/lib/model/search/aggregation.model.d.ts +1 -1
  43. package/libs/common/domain/src/lib/model/search/aggregation.model.d.ts.map +1 -1
  44. package/libs/common/domain/src/lib/model/search/field.model.d.ts +2 -0
  45. package/libs/common/domain/src/lib/model/search/field.model.d.ts.map +1 -0
  46. package/libs/common/domain/src/lib/model/search/filter.model.d.ts +1 -1
  47. package/libs/common/domain/src/lib/model/search/filter.model.d.ts.map +1 -1
  48. package/libs/common/domain/src/lib/model/search/index.d.ts +1 -0
  49. package/libs/common/domain/src/lib/model/search/index.d.ts.map +1 -1
  50. package/libs/common/domain/src/lib/model/search/search.model.d.ts +2 -3
  51. package/libs/common/domain/src/lib/model/search/search.model.d.ts.map +1 -1
  52. package/libs/feature/catalog/src/lib/my-org/my-org.service.d.ts +0 -1
  53. package/libs/feature/catalog/src/lib/my-org/my-org.service.d.ts.map +1 -1
  54. package/libs/feature/search/src/index.d.ts +3 -0
  55. package/libs/feature/search/src/index.d.ts.map +1 -1
  56. package/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.d.ts +3 -3
  57. package/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.d.ts.map +1 -1
  58. package/libs/feature/search/src/lib/feature-search.module.d.ts +0 -4
  59. package/libs/feature/search/src/lib/feature-search.module.d.ts.map +1 -1
  60. package/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.d.ts +3 -2
  61. package/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.d.ts.map +1 -1
  62. package/libs/feature/search/src/lib/filter-geometry.token.d.ts +4 -0
  63. package/libs/feature/search/src/lib/filter-geometry.token.d.ts.map +1 -0
  64. package/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.d.ts +1 -1
  65. package/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.d.ts.map +1 -1
  66. package/libs/feature/search/src/lib/record-url.token.d.ts +3 -0
  67. package/libs/feature/search/src/lib/record-url.token.d.ts.map +1 -0
  68. package/libs/feature/search/src/lib/results-table/results-table.component.d.ts +33 -0
  69. package/libs/feature/search/src/lib/results-table/results-table.component.d.ts.map +1 -0
  70. package/libs/feature/search/src/lib/state/effects.d.ts.map +1 -1
  71. package/libs/feature/search/src/lib/state/search.facade.d.ts +1 -1
  72. package/libs/feature/search/src/lib/state/search.facade.d.ts.map +1 -1
  73. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +3 -1
  74. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts.map +1 -1
  75. package/libs/ui/inputs/src/index.d.ts +1 -0
  76. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  77. package/libs/ui/layout/src/index.d.ts +2 -0
  78. package/libs/ui/layout/src/index.d.ts.map +1 -1
  79. package/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.d.ts +14 -0
  80. package/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.d.ts.map +1 -0
  81. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts +15 -0
  82. package/libs/ui/layout/src/lib/interactive-table/interactive-table.component.d.ts.map +1 -0
  83. package/libs/ui/search/src/index.d.ts +0 -1
  84. package/libs/ui/search/src/index.d.ts.map +1 -1
  85. package/libs/ui/search/src/lib/ui-search.module.d.ts +15 -15
  86. package/libs/ui/search/src/lib/ui-search.module.d.ts.map +1 -1
  87. package/libs/util/i18n/src/index.d.ts +0 -1
  88. package/libs/util/i18n/src/index.d.ts.map +1 -1
  89. package/package.json +2 -2
  90. package/src/libs/api/repository/src/lib/gn4/selection/selection.service.ts +14 -38
  91. package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +1 -1
  92. package/src/libs/common/domain/src/lib/model/search/field.model.ts +1 -0
  93. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +1 -1
  94. package/src/libs/common/domain/src/lib/model/search/index.ts +1 -0
  95. package/src/libs/common/domain/src/lib/model/search/search.model.ts +2 -2
  96. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  97. package/src/libs/feature/catalog/src/lib/my-org/my-org.service.ts +4 -16
  98. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +1 -1
  99. package/src/libs/feature/search/src/index.ts +3 -0
  100. package/src/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.ts +1 -1
  101. package/src/libs/feature/search/src/lib/feature-search.module.ts +1 -10
  102. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +4 -4
  103. package/src/libs/feature/search/src/lib/filter-geometry.token.ts +7 -0
  104. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.ts +9 -3
  105. package/src/libs/feature/search/src/lib/record-url.token.ts +4 -0
  106. package/src/libs/feature/search/src/lib/results-list/results-list.container.component.ts +1 -1
  107. package/src/libs/feature/search/src/lib/results-table/results-table.component.css +0 -0
  108. package/src/libs/feature/search/src/lib/results-table/results-table.component.html +112 -0
  109. package/src/libs/feature/search/src/lib/results-table/results-table.component.ts +164 -0
  110. package/src/libs/feature/search/src/lib/state/effects.ts +5 -4
  111. package/src/libs/feature/search/src/lib/state/search.facade.ts +2 -1
  112. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +5 -5
  113. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +12 -4
  114. package/src/libs/ui/inputs/src/index.ts +1 -0
  115. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +1 -1
  116. package/src/libs/ui/inputs/src/lib/checkbox/checkbox.component.html +0 -1
  117. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  118. package/src/libs/ui/layout/src/index.ts +2 -0
  119. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.css +0 -0
  120. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.html +1 -0
  121. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.ts +33 -0
  122. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.css +15 -0
  123. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +54 -0
  124. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +42 -0
  125. package/src/libs/ui/search/src/index.ts +0 -1
  126. package/src/libs/ui/search/src/lib/ui-search.module.ts +2 -3
  127. package/src/libs/util/i18n/src/index.ts +0 -1
  128. package/translations/de.json +4 -3
  129. package/translations/en.json +5 -4
  130. package/translations/es.json +4 -3
  131. package/translations/fr.json +5 -4
  132. package/translations/it.json +4 -3
  133. package/translations/nl.json +4 -3
  134. package/translations/pt.json +4 -3
  135. package/translations/sk.json +4 -3
  136. package/esm2022/libs/ui/search/src/lib/record-table/record-table.component.mjs +0 -145
  137. package/esm2022/libs/util/i18n/src/lib/testing/test.translate.loader.mjs +0 -24
  138. package/esm2022/libs/util/i18n/src/lib/testing/test.translate.module.mjs +0 -109
  139. package/esm2022/libs/util/i18n/src/lib/testing/translations.model.mjs +0 -6
  140. package/libs/ui/search/src/lib/record-table/record-table.component.d.ts +0 -31
  141. package/libs/ui/search/src/lib/record-table/record-table.component.d.ts.map +0 -1
  142. package/libs/util/i18n/src/lib/testing/test.translate.loader.d.ts +0 -17
  143. package/libs/util/i18n/src/lib/testing/test.translate.loader.d.ts.map +0 -1
  144. package/libs/util/i18n/src/lib/testing/test.translate.module.d.ts +0 -131
  145. package/libs/util/i18n/src/lib/testing/test.translate.module.d.ts.map +0 -1
  146. package/libs/util/i18n/src/lib/testing/translations.model.d.ts +0 -21
  147. package/libs/util/i18n/src/lib/testing/translations.model.d.ts.map +0 -1
  148. package/src/libs/ui/search/src/lib/record-table/record-table.component.css +0 -7
  149. package/src/libs/ui/search/src/lib/record-table/record-table.component.html +0 -215
  150. package/src/libs/ui/search/src/lib/record-table/record-table.component.ts +0 -149
  151. package/src/libs/util/i18n/src/lib/testing/test.translate.loader.ts +0 -26
  152. package/src/libs/util/i18n/src/lib/testing/test.translate.module.ts +0 -235
  153. package/src/libs/util/i18n/src/lib/testing/translations.model.ts +0 -28
@@ -1,7 +1,7 @@
1
1
  import { Injectable } from '@angular/core'
2
2
  import { CatalogRecord } from '../../../../../../../libs/common/domain/src/lib/model/record'
3
3
  import { SelectionsApiService } from '../../../../../../../libs/data-access/gn4/src'
4
- import { BehaviorSubject, Observable, Subscription, map, tap } from 'rxjs'
4
+ import { BehaviorSubject, firstValueFrom } from 'rxjs'
5
5
 
6
6
  const BUCKET_ID = 'gnui'
7
7
 
@@ -12,7 +12,6 @@ export class SelectionService {
12
12
  selectedRecordsIdentifiers$: BehaviorSubject<string[]> = new BehaviorSubject(
13
13
  []
14
14
  )
15
- subscription: Subscription
16
15
 
17
16
  constructor(private selectionsApi: SelectionsApiService) {
18
17
  this.selectionsApi.get(BUCKET_ID).subscribe((selectedIds) => {
@@ -33,48 +32,25 @@ export class SelectionService {
33
32
  this.selectedRecordsIdentifiers$.next(filtered)
34
33
  }
35
34
 
36
- selectRecords(records: CatalogRecord[]): Observable<void> {
37
- const newIds = []
38
- records.map((record) => {
39
- newIds.push(record.uniqueIdentifier)
35
+ selectRecords(records: CatalogRecord[]) {
36
+ const newIds = records.map((record) => record.uniqueIdentifier)
37
+ this.selectionsApi.add(BUCKET_ID, newIds).subscribe(() => {
38
+ this.addIdsToSelected(newIds)
40
39
  })
41
- const apiResponse = this.selectionsApi.add(BUCKET_ID, newIds)
42
- return apiResponse.pipe(
43
- tap(() => {
44
- this.addIdsToSelected(newIds)
45
- }),
46
- map(() => undefined)
47
- )
48
40
  }
49
41
 
50
- deselectRecords(records: CatalogRecord[]): Observable<void> {
51
- const idsToBeRemoved = []
52
- records.map((record) => {
53
- idsToBeRemoved.push(record.uniqueIdentifier)
42
+ deselectRecords(records: CatalogRecord[]) {
43
+ const idsToBeRemoved = records.map((record) => record.uniqueIdentifier)
44
+ this.selectionsApi.clear(BUCKET_ID, idsToBeRemoved).subscribe(() => {
45
+ this.removeIdsFromSelected(idsToBeRemoved)
54
46
  })
55
- const apiResponse = this.selectionsApi.clear(BUCKET_ID, idsToBeRemoved)
56
- return apiResponse.pipe(
57
- tap(() => {
58
- this.removeIdsFromSelected(idsToBeRemoved)
59
- }),
60
- map(() => undefined)
61
- )
62
47
  }
63
48
 
64
- clearSelection(): Observable<void> {
65
- const currentSelectedResponse = this.selectionsApi.get(BUCKET_ID)
66
- let currentSelection
67
- this.subscription = currentSelectedResponse.subscribe((value) => {
68
- currentSelection = [...value]
49
+ async clearSelection() {
50
+ const response = await firstValueFrom(this.selectionsApi.get(BUCKET_ID))
51
+ const currentSelection = Array.from(response)
52
+ this.selectionsApi.clear(BUCKET_ID, currentSelection).subscribe(() => {
53
+ this.removeIdsFromSelected(currentSelection)
69
54
  })
70
- this.selectionsApi.clear(BUCKET_ID, currentSelection)
71
- const apiResponse = this.selectionsApi.clear(BUCKET_ID, currentSelection)
72
-
73
- return apiResponse.pipe(
74
- tap(() => {
75
- this.removeIdsFromSelected(currentSelection)
76
- }),
77
- map(() => undefined)
78
- )
79
55
  }
80
56
  }
@@ -1,4 +1,4 @@
1
- import { FieldName } from './search.model'
1
+ import { FieldName } from './field.model'
2
2
  import { FieldFilters } from './filter.model'
3
3
 
4
4
  export interface TermsAggregationParams {
@@ -0,0 +1 @@
1
+ export type FieldName = string
@@ -1,4 +1,4 @@
1
- import { FieldName } from './search.model'
1
+ import { FieldName } from './field.model'
2
2
 
3
3
  export type FieldFilterByValues = Record<string, boolean>
4
4
  export type FieldFilterByExpression = string | number
@@ -2,3 +2,4 @@ export * from './aggregation.model'
2
2
  export * from './filter.model'
3
3
  export * from './search.model'
4
4
  export * from './sort-by.model'
5
+ export * from './field.model'
@@ -1,10 +1,10 @@
1
1
  import { FieldFilters } from './filter.model'
2
2
  import { CatalogRecord } from '../record'
3
3
  import { Geometry } from 'geojson'
4
+ import { FieldName } from './field.model'
4
5
 
5
- type FieldSort = ['desc' | 'asc', FieldName]
6
+ export type FieldSort = ['desc' | 'asc', FieldName]
6
7
  export type SortByField = FieldSort | FieldSort[] // several sort criteria can be used!
7
- export type FieldName = string
8
8
 
9
9
  export interface SearchParams {
10
10
  filters?: FieldFilters
@@ -0,0 +1 @@
1
+ export * from './user.model'
@@ -1,8 +1,9 @@
1
1
  import { Injectable } from '@angular/core'
2
2
  import { OrganizationsServiceInterface } from '../../../../../../libs/common/domain/src/lib/organizations.service.interface'
3
- import { BehaviorSubject, combineLatest, map, Observable } from 'rxjs'
3
+ import { combineLatest, map, Observable } from 'rxjs'
4
4
  import { UserModel } from '../../../../../../libs/common/domain/src/lib/model/user/user.model'
5
5
  import { PlatformServiceInterface } from '../../../../../../libs/common/domain/src/lib/platform.service.interface'
6
+ import { shareReplay } from 'rxjs/operators'
6
7
 
7
8
  @Injectable({
8
9
  providedIn: 'root',
@@ -16,20 +17,6 @@ export class MyOrgService {
16
17
  userList: UserModel[]
17
18
  }>
18
19
 
19
- private myOrgDataSubject = new BehaviorSubject<{
20
- orgName: string
21
- logoUrl: string
22
- recordCount: number
23
- userCount: number
24
- userList: UserModel[]
25
- }>({
26
- orgName: '',
27
- logoUrl: '',
28
- recordCount: 0,
29
- userCount: 0,
30
- userList: [],
31
- })
32
-
33
20
  constructor(
34
21
  private platformService: PlatformServiceInterface,
35
22
  private orgService: OrganizationsServiceInterface
@@ -55,7 +42,8 @@ export class MyOrgService {
55
42
  userList,
56
43
  userCount,
57
44
  }
58
- })
45
+ }),
46
+ shareReplay(1)
59
47
  )
60
48
  }
61
49
  }
@@ -50,7 +50,7 @@ const mdViewReducer = createReducer(
50
50
  chartConfig,
51
51
  })),
52
52
  on(MdViewActions.close, (state) => {
53
- // eslint-disable-next-line
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
54
  const { metadata, related, ...stateWithoutMd } = state
55
55
  return stateWithoutMd
56
56
  })
@@ -1,4 +1,6 @@
1
1
  export * from './lib/feature-search.module'
2
+ export * from './lib/filter-geometry.token'
3
+ export * from './lib/record-url.token'
2
4
  export * from './lib/state/actions'
3
5
  export * from './lib/state/selectors'
4
6
  export * from './lib/state/search.facade'
@@ -18,3 +20,4 @@ export * from './lib/results-hits-number/results-hits.container.component'
18
20
  export * from './lib/results-layout/results-layout.component'
19
21
  export * from './lib/sort-by/sort-by.component'
20
22
  export * from './lib/state/container/search-state.container.directive'
23
+ export * from './lib/results-table/results-table.component'
@@ -39,7 +39,7 @@ export class FavoriteStarComponent implements AfterViewInit, OnDestroy {
39
39
  map((favorites) => favorites.indexOf(this.record.uniqueIdentifier) > -1)
40
40
  )
41
41
  isAnonymous$ = this.platformService.isAnonymous()
42
- record_: CatalogRecord
42
+ record_: Partial<CatalogRecord>
43
43
  favoriteCount: number | null
44
44
  loading = false
45
45
  loginUrl = this.authService.loginUrl
@@ -15,24 +15,15 @@ import { initialState, reducer, SEARCH_FEATURE_KEY } from './state/reducer'
15
15
  import { ResultsHitsContainerComponent } from './results-hits-number/results-hits.container.component'
16
16
  import { SearchStateContainerDirective } from './state/container/search-state.container.directive'
17
17
  import { UiInputsModule } from '../../../../../libs/ui/inputs/src'
18
- import { InjectionToken, NgModule } from '@angular/core'
18
+ import { NgModule } from '@angular/core'
19
19
  import { UiElementsModule } from '../../../../../libs/ui/elements/src'
20
20
  import { FavoriteStarComponent } from './favorites/favorite-star/favorite-star.component'
21
21
  import { MatIconModule } from '@angular/material/icon'
22
22
  import { FilterDropdownComponent } from './filter-dropdown/filter-dropdown.component'
23
- import { Geometry } from 'geojson'
24
23
  import { UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
25
24
  import { RecordsRepositoryInterface } from '../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
26
25
  import { Gn4Repository } from '../../../../../libs/api/repository/src'
27
26
 
28
- // this geometry will be used to filter & boost results accordingly
29
- export const FILTER_GEOMETRY = new InjectionToken<Promise<Geometry>>(
30
- 'filter-geometry'
31
- )
32
-
33
- // expects the replacement key ${uuid}
34
- export const RECORD_URL_TOKEN = new InjectionToken<string>('record-url-token')
35
-
36
27
  @NgModule({
37
28
  declarations: [
38
29
  SortByComponent,
@@ -10,7 +10,7 @@ import { catchError, filter, map, startWith } from 'rxjs/operators'
10
10
  import { SearchFacade } from '../state/search.facade'
11
11
  import { SearchService } from '../utils/service/search.service'
12
12
  import { FieldsService } from '../utils/service/fields.service'
13
- import { FieldAvailableValue } from '../utils/service/fields'
13
+ import { FieldAvailableValue, FieldValue } from '../utils/service/fields'
14
14
 
15
15
  @Component({
16
16
  selector: 'gn-ui-filter-dropdown',
@@ -31,11 +31,11 @@ export class FilterDropdownComponent implements OnInit {
31
31
  filter((selected) => !!selected),
32
32
  startWith([]),
33
33
  catchError(() => of([]))
34
- )
34
+ ) as Observable<FieldValue[]>
35
35
 
36
- onSelectedValues(values: (string | number)[]) {
36
+ onSelectedValues(values: unknown[]) {
37
37
  this.fieldsService
38
- .buildFiltersFromFieldValues({ [this.fieldName]: values })
38
+ .buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
39
39
  .subscribe((filters) => this.searchService.updateFilters(filters))
40
40
  }
41
41
 
@@ -0,0 +1,7 @@
1
+ // this geometry will be used to filter & boost results accordingly
2
+ import { InjectionToken } from '@angular/core'
3
+ import { Geometry } from 'geojson'
4
+
5
+ export const FILTER_GEOMETRY = new InjectionToken<Promise<Geometry>>(
6
+ 'filter-geometry'
7
+ )
@@ -11,12 +11,13 @@ import {
11
11
  AutocompleteComponent,
12
12
  AutocompleteItem,
13
13
  } from '../../../../../../libs/ui/inputs/src'
14
- import { Observable } from 'rxjs'
14
+ import { Observable, firstValueFrom } from 'rxjs'
15
15
  import { map } from 'rxjs/operators'
16
16
  import { SearchFacade } from '../state/search.facade'
17
17
  import { SearchService } from '../utils/service/search.service'
18
18
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
19
19
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
20
+ import { SearchFilters } from '../../../../../../libs/api/metadata-converter/src'
20
21
 
21
22
  @Component({
22
23
  selector: 'gn-ui-fuzzy-search',
@@ -75,7 +76,12 @@ export class FuzzySearchComponent implements OnInit {
75
76
  }
76
77
  }
77
78
 
78
- handleInputCleared() {
79
- this.searchService.updateFilters({ any: '' })
79
+ async handleInputCleared() {
80
+ const currentSearchFilters: SearchFilters = await firstValueFrom(
81
+ this.searchFacade.searchFilters$
82
+ )
83
+ if (currentSearchFilters.any) {
84
+ this.searchService.updateFilters({ any: '' })
85
+ }
80
86
  }
81
87
  }
@@ -0,0 +1,4 @@
1
+ import { InjectionToken } from '@angular/core'
2
+
3
+ // expects the replacement key ${uuid}
4
+ export const RECORD_URL_TOKEN = new InjectionToken<string>('record-url-token')
@@ -17,7 +17,7 @@ import {
17
17
  ResultsLayoutConfigItem,
18
18
  ResultsLayoutConfigModel,
19
19
  } from '../../../../../../libs/ui/search/src'
20
- import { RECORD_URL_TOKEN } from '../feature-search.module'
20
+ import { RECORD_URL_TOKEN } from '../record-url.token'
21
21
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
22
22
 
23
23
  export type ResultsListShowMoreStrategy = 'auto' | 'button' | 'none'
@@ -0,0 +1,112 @@
1
+ <gn-ui-interactive-table
2
+ [items]="records$ | async"
3
+ (itemClick)="handleRecordClick($event)"
4
+ >
5
+ <!-- SELECTED COLUMN -->
6
+ <gn-ui-interactive-table-column>
7
+ <ng-template #header>
8
+ <gn-ui-checkbox
9
+ [checked]="isAllSelected() | async"
10
+ [indeterminate]="isSomeSelected() | async"
11
+ (changed)="toggleSelectAll()"
12
+ type="default"
13
+ class="-m-2 mr-3"
14
+ >
15
+ </gn-ui-checkbox>
16
+ </ng-template>
17
+ <ng-template #cell let-item>
18
+ <gn-ui-checkbox
19
+ [checked]="isChecked(item) | async"
20
+ (changed)="handleRecordSelectedChange($event, item)"
21
+ class="-m-2"
22
+ type="default"
23
+ ></gn-ui-checkbox>
24
+ </ng-template>
25
+ </gn-ui-interactive-table-column>
26
+
27
+ <!-- TITLE COLUMN -->
28
+ <gn-ui-interactive-table-column
29
+ [sortable]="true"
30
+ [activeSort]="isSortedBy('resourceTitleObject.default.keyword') | async"
31
+ (sortChange)="setSortBy('resourceTitleObject.default.keyword', $event)"
32
+ >
33
+ <ng-template #header>
34
+ <span translate>record.metadata.title</span>
35
+ </ng-template>
36
+ <ng-template #cell let-item>
37
+ {{ item.title }}
38
+ </ng-template>
39
+ </gn-ui-interactive-table-column>
40
+
41
+ <!-- FORMATS COLUMN -->
42
+ <gn-ui-interactive-table-column>
43
+ <ng-template #header>
44
+ <span translate>record.metadata.formats</span>
45
+ </ng-template>
46
+ <ng-template #cell let-item>
47
+ <div
48
+ class="flex justify-start items-center gap-2"
49
+ *ngIf="getRecordFormats(item) as formats"
50
+ [title]="formats.join(', ')"
51
+ >
52
+ <span
53
+ class="badge-btn min-w-[45px] text-sm text-white px-2 flex-shrink-0"
54
+ [style.background-color]="getBadgeColor(formats[0])"
55
+ *ngIf="formats[0]"
56
+ >
57
+ {{ formats[0] }}
58
+ </span>
59
+ <span
60
+ class="badge-btn min-w-[45px] text-sm text-white px-2 flex-shrink-0"
61
+ [style.background-color]="getBadgeColor(formats[1])"
62
+ *ngIf="formats[1]"
63
+ >
64
+ {{ formats[1] }}
65
+ </span>
66
+ <div class="flex-shrink-0" *ngIf="formats.slice(2).length > 0">
67
+ <span>+{{ formats.slice(2).length }}</span>
68
+ </div>
69
+ </div>
70
+ <div *ngIf="!getRecordFormats(item)"></div>
71
+ </ng-template>
72
+ </gn-ui-interactive-table-column>
73
+
74
+ <!-- OWNER COLUMN -->
75
+ <gn-ui-interactive-table-column
76
+ [sortable]="true"
77
+ [activeSort]="isSortedBy('recordOwner') | async"
78
+ (sortChange)="setSortBy('recordOwner', $event)"
79
+ >
80
+ <ng-template #header>
81
+ <span translate>record.metadata.author</span>
82
+ </ng-template>
83
+ <ng-template #cell let-item>
84
+ <mat-icon class="material-symbols-outlined">person</mat-icon>
85
+ <span>{{ formatUserInfo(item.extras?.ownerInfo) }}</span>
86
+ </ng-template>
87
+ </gn-ui-interactive-table-column>
88
+
89
+ <!-- STATUS COLUMN -->
90
+ <gn-ui-interactive-table-column>
91
+ <ng-template #header>
92
+ <span translate>record.metadata.status</span>
93
+ </ng-template>
94
+ <ng-template #cell let-item>
95
+ {{ item.status }}
96
+ </ng-template>
97
+ </gn-ui-interactive-table-column>
98
+
99
+ <!-- UPDATE DATE COLUMN -->
100
+ <gn-ui-interactive-table-column
101
+ [sortable]="true"
102
+ [activeSort]="isSortedBy('changeDate') | async"
103
+ (sortChange)="setSortBy('changeDate', $event)"
104
+ >
105
+ <ng-template #header>
106
+ <span translate>record.metadata.updatedOn</span>
107
+ </ng-template>
108
+ <ng-template #cell let-item>
109
+ {{ dateToString(item.recordUpdated) }}
110
+ </ng-template>
111
+ </gn-ui-interactive-table-column>
112
+ </gn-ui-interactive-table>
@@ -0,0 +1,164 @@
1
+ import { Component, EventEmitter, Output } from '@angular/core'
2
+ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+ import {
4
+ FileFormat,
5
+ getBadgeColor,
6
+ getFileFormat,
7
+ getFormatPriority,
8
+ } from '../../../../../../libs/util/shared/src'
9
+ import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
10
+ import {
11
+ InteractiveTableColumnComponent,
12
+ InteractiveTableComponent,
13
+ } from '../../../../../../libs/ui/layout/src'
14
+ import { MatIconModule } from '@angular/material/icon'
15
+ import { TranslateModule } from '@ngx-translate/core'
16
+ import { SearchFacade } from '../state/search.facade'
17
+ import { SelectionService } from '../../../../../../libs/api/repository/src'
18
+ import { combineLatest, firstValueFrom, Observable } from 'rxjs'
19
+ import { CommonModule } from '@angular/common'
20
+ import { map, take } from 'rxjs/operators'
21
+ import { FieldSort } from '../../../../../../libs/common/domain/src/lib/model/search'
22
+ import { SearchService } from '../utils/service/search.service'
23
+
24
+ @Component({
25
+ selector: 'gn-ui-results-table',
26
+ templateUrl: './results-table.component.html',
27
+ styleUrls: ['./results-table.component.css'],
28
+ standalone: true,
29
+ imports: [
30
+ CommonModule,
31
+ UiInputsModule,
32
+ InteractiveTableComponent,
33
+ InteractiveTableColumnComponent,
34
+ MatIconModule,
35
+ TranslateModule,
36
+ ],
37
+ })
38
+ export class ResultsTableComponent {
39
+ @Output() recordClick = new EventEmitter<CatalogRecord>()
40
+
41
+ records$ = this.searchFacade.results$
42
+ selectedRecords$ = this.selectionService.selectedRecordsIdentifiers$
43
+
44
+ constructor(
45
+ private searchFacade: SearchFacade,
46
+ private searchService: SearchService,
47
+ private selectionService: SelectionService
48
+ ) {}
49
+
50
+ dateToString(date: Date): string {
51
+ return date?.toLocaleDateString(undefined, {
52
+ year: 'numeric',
53
+ month: 'long',
54
+ day: 'numeric',
55
+ timeZone: 'UTC',
56
+ })
57
+ }
58
+
59
+ getStatus(isPublishedToAll: boolean | unknown) {
60
+ return isPublishedToAll ? 'published' : 'not published'
61
+ }
62
+
63
+ getRecordFormats(record: CatalogRecord): FileFormat[] {
64
+ if (record.kind === 'service' || !('distributions' in record)) {
65
+ return []
66
+ }
67
+ const formats = Array.from(
68
+ new Set(
69
+ record.distributions.map((distribution) => getFileFormat(distribution))
70
+ )
71
+ ).filter((format) => !!format)
72
+ formats.sort((a, b) => getFormatPriority(b) - getFormatPriority(a))
73
+ return formats
74
+ }
75
+
76
+ formatUserInfo(userInfo: string | unknown): string {
77
+ const infos = (typeof userInfo === 'string' ? userInfo : '').split('|')
78
+ if (infos && infos.length === 4) {
79
+ return `${infos[2]} ${infos[1]}`
80
+ }
81
+ return undefined
82
+ }
83
+
84
+ getBadgeColor(format: FileFormat): string {
85
+ return getBadgeColor(format)
86
+ }
87
+
88
+ handleRecordClick(item: unknown) {
89
+ this.recordClick.emit(item as CatalogRecord)
90
+ }
91
+
92
+ setSortBy(col: string, order: 'asc' | 'desc') {
93
+ this.searchService.setSortBy([order, col])
94
+ }
95
+
96
+ isSortedBy(col: string): Observable<'asc' | 'desc' | null> {
97
+ return this.searchFacade.sortBy$.pipe(
98
+ take(1),
99
+ map((sortOrder) => {
100
+ const sortArray = Array.isArray(sortOrder[0])
101
+ ? (sortOrder as FieldSort[])
102
+ : ([sortOrder] as FieldSort[])
103
+ for (const sort of sortArray) {
104
+ if (sort[1] === col) {
105
+ return sort[0]
106
+ }
107
+ }
108
+ return null
109
+ })
110
+ )
111
+ }
112
+
113
+ isChecked(record: CatalogRecord): Observable<boolean> {
114
+ return this.selectedRecords$.pipe(
115
+ take(1),
116
+ map((selectedRecords) => {
117
+ return selectedRecords.includes(record.uniqueIdentifier)
118
+ })
119
+ )
120
+ }
121
+
122
+ handleRecordSelectedChange(selected: boolean, record: CatalogRecord) {
123
+ if (!selected) {
124
+ this.selectionService.deselectRecords([record])
125
+ } else {
126
+ this.selectionService.selectRecords([record])
127
+ }
128
+ }
129
+
130
+ async toggleSelectAll() {
131
+ const records = await firstValueFrom(this.records$)
132
+ if (await firstValueFrom(this.isAllSelected())) {
133
+ this.selectionService.deselectRecords(records)
134
+ } else {
135
+ this.selectionService.selectRecords(records)
136
+ }
137
+ }
138
+
139
+ isAllSelected(): Observable<boolean> {
140
+ return combineLatest([this.records$, this.selectedRecords$]).pipe(
141
+ take(1),
142
+ map(([records, selectedRecords]) => {
143
+ return records.every((record) =>
144
+ selectedRecords.includes(record.uniqueIdentifier)
145
+ )
146
+ })
147
+ )
148
+ }
149
+
150
+ isSomeSelected(): Observable<boolean> {
151
+ return combineLatest([this.records$, this.selectedRecords$]).pipe(
152
+ take(1),
153
+ map(([records, selectedRecords]) => {
154
+ const allSelected = records.every((record) =>
155
+ selectedRecords.includes(record.uniqueIdentifier)
156
+ )
157
+ const someSelected = records.some((record) =>
158
+ selectedRecords.includes(record.uniqueIdentifier)
159
+ )
160
+ return !allSelected && someSelected
161
+ })
162
+ )
163
+ }
164
+ }
@@ -41,7 +41,7 @@ import { getSearchStateSearch } from './selectors'
41
41
  import { HttpErrorResponse } from '@angular/common/http'
42
42
  import { switchMapWithSearchId } from '../utils/operators/search.operator'
43
43
  import { Geometry } from 'geojson'
44
- import { FILTER_GEOMETRY } from '../feature-search.module'
44
+ import { FILTER_GEOMETRY } from '../filter-geometry.token'
45
45
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
46
46
  import { FavoritesService } from '../../../../../../libs/api/repository/src'
47
47
  import { PlatformServiceInterface } from '../../../../../../libs/common/domain/src/lib/platform.service.interface'
@@ -126,10 +126,11 @@ export class SearchEffects {
126
126
  ),
127
127
  switchMap(([state, favorites]) => {
128
128
  if (!state.params.useSpatialFilter || !this.filterGeometry$) {
129
- return of([state, favorites, null])
129
+ return of([state, favorites, undefined])
130
130
  }
131
131
  return this.filterGeometry$.pipe(
132
132
  tap((geom) => {
133
+ if (!geom) return
133
134
  try {
134
135
  const trace = validGeoJson(geom, true) as string[]
135
136
  if (trace?.length > 0) {
@@ -145,7 +146,7 @@ export class SearchEffects {
145
146
  }),
146
147
  map((geom) => [state, favorites, geom]),
147
148
  catchError((e) => {
148
- return of([state, favorites, null])
149
+ return of([state, favorites, undefined])
149
150
  })
150
151
  )
151
152
  }),
@@ -153,7 +154,7 @@ export class SearchEffects {
153
154
  ([state, favorites, geometry]: [
154
155
  SearchStateSearch,
155
156
  string[],
156
- Geometry | null
157
+ Geometry | undefined
157
158
  ]) => {
158
159
  const { currentPage, pageSize, sort } = state.params
159
160
  const filters = {
@@ -40,7 +40,7 @@ import {
40
40
  isEndOfResults,
41
41
  totalPages,
42
42
  } from './selectors'
43
- import { FILTER_GEOMETRY } from '../feature-search.module'
43
+ import { FILTER_GEOMETRY } from '../filter-geometry.token'
44
44
  import { Geometry } from 'geojson'
45
45
  import { catchError, map, shareReplay } from 'rxjs/operators'
46
46
  import {
@@ -215,5 +215,6 @@ export class SearchFacade {
215
215
  this.store.dispatch(new SetFilters({}, this.searchId))
216
216
  this.store.dispatch(new SetSortBy([], this.searchId))
217
217
  this.store.dispatch(new SetFavoritesOnly(false, this.searchId))
218
+ return this
218
219
  }
219
220
  }