geonetwork-ui 2.6.0-dev.186d57931 → 2.6.0-dev.3e7617888

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 (102) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +2 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/common/resource-types.mjs +17 -15
  3. package/esm2022/libs/feature/search/src/index.mjs +2 -2
  4. package/esm2022/libs/feature/search/src/lib/feature-search.module.mjs +12 -5
  5. package/esm2022/libs/feature/search/src/lib/results-hits/results-hits.container.component.mjs +41 -0
  6. package/esm2022/libs/feature/search/src/lib/utils/service/fields.mjs +70 -1
  7. package/esm2022/libs/feature/search/src/lib/utils/service/fields.service.mjs +4 -3
  8. package/esm2022/libs/ui/elements/src/index.mjs +2 -1
  9. package/esm2022/libs/ui/elements/src/lib/kind-badge/kind-badge.component.mjs +51 -0
  10. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +10 -4
  11. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  12. package/esm2022/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.mjs +3 -3
  13. package/esm2022/libs/ui/inputs/src/lib/inline-filter/inline-filter.component.mjs +42 -0
  14. package/esm2022/libs/ui/inputs/src/lib/inline-filter/inline-filter.model.mjs +2 -0
  15. package/esm2022/libs/ui/inputs/src/lib/ui-inputs.module.mjs +7 -2
  16. package/esm2022/libs/ui/search/src/index.mjs +2 -1
  17. package/esm2022/libs/ui/search/src/lib/results-hits-number/results-hits-number.component.mjs +3 -3
  18. package/esm2022/libs/ui/search/src/lib/results-hits-search-kind/results-hits-search-kind.component.mjs +53 -0
  19. package/esm2022/libs/ui/search/src/lib/ui-search.module.mjs +11 -3
  20. package/esm2022/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.mjs +3 -3
  21. package/esm2022/translations/de.json +5 -1
  22. package/esm2022/translations/en.json +5 -1
  23. package/esm2022/translations/es.json +5 -1
  24. package/esm2022/translations/fr.json +5 -1
  25. package/esm2022/translations/it.json +5 -1
  26. package/esm2022/translations/nl.json +5 -1
  27. package/esm2022/translations/pt.json +5 -1
  28. package/fesm2022/geonetwork-ui.mjs +337 -74
  29. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  30. package/libs/api/metadata-converter/src/index.d.ts +1 -0
  31. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  32. package/libs/api/metadata-converter/src/lib/common/resource-types.d.ts +21 -0
  33. package/libs/api/metadata-converter/src/lib/common/resource-types.d.ts.map +1 -1
  34. package/libs/feature/search/src/index.d.ts +1 -1
  35. package/libs/feature/search/src/index.d.ts.map +1 -1
  36. package/libs/feature/search/src/lib/feature-search.module.d.ts +4 -2
  37. package/libs/feature/search/src/lib/feature-search.module.d.ts.map +1 -1
  38. package/libs/feature/search/src/lib/results-hits/results-hits.container.component.d.ts +21 -0
  39. package/libs/feature/search/src/lib/results-hits/results-hits.container.component.d.ts.map +1 -0
  40. package/libs/feature/search/src/lib/utils/service/fields.d.ts +21 -0
  41. package/libs/feature/search/src/lib/utils/service/fields.d.ts.map +1 -1
  42. package/libs/feature/search/src/lib/utils/service/fields.service.d.ts.map +1 -1
  43. package/libs/ui/elements/src/index.d.ts +1 -0
  44. package/libs/ui/elements/src/index.d.ts.map +1 -1
  45. package/libs/ui/elements/src/lib/kind-badge/kind-badge.component.d.ts +11 -0
  46. package/libs/ui/elements/src/lib/kind-badge/kind-badge.component.d.ts.map +1 -0
  47. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +2 -1
  48. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  49. package/libs/ui/inputs/src/index.d.ts +1 -0
  50. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  51. package/libs/ui/inputs/src/lib/inline-filter/inline-filter.component.d.ts +14 -0
  52. package/libs/ui/inputs/src/lib/inline-filter/inline-filter.component.d.ts.map +1 -0
  53. package/libs/ui/inputs/src/lib/inline-filter/inline-filter.model.d.ts +6 -0
  54. package/libs/ui/inputs/src/lib/inline-filter/inline-filter.model.d.ts.map +1 -0
  55. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts +25 -24
  56. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts.map +1 -1
  57. package/libs/ui/search/src/index.d.ts +1 -0
  58. package/libs/ui/search/src/index.d.ts.map +1 -1
  59. package/libs/ui/search/src/lib/results-hits-search-kind/results-hits-search-kind.component.d.ts +17 -0
  60. package/libs/ui/search/src/lib/results-hits-search-kind/results-hits-search-kind.component.d.ts.map +1 -0
  61. package/libs/ui/search/src/lib/ui-search.module.d.ts +21 -19
  62. package/libs/ui/search/src/lib/ui-search.module.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/libs/api/metadata-converter/src/index.ts +1 -0
  65. package/src/libs/api/metadata-converter/src/lib/common/resource-types.ts +29 -15
  66. package/src/libs/feature/search/src/index.ts +1 -1
  67. package/src/libs/feature/search/src/lib/feature-search.module.ts +9 -2
  68. package/src/libs/feature/search/src/lib/results-hits/results-hits.container.component.css +0 -0
  69. package/src/libs/feature/search/src/lib/results-hits/results-hits.container.component.html +16 -0
  70. package/src/libs/feature/search/src/lib/results-hits/results-hits.container.component.ts +59 -0
  71. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +4 -5
  72. package/src/libs/feature/search/src/lib/utils/service/fields.ts +99 -0
  73. package/src/libs/ui/elements/src/index.ts +1 -0
  74. package/src/libs/ui/elements/src/lib/kind-badge/kind-badge.component.css +6 -0
  75. package/src/libs/ui/elements/src/lib/kind-badge/kind-badge.component.html +9 -0
  76. package/src/libs/ui/elements/src/lib/kind-badge/kind-badge.component.ts +51 -0
  77. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +3 -0
  78. package/src/libs/ui/inputs/src/index.ts +1 -0
  79. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  80. package/src/libs/ui/inputs/src/lib/inline-filter/inline-filter.component.html +23 -0
  81. package/src/libs/ui/inputs/src/lib/inline-filter/inline-filter.component.ts +44 -0
  82. package/src/libs/ui/inputs/src/lib/inline-filter/inline-filter.model.ts +5 -0
  83. package/src/libs/ui/inputs/src/lib/ui-inputs.module.ts +3 -0
  84. package/src/libs/ui/search/src/index.ts +1 -0
  85. package/src/libs/ui/search/src/lib/results-hits-number/results-hits-number.component.html +1 -1
  86. package/src/libs/ui/search/src/lib/results-hits-search-kind/results-hits-search-kind.component.html +24 -0
  87. package/src/libs/ui/search/src/lib/results-hits-search-kind/results-hits-search-kind.component.ts +53 -0
  88. package/src/libs/ui/search/src/lib/ui-search.module.ts +5 -0
  89. package/src/libs/ui/widgets/src/lib/progress-bar/progress-bar.component.html +1 -1
  90. package/translations/de.json +5 -1
  91. package/translations/en.json +5 -1
  92. package/translations/es.json +5 -1
  93. package/translations/fr.json +5 -1
  94. package/translations/it.json +5 -1
  95. package/translations/nl.json +5 -1
  96. package/translations/pt.json +5 -1
  97. package/translations/sk.json +5 -1
  98. package/esm2022/libs/feature/search/src/lib/results-hits-number/results-hits.container.component.mjs +0 -18
  99. package/libs/feature/search/src/lib/results-hits-number/results-hits.container.component.d.ts +0 -9
  100. package/libs/feature/search/src/lib/results-hits-number/results-hits.container.component.d.ts.map +0 -1
  101. package/src/libs/feature/search/src/lib/results-hits-number/results-hits.container.component.html +0 -4
  102. package/src/libs/feature/search/src/lib/results-hits-number/results-hits.container.component.ts +0 -10
@@ -0,0 +1,16 @@
1
+ <div
2
+ class="flex flex-col gap-3 sm:flex-row justify-between"
3
+ [ngClass]="{
4
+ 'sm:items-center': (searchFacade.resultsHits$ | async) !== 0,
5
+ }"
6
+ >
7
+ <gn-ui-results-hits-number
8
+ [loading]="searchFacade.isLoading$ | async"
9
+ [hits]="searchFacade.resultsHits$ | async"
10
+ ></gn-ui-results-hits-number>
11
+ <gn-ui-results-hits-search-kind
12
+ (selectionChanged)="onSelectionChanged($event)"
13
+ [choices]="filterChoices$ | async"
14
+ [selected]="selected$ | async"
15
+ ></gn-ui-results-hits-search-kind>
16
+ </div>
@@ -0,0 +1,59 @@
1
+ import {
2
+ catchError,
3
+ filter,
4
+ map,
5
+ Observable,
6
+ of,
7
+ startWith,
8
+ switchMap,
9
+ } from 'rxjs'
10
+ import { Component, OnInit } from '@angular/core'
11
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
12
+ import { SearchFacade } from '../state/search.facade'
13
+ import { FieldAvailableValue, FieldValue } from '../utils/service/fields'
14
+ import { SearchService } from '../utils/service/search.service'
15
+ import { FieldsService } from '../utils/service/fields.service'
16
+
17
+ marker('search.filters.recordKind.all')
18
+ marker('search.filters.recordKind.dataset')
19
+ marker('search.filters.recordKind.service')
20
+ marker('search.filters.recordKind.reuse')
21
+
22
+ @Component({
23
+ selector: 'gn-ui-results-hits',
24
+ templateUrl: './results-hits.container.component.html',
25
+ styleUrls: ['./results-hits.container.component.css'],
26
+ })
27
+ export class ResultsHitsContainerComponent implements OnInit {
28
+ fieldName = 'recordKind'
29
+ filterChoices$: Observable<FieldAvailableValue[]>
30
+ selected$: Observable<FieldValue[]>
31
+
32
+ constructor(
33
+ protected searchFacade: SearchFacade,
34
+ private searchService: SearchService,
35
+ private fieldsService: FieldsService
36
+ ) {}
37
+
38
+ ngOnInit() {
39
+ this.selected$ = this.searchFacade.searchFilters$.pipe(
40
+ switchMap((filters) =>
41
+ this.fieldsService.readFieldValuesFromFilters(filters)
42
+ ),
43
+ map((fieldValues) => fieldValues[this.fieldName]),
44
+ filter((selected) => !!selected),
45
+ startWith([]),
46
+ catchError(() => of([]))
47
+ ) as Observable<FieldValue[]>
48
+
49
+ this.filterChoices$ = <Observable<FieldAvailableValue[]>>(
50
+ this.fieldsService.getAvailableValues(this.fieldName)
51
+ )
52
+ }
53
+
54
+ onSelectionChanged(values: string[]) {
55
+ this.fieldsService
56
+ .buildFiltersFromFieldValues({ [this.fieldName]: values })
57
+ .subscribe((filters) => this.searchService.updateFilters(filters))
58
+ }
59
+ }
@@ -10,8 +10,10 @@ import {
10
10
  MultilingualSearchField,
11
11
  OrganizationSearchField,
12
12
  OwnerSearchField,
13
+ ResourceTypeLegacyField,
13
14
  SimpleSearchField,
14
15
  TranslatedSearchField,
16
+ RecordKindField,
15
17
  UserSearchField,
16
18
  } from './fields'
17
19
  import { forkJoin, Observable, of } from 'rxjs'
@@ -47,11 +49,8 @@ export class FieldsService {
47
49
  protected fields = {
48
50
  organization: new OrganizationSearchField(this.injector),
49
51
  format: new SimpleSearchField('format', this.injector, 'asc'),
50
- resourceType: new TranslatedSearchField(
51
- 'resourceType',
52
- this.injector,
53
- 'asc'
54
- ),
52
+ resourceType: new ResourceTypeLegacyField(this.injector), // Deprecated, use `recordKind` instead
53
+ recordKind: new RecordKindField(this.injector),
55
54
  representationType: new TranslatedSearchField(
56
55
  'cl_spatialRepresentationType.key',
57
56
  this.injector,
@@ -22,6 +22,7 @@ import {
22
22
  } from '../../../../../../../libs/api/repository/src'
23
23
  import { LangService } from '../../../../../../../libs/util/i18n/src'
24
24
  import { formatUserInfo } from '../../../../../../../libs/util/shared/src'
25
+ import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
25
26
 
26
27
  export type FieldType = 'values' | 'dateRange'
27
28
 
@@ -29,6 +30,7 @@ export type FieldValue = string | number
29
30
  export interface FieldAvailableValue {
30
31
  value: FieldValue
31
32
  label: string
33
+ count?: number
32
34
  }
33
35
 
34
36
  export abstract class AbstractSearchField {
@@ -80,6 +82,7 @@ export class SimpleSearchField implements AbstractSearchField {
80
82
  const bucketPromises = buckets.map(async (bucket) => ({
81
83
  label: `${await this.getBucketLabel(bucket)} (${bucket.count})`,
82
84
  value: bucket.term.toString(),
85
+ count: bucket.count,
83
86
  }))
84
87
  return Promise.all(bucketPromises)
85
88
  })
@@ -480,3 +483,99 @@ export class AvailableServicesField extends SimpleSearchField {
480
483
  return of(values)
481
484
  }
482
485
  }
486
+
487
+ /**
488
+ * This class is meant to be used with the legacy filter on `resourceType` (now deprecated, the use of `recordKind` field is recommended).
489
+ * Since creating filters on the same ES field is not possible, in order to make the resource type filter still working,
490
+ * we create an ES on the fly: `resourceTypeLegacy` that references the `resourceType` under the hood.
491
+ * @deprecated Use `recordKind` field instead.
492
+ */
493
+ export class ResourceTypeLegacyField extends TranslatedSearchField {
494
+ constructor(injector: Injector) {
495
+ super('resourceTypeLegacy', injector, 'asc')
496
+
497
+ // Ask ES to create a field on the fly: 'resourceTypeLegacy' that is in fact, 'resourceType'
498
+ this.esService.registerRuntimeField(
499
+ 'resourceTypeLegacy',
500
+ `for (resourceType in doc.resourceType) { emit(resourceType) }`
501
+ )
502
+ }
503
+ }
504
+
505
+ export class RecordKindField extends SimpleSearchField {
506
+ TYPE_MAPPING = {
507
+ dataset: ['dataset', 'series', 'featureCatalog'],
508
+ service: ['service'],
509
+ reuse: Object.entries(PossibleResourceTypes)
510
+ .filter(([_, v]) => v === 'reuse')
511
+ .map(([k]) => k), // = ['application', 'map', 'staticMap', 'interactiveMap', ...]
512
+ }
513
+
514
+ constructor(injector: Injector) {
515
+ super('resourceType', injector, 'asc')
516
+ }
517
+
518
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
519
+ return this.repository.aggregate(this.getAggregations()).pipe(
520
+ map(
521
+ (response) =>
522
+ (response[this.esFieldName] as AggregationBuckets).buckets || []
523
+ ),
524
+ map((buckets: TermBucket[]) => {
525
+ const counts = buckets.reduce(
526
+ (acc, { term, count }) => {
527
+ const value = term.toString()
528
+ const key = this.TYPE_MAPPING.reuse.includes(value)
529
+ ? 'reuse'
530
+ : this.TYPE_MAPPING.dataset.includes(value)
531
+ ? 'dataset'
532
+ : value
533
+
534
+ acc[key] = (acc[key] || 0) + count
535
+ return acc
536
+ },
537
+ {} as Record<string, number>
538
+ )
539
+
540
+ return Object.keys(this.TYPE_MAPPING).map((type) => ({
541
+ label: type,
542
+ value: type,
543
+ count: counts[type] ?? 0,
544
+ }))
545
+ })
546
+ )
547
+ }
548
+
549
+ getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
550
+ const filters = {
551
+ [this.esFieldName]: values.reduce((acc, value) => {
552
+ if (value === '') return { ...acc, [value]: true }
553
+
554
+ const keysToAdd = this.TYPE_MAPPING[value] || [value]
555
+ keysToAdd.forEach((key: string) => (acc[key] = true))
556
+
557
+ return acc
558
+ }, {}),
559
+ }
560
+
561
+ return of(filters)
562
+ }
563
+
564
+ getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
565
+ const filter = filters[this.esFieldName]
566
+ if (!filter) return of([])
567
+
568
+ const activeValues = Object.keys(filter).filter((v) => filter[v])
569
+ const activeTypes = Object.keys(this.TYPE_MAPPING).filter((type) =>
570
+ this.TYPE_MAPPING[type].every((t: string) => activeValues.includes(t))
571
+ )
572
+
573
+ // Allow unknown values eg. 'type=somethingnotexist' (for UI to select none)
574
+ const allTypes = [].concat(...Object.values(this.TYPE_MAPPING))
575
+ const unknownValues = activeValues.filter(
576
+ (value) => !allTypes.includes(value)
577
+ )
578
+
579
+ return of([...activeTypes, ...unknownValues])
580
+ }
581
+ }
@@ -7,6 +7,7 @@ export * from './lib/downloads-list/downloads-list.component'
7
7
  export * from './lib/error/error.component'
8
8
  export * from './lib/image-input/image-input.component'
9
9
  export * from './lib/image-overlay-preview/image-overlay-preview.component'
10
+ export * from './lib/kind-badge/kind-badge.component'
10
11
  export * from './lib/link-card/link-card.component'
11
12
  export * from './lib/markdown-editor/markdown-editor.component'
12
13
  export * from './lib/markdown-parser/markdown-parser.component'
@@ -0,0 +1,6 @@
1
+ :host gn-ui-badge ng-icon {
2
+ font-size: 1.2rem;
3
+ width: 1.2rem;
4
+ height: 1.2rem;
5
+ transform: translateY(-2px);
6
+ }
@@ -0,0 +1,9 @@
1
+ <ng-icon [name]="iconKind" class="mr-1"></ng-icon>
2
+
3
+ <ng-container *ngIf="contentTemplate; else defaultContent">
4
+ <ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
5
+ </ng-container>
6
+
7
+ <ng-template #defaultContent>
8
+ {{ kind | translate }}
9
+ </ng-template>
@@ -0,0 +1,51 @@
1
+ import { CommonModule } from '@angular/common'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ Input,
6
+ TemplateRef,
7
+ } from '@angular/core'
8
+ import { provideIcons } from '@ng-icons/core'
9
+ import {
10
+ iconoirAppleWallet,
11
+ iconoirAppleShortcuts,
12
+ iconoirCode,
13
+ iconoirCreditCard,
14
+ } from '@ng-icons/iconoir'
15
+ import { NgIconsModule } from '@ng-icons/core'
16
+ import { BadgeComponent } from '../../../../../../libs/ui/inputs/src'
17
+ import { TranslateModule } from '@ngx-translate/core'
18
+
19
+ enum KindConfig {
20
+ all = 'iconoirAppleWallet', // (this one is for filter)
21
+ dataset = 'iconoirAppleShortcuts',
22
+ service = 'iconoirCode',
23
+ reuse = 'iconoirCreditCard',
24
+ }
25
+
26
+ @Component({
27
+ selector: 'gn-ui-kind-badge',
28
+ templateUrl: './kind-badge.component.html',
29
+ styleUrls: ['./kind-badge.component.css'],
30
+ viewProviders: [
31
+ provideIcons({
32
+ iconoirAppleWallet,
33
+ iconoirAppleShortcuts,
34
+ iconoirCode,
35
+ iconoirCreditCard,
36
+ }),
37
+ ],
38
+ changeDetection: ChangeDetectionStrategy.OnPush,
39
+ standalone: true,
40
+ imports: [NgIconsModule, CommonModule, BadgeComponent, TranslateModule],
41
+ })
42
+ export class KindBadgeComponent {
43
+ @Input() contentTemplate: TemplateRef<unknown>
44
+ @Input() kind: string
45
+
46
+ hasProjectedContent = false
47
+
48
+ get iconKind() {
49
+ return KindConfig[this.kind] || KindConfig.dataset
50
+ }
51
+ }
@@ -16,6 +16,7 @@ import { TranslateModule } from '@ngx-translate/core'
16
16
  import { AvatarComponent } from './avatar/avatar.component'
17
17
  import { ImageInputComponent } from './image-input/image-input.component'
18
18
  import { MarkdownParserComponent } from './markdown-parser/markdown-parser.component'
19
+ import { KindBadgeComponent } from './kind-badge/kind-badge.component'
19
20
  import { ThumbnailComponent } from './thumbnail/thumbnail.component'
20
21
  import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
21
22
  import { UserPreviewComponent } from './user-preview/user-preview.component'
@@ -46,6 +47,7 @@ import { ServiceCapabilitiesComponent } from './service-capabilities/service-cap
46
47
  ApplicationBannerComponent,
47
48
  InternalLinkCardComponent,
48
49
  ServiceCapabilitiesComponent,
50
+ KindBadgeComponent,
49
51
  ],
50
52
  providers: [
51
53
  provideNgIconsConfig({
@@ -62,6 +64,7 @@ import { ServiceCapabilitiesComponent } from './service-capabilities/service-cap
62
64
  ApplicationBannerComponent,
63
65
  InternalLinkCardComponent,
64
66
  ServiceCapabilitiesComponent,
67
+ KindBadgeComponent,
65
68
  ],
66
69
  })
67
70
  export class UiElementsModule {}
@@ -15,6 +15,7 @@ export * from './lib/dropdown-selector/dropdown-selector.model'
15
15
  export * from './lib/editable-label/editable-label.directive'
16
16
  export * from './lib/file-input/file-input.component'
17
17
  export * from './lib/files-drop/files-drop.directive'
18
+ export * from './lib/inline-filter/inline-filter.component'
18
19
  export * from './lib/search-input/search-input.component'
19
20
  export * from './lib/star-toggle/star-toggle.component'
20
21
  export * from './lib/switch-toggle/switch-toggle.component'
@@ -59,7 +59,7 @@
59
59
  #overlayContainer
60
60
  >
61
61
  <div
62
- class="border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary"
62
+ class="border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary"
63
63
  >
64
64
  <button
65
65
  type="button"
@@ -0,0 +1,23 @@
1
+ <ul class="flex flex-wrap list-none gap-2">
2
+ <li *ngFor="let choice of choices">
3
+ <label
4
+ class=""
5
+ [ngClass]="{
6
+ selected: isSelected(choice),
7
+ }"
8
+ >
9
+ <input
10
+ class="hidden"
11
+ type="checkbox"
12
+ #checkBox
13
+ [checked]="isSelected(choice)"
14
+ (change)="select(choice, checkBox.checked)"
15
+ [attr.data-cy]="'inlineFilter-' + choice.value"
16
+ />
17
+ <ng-container
18
+ *ngTemplateOutlet="itemTemplate; context: { $implicit: choice }"
19
+ >
20
+ </ng-container>
21
+ </label>
22
+ </li>
23
+ </ul>
@@ -0,0 +1,44 @@
1
+ import {
2
+ Component,
3
+ ContentChild,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ TemplateRef,
8
+ } from '@angular/core'
9
+ import { Choice } from './inline-filter.model'
10
+
11
+ @Component({
12
+ selector: 'gn-ui-inline-filter',
13
+ templateUrl: './inline-filter.component.html',
14
+ })
15
+ export class InlineFilterComponent {
16
+ @Input() choices: Choice[]
17
+ @Input() selected: unknown[] = []
18
+
19
+ @Output() selectValues = new EventEmitter<unknown[]>()
20
+
21
+ @ContentChild(TemplateRef) itemTemplate: TemplateRef<any>
22
+
23
+ isSelected(choice: Choice) {
24
+ return (
25
+ this.selected.indexOf(choice.value) > -1 ||
26
+ (this.selected.length === 0 && choice.value === 'all')
27
+ )
28
+ }
29
+
30
+ select(choice: Choice, selected: boolean) {
31
+ if (choice.value === 'all' && selected) {
32
+ this.selected = [choice.value]
33
+ } else {
34
+ this.selected = selected
35
+ ? [...this.selected.filter((v) => v !== choice.value), choice.value]
36
+ : this.selected.filter((v) => v !== choice.value)
37
+
38
+ // If any value selected, unselect all
39
+ this.selected = this.selected.filter((v) => v !== 'all')
40
+ }
41
+
42
+ this.selectValues.emit(this.selected)
43
+ }
44
+ }
@@ -0,0 +1,5 @@
1
+ export interface Choice {
2
+ value: unknown
3
+ label: string
4
+ icon?: string
5
+ }
@@ -33,6 +33,7 @@ import { DragAndDropFileInputComponent } from './drag-and-drop-file-input/drag-a
33
33
  import { DropdownMultiselectComponent } from './dropdown-multiselect/dropdown-multiselect.component'
34
34
  import { DropdownSelectorComponent } from './dropdown-selector/dropdown-selector.component'
35
35
  import { EditableLabelDirective } from './editable-label/editable-label.directive'
36
+ import { InlineFilterComponent } from './inline-filter/inline-filter.component'
36
37
  import { TextAreaComponent } from './text-area/text-area.component'
37
38
  import { ViewportIntersectorComponent } from './viewport-intersector/viewport-intersector.component'
38
39
 
@@ -42,6 +43,7 @@ import { ViewportIntersectorComponent } from './viewport-intersector/viewport-in
42
43
  DropdownMultiselectComponent,
43
44
  ViewportIntersectorComponent,
44
45
  CheckboxComponent,
46
+ InlineFilterComponent,
45
47
  ],
46
48
  imports: [
47
49
  CommonModule,
@@ -90,6 +92,7 @@ import { ViewportIntersectorComponent } from './viewport-intersector/viewport-in
90
92
  CheckboxComponent,
91
93
  DateRangePickerComponent,
92
94
  EditableLabelDirective,
95
+ InlineFilterComponent,
93
96
  BadgeComponent,
94
97
  ],
95
98
  })
@@ -16,5 +16,6 @@ export * from './lib/record-preview-text/record-preview-text.component'
16
16
  export * from './lib/record-preview-title/record-preview-title.component'
17
17
  export * from './lib/record-metric/record-metric.component'
18
18
  export * from './lib/results-list-item/results-list-item.component'
19
+ export * from './lib/results-hits-search-kind/results-hits-search-kind.component'
19
20
  export * from './lib/results-hits-number/results-hits-number.component'
20
21
  export * from './lib/results-table/results-table.component'
@@ -1,5 +1,5 @@
1
1
  <div *ngIf="!loading" class="w-full">
2
- <span translate [translateParams]="{ hits: hits }"
2
+ <span data-cy="resultsHitsFound" translate [translateParams]="{ hits: hits }"
3
3
  >results.records.hits.found</span
4
4
  >
5
5
  <p
@@ -0,0 +1,24 @@
1
+ <gn-ui-inline-filter
2
+ [choices]="availableChoices"
3
+ [selected]="selected"
4
+ (selectValues)="onSelectedValues($event)"
5
+ >
6
+ <ng-template let-choice>
7
+ <gn-ui-kind-badge
8
+ [kind]="choice.value"
9
+ [contentTemplate]="customTemplate"
10
+ class="gn-ui-btn-outline rounded-md py-2 px-2 border-gray-400 cursor-pointer focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors bg-transparent hover:text-primary-darker focus:text-primary-darker active:text-primary-black"
11
+ [ngClass]="{
12
+ '!bg-primary-lightest hover:bg-primary-lightest':
13
+ isSelectedChoice(choice.value) || isAllChoice(choice.value),
14
+ }"
15
+ >
16
+ <ng-template #customTemplate>
17
+ {{ 'search.filters.recordKind.' + choice.value | translate
18
+ }}<span class="ml-1" *ngIf="isSelectedChoice(choice.value)"
19
+ >({{ choice.count }})</span
20
+ >
21
+ </ng-template>
22
+ </gn-ui-kind-badge>
23
+ </ng-template>
24
+ </gn-ui-inline-filter>
@@ -0,0 +1,53 @@
1
+ import {
2
+ Component,
3
+ EventEmitter,
4
+ Input,
5
+ OnChanges,
6
+ SimpleChanges,
7
+ Output,
8
+ } from '@angular/core'
9
+ import { Choice } from '../../../../../../libs/ui/inputs/src'
10
+
11
+ @Component({
12
+ selector: 'gn-ui-results-hits-search-kind',
13
+ templateUrl: './results-hits-search-kind.component.html',
14
+ })
15
+ export class ResultsHitsSearchKindComponent implements OnChanges {
16
+ @Input() selected: string[] = []
17
+ @Input() choices: Choice[] = []
18
+ @Output() selectionChanged = new EventEmitter<string[]>()
19
+
20
+ availableChoices: Choice[] = []
21
+
22
+ ngOnChanges(changes: SimpleChanges) {
23
+ if (changes.choices && changes.choices.currentValue) {
24
+ this.availableChoices = this.buildFilterChoices(this.choices)
25
+ }
26
+ }
27
+
28
+ buildFilterChoices(availableValues: Choice[]) {
29
+ return [
30
+ ...[
31
+ {
32
+ label: 'all',
33
+ value: 'all',
34
+ },
35
+ ],
36
+ ...availableValues,
37
+ ]
38
+ }
39
+
40
+ onSelectedValues(values: string[]) {
41
+ const selectedValues = values.includes('all') ? [] : values
42
+
43
+ this.selectionChanged.emit(selectedValues)
44
+ }
45
+
46
+ isSelectedChoice(choiceValue: string) {
47
+ return this.selected.includes(choiceValue)
48
+ }
49
+
50
+ isAllChoice(choiceValue: string) {
51
+ return this.selected.length === 0 && choiceValue === 'all'
52
+ }
53
+ }
@@ -11,6 +11,7 @@ import { RecordPreviewRowComponent } from './record-preview-row/record-preview-r
11
11
  import { RecordPreviewTextComponent } from './record-preview-text/record-preview-text.component'
12
12
  import { RecordPreviewTitleComponent } from './record-preview-title/record-preview-title.component'
13
13
  import { ResultsHitsNumberComponent } from './results-hits-number/results-hits-number.component'
14
+ import { ResultsHitsSearchKindComponent } from './results-hits-search-kind/results-hits-search-kind.component'
14
15
  import {
15
16
  DEFAULT_RESULTS_LAYOUT_CONFIG,
16
17
  RESULTS_LAYOUT_CONFIG,
@@ -24,6 +25,7 @@ import { UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
24
25
  import {
25
26
  MetadataQualityComponent,
26
27
  UiElementsModule,
28
+ KindBadgeComponent,
27
29
  } from '../../../../../libs/ui/elements/src'
28
30
  import { RecordPreviewFeedComponent } from './record-preview-feed/record-preview-feed.component'
29
31
  import { CommonModule } from '@angular/common'
@@ -48,6 +50,7 @@ import { matFace } from '@ng-icons/material-icons/baseline'
48
50
  RecordMetricComponent,
49
51
  ResultsListComponent,
50
52
  ResultsHitsNumberComponent,
53
+ ResultsHitsSearchKindComponent,
51
54
  ResultsListItemComponent,
52
55
  RecordPreviewFeedComponent,
53
56
  RecordPreviewRowComponent,
@@ -74,6 +77,7 @@ import { matFace } from '@ng-icons/material-icons/baseline'
74
77
  matFace,
75
78
  matHomeWorkOutline,
76
79
  }),
80
+ KindBadgeComponent,
77
81
  MetadataQualityComponent,
78
82
  ],
79
83
  exports: [
@@ -86,6 +90,7 @@ import { matFace } from '@ng-icons/material-icons/baseline'
86
90
  FacetsModule,
87
91
  RecordPreviewComponent,
88
92
  ResultsHitsNumberComponent,
93
+ ResultsHitsSearchKindComponent,
89
94
  RecordPreviewFeedComponent,
90
95
  RecordPreviewRowComponent,
91
96
  ],
@@ -3,7 +3,7 @@
3
3
  <ng-container *ngSwitchCase="'light'">
4
4
  <div class="flex items-center relative">
5
5
  <div
6
- class="flex-shrink-0 {{ color.text }} text-xs font-medium mr-2
6
+ class="flex-shrink-0 {{ color.text }} font-medium mr-2
7
7
  text-opacity-100 !text-slate-800"
8
8
  data-cy="progressPercentage"
9
9
  >
@@ -51,7 +51,7 @@
51
51
  "datafeeder.month.september": "September",
52
52
  "datafeeder.wizardSummarize.createdAt": "Erstellt am",
53
53
  "datafeeder.wizardSummarize.scale": "Maßstab",
54
- "datahub.header.datasets": "Datensätze",
54
+ "datahub.header.datasets": "Katalog",
55
55
  "datahub.header.lastRecords": "Die neuesten",
56
56
  "datahub.header.myfavorites": "Meine Favoriten",
57
57
  "datahub.header.news": "Startseite",
@@ -467,6 +467,10 @@
467
467
  "results.layout.selectOne": "Layout auswählen",
468
468
  "results.records.hits.displayedOn": "{displayed, plural, =0{Kein Datensatz.} one{1 Datensatz} other{{displayed} Datensätze }} {hits, plural, other{angezeigt von {hits} insgesamt.}}",
469
469
  "results.records.hits.empty.help.html": "Vorschläge: <ul class='list-disc list-inside'><li>Versuchen Sie es mit anderen Worten</li><li>Beschränken Sie die Suchwörter</li></ul>",
470
+ "search.filters.recordKind.all": "Alle",
471
+ "search.filters.recordKind.dataset": "Datensätze",
472
+ "search.filters.recordKind.reuse": "Wiederverwendungen",
473
+ "search.filters.recordKind.service": "Dienste",
470
474
  "results.records.hits.found": "{hits, plural, =0{Keine Dokumente entsprechen der angegebenen Suche.} one{1 Datensatz gefunden.} other{{hits} Datensätze gefunden.}}",
471
475
  "results.records.hits.selected": "{ amount } ausgewählt",
472
476
  "results.showMore": "Mehr Ergebnisse anzeigen...",
@@ -51,7 +51,7 @@
51
51
  "datafeeder.month.september": "September",
52
52
  "datafeeder.wizardSummarize.createdAt": "Created at",
53
53
  "datafeeder.wizardSummarize.scale": "Scale",
54
- "datahub.header.datasets": "Datasets",
54
+ "datahub.header.datasets": "Catalog",
55
55
  "datahub.header.lastRecords": "The latest",
56
56
  "datahub.header.myfavorites": "My favorites",
57
57
  "datahub.header.news": "Home",
@@ -467,6 +467,10 @@
467
467
  "results.layout.selectOne": "Results layout",
468
468
  "results.records.hits.displayedOn": "{displayed, plural, =0{No dataset.} one{1 } other{{displayed} }} {hits, plural, other{displayed on {hits} total.}}",
469
469
  "results.records.hits.empty.help.html": "Suggestions: <ul class='list-disc list-inside'><li>Try other words</li><li>Specify fewer words</li></ul>",
470
+ "search.filters.recordKind.all": "All",
471
+ "search.filters.recordKind.dataset": "Datasets",
472
+ "search.filters.recordKind.reuse": "Reuses",
473
+ "search.filters.recordKind.service": "Services",
470
474
  "results.records.hits.found": "{hits, plural, =0{No datasets match the specified search.} one{1 dataset found.} other{{hits} datasets found.}}",
471
475
  "results.records.hits.selected": "{ amount } selected",
472
476
  "results.showMore": "Show more results...",
@@ -51,7 +51,7 @@
51
51
  "datafeeder.month.september": "",
52
52
  "datafeeder.wizardSummarize.createdAt": "",
53
53
  "datafeeder.wizardSummarize.scale": "",
54
- "datahub.header.datasets": "",
54
+ "datahub.header.datasets": "Catálogo",
55
55
  "datahub.header.lastRecords": "",
56
56
  "datahub.header.myfavorites": "",
57
57
  "datahub.header.news": "",
@@ -467,6 +467,10 @@
467
467
  "results.layout.selectOne": "",
468
468
  "results.records.hits.displayedOn": "",
469
469
  "results.records.hits.empty.help.html": "",
470
+ "search.filters.recordKind.all": "Todos",
471
+ "search.filters.recordKind.dataset": "",
472
+ "search.filters.recordKind.reuse": "",
473
+ "search.filters.recordKind.service": "",
470
474
  "results.records.hits.found": "",
471
475
  "results.records.hits.selected": "",
472
476
  "results.showMore": "",