geonetwork-ui 2.3.0-dev.2b8cc035 → 2.3.0-dev.3222f975

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 (88) hide show
  1. package/esm2022/libs/feature/dataviz/src/lib/service/data.service.mjs +35 -2
  2. package/esm2022/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.mjs +73 -20
  3. package/esm2022/libs/feature/map/src/lib/map-context/map-context.model.mjs +1 -1
  4. package/esm2022/libs/feature/map/src/lib/map-context/map-context.service.mjs +29 -8
  5. package/esm2022/libs/feature/record/src/lib/map-view/map-view.component.mjs +3 -2
  6. package/esm2022/libs/feature/record/src/lib/state/mdview.facade.mjs +1 -1
  7. package/esm2022/libs/ui/elements/src/lib/downloads-list/downloads-list.component.mjs +2 -2
  8. package/esm2022/libs/ui/elements/src/lib/link-card/link-card.component.mjs +16 -3
  9. package/esm2022/libs/ui/elements/src/lib/record-api-form/record-api-form.component.mjs +5 -3
  10. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +1 -6
  11. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  12. package/esm2022/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.mjs +29 -0
  13. package/esm2022/libs/ui/layout/src/index.mjs +2 -1
  14. package/esm2022/libs/ui/layout/src/lib/block-list/block-list.component.mjs +76 -0
  15. package/esm2022/libs/ui/layout/src/lib/carousel/carousel.component.mjs +42 -18
  16. package/esm2022/libs/ui/layout/src/lib/ui-layout.module.mjs +3 -8
  17. package/esm2022/libs/util/shared/src/lib/links/link-classifier.service.mjs +2 -2
  18. package/esm2022/translations/de.json +9 -14
  19. package/esm2022/translations/en.json +9 -14
  20. package/esm2022/translations/es.json +9 -14
  21. package/esm2022/translations/fr.json +9 -14
  22. package/esm2022/translations/it.json +9 -14
  23. package/esm2022/translations/nl.json +9 -14
  24. package/esm2022/translations/pt.json +9 -14
  25. package/fesm2022/geonetwork-ui.mjs +352 -157
  26. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  27. package/libs/feature/dataviz/src/lib/service/data.service.d.ts +3 -1
  28. package/libs/feature/dataviz/src/lib/service/data.service.d.ts.map +1 -1
  29. package/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.d.ts +10 -5
  30. package/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.d.ts.map +1 -1
  31. package/libs/feature/map/src/lib/map-context/map-context.model.d.ts +1 -0
  32. package/libs/feature/map/src/lib/map-context/map-context.model.d.ts.map +1 -1
  33. package/libs/feature/map/src/lib/map-context/map-context.service.d.ts.map +1 -1
  34. package/libs/feature/record/src/lib/map-view/map-view.component.d.ts.map +1 -1
  35. package/libs/feature/record/src/lib/state/mdview.facade.d.ts.map +1 -1
  36. package/libs/ui/elements/src/lib/link-card/link-card.component.d.ts +3 -1
  37. package/libs/ui/elements/src/lib/link-card/link-card.component.d.ts.map +1 -1
  38. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts.map +1 -1
  39. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +29 -30
  40. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  41. package/libs/ui/inputs/src/index.d.ts +1 -0
  42. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  43. package/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.d.ts +12 -0
  44. package/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.d.ts.map +1 -0
  45. package/libs/ui/layout/src/index.d.ts +1 -0
  46. package/libs/ui/layout/src/index.d.ts.map +1 -1
  47. package/libs/ui/layout/src/lib/block-list/block-list.component.d.ts +25 -0
  48. package/libs/ui/layout/src/lib/block-list/block-list.component.d.ts.map +1 -0
  49. package/libs/ui/layout/src/lib/carousel/carousel.component.d.ts +13 -6
  50. package/libs/ui/layout/src/lib/carousel/carousel.component.d.ts.map +1 -1
  51. package/libs/ui/layout/src/lib/ui-layout.module.d.ts +4 -5
  52. package/libs/ui/layout/src/lib/ui-layout.module.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/libs/common/fixtures/src/lib/link.fixtures.ts +8 -0
  55. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +51 -1
  56. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.css +7 -0
  57. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html +32 -18
  58. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts +72 -17
  59. package/src/libs/feature/map/src/lib/map-context/map-context.model.ts +1 -0
  60. package/src/libs/feature/map/src/lib/map-context/map-context.service.ts +26 -8
  61. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +2 -1
  62. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +0 -1
  63. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts +1 -1
  64. package/src/libs/ui/elements/src/lib/link-card/link-card.component.html +38 -20
  65. package/src/libs/ui/elements/src/lib/link-card/link-card.component.ts +12 -0
  66. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -2
  67. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +0 -2
  68. package/src/libs/ui/inputs/src/index.ts +1 -0
  69. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.css +6 -0
  70. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.html +26 -0
  71. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.ts +32 -0
  72. package/src/libs/ui/layout/src/index.ts +1 -0
  73. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +23 -0
  74. package/src/libs/ui/layout/src/lib/block-list/block-list.component.html +20 -0
  75. package/src/libs/ui/layout/src/lib/block-list/block-list.component.ts +84 -0
  76. package/src/libs/ui/layout/src/lib/carousel/carousel.component.css +7 -4
  77. package/src/libs/ui/layout/src/lib/carousel/carousel.component.html +4 -4
  78. package/src/libs/ui/layout/src/lib/carousel/carousel.component.ts +45 -15
  79. package/src/libs/ui/layout/src/lib/ui-layout.module.ts +0 -2
  80. package/src/libs/util/shared/src/lib/links/link-classifier.service.ts +1 -1
  81. package/translations/de.json +9 -14
  82. package/translations/en.json +9 -14
  83. package/translations/es.json +9 -14
  84. package/translations/fr.json +9 -14
  85. package/translations/it.json +9 -14
  86. package/translations/nl.json +9 -14
  87. package/translations/pt.json +9 -14
  88. package/translations/sk.json +9 -14
@@ -14,7 +14,7 @@ import {
14
14
  MapContextLayerTypeEnum,
15
15
  } from '../map-context/map-context.model'
16
16
  import { TranslateModule } from '@ngx-translate/core'
17
- import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
17
+ import { DropdownChoice, UiInputsModule } from '../../../../../../libs/ui/inputs/src'
18
18
  import { CommonModule } from '@angular/common'
19
19
  import { MapLayer } from '../+state/map.models'
20
20
 
@@ -30,18 +30,16 @@ export class AddLayerFromOgcApiComponent implements OnInit {
30
30
  @Output() layerAdded = new EventEmitter<MapLayer>()
31
31
 
32
32
  urlChange = new Subject<string>()
33
- layerUrl = ''
34
33
  loading = false
35
- layers: string[] = []
36
- ogcEndpoint: OgcApiEndpoint = null
34
+ layers: any[] = []
37
35
  errorMessage: string | null = null
36
+ selectedLayerTypes: { [key: string]: DropdownChoice['value'] } = {}
38
37
 
39
38
  constructor(private changeDetectorRef: ChangeDetectorRef) {}
40
39
 
41
40
  ngOnInit() {
42
41
  this.urlChange.pipe(debounceTime(700)).subscribe(() => {
43
42
  this.loadLayers()
44
- this.changeDetectorRef.detectChanges() // manually trigger change detection
45
43
  })
46
44
  }
47
45
 
@@ -49,14 +47,13 @@ export class AddLayerFromOgcApiComponent implements OnInit {
49
47
  this.errorMessage = null
50
48
  try {
51
49
  this.loading = true
52
- if (this.ogcUrl.trim() === '') {
50
+ if (!this.ogcUrl.trim()) {
53
51
  this.layers = []
54
52
  return
55
53
  }
56
- this.ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
57
-
58
- // Currently only supports feature collections
59
- this.layers = await this.ogcEndpoint.featureCollections
54
+ const ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
55
+ this.layers = await ogcEndpoint.allCollections
56
+ this.setDefaultLayerTypes()
60
57
  } catch (error) {
61
58
  const err = error as Error
62
59
  this.layers = []
@@ -67,14 +64,72 @@ export class AddLayerFromOgcApiComponent implements OnInit {
67
64
  }
68
65
  }
69
66
 
70
- async addLayer(layer: string) {
71
- this.layerUrl = await this.ogcEndpoint.getCollectionItemsUrl(layer)
67
+ setDefaultLayerTypes() {
68
+ this.layers.forEach((layer) => {
69
+ const choices = this.getLayerChoices(layer)
70
+ if (choices.length > 0) {
71
+ this.selectedLayerTypes[layer.name] = choices[0].value
72
+ }
73
+ })
74
+ }
72
75
 
73
- const layerToAdd: MapContextLayerModel = {
74
- name: layer,
75
- url: this.layerUrl,
76
- type: MapContextLayerTypeEnum.OGCAPI,
76
+ getLayerChoices(layer: any) {
77
+ const choices = []
78
+ if (layer.hasRecords) {
79
+ choices.push({ label: 'Records', value: 'record' })
80
+ }
81
+ if (layer.hasFeatures) {
82
+ choices.push({ label: 'Features', value: 'features' })
83
+ }
84
+ if (layer.hasVectorTiles) {
85
+ choices.push({ label: 'Vector Tiles', value: 'vectorTiles' })
86
+ }
87
+ if (layer.hasMapTiles) {
88
+ choices.push({ label: 'Map Tiles', value: 'mapTiles' })
89
+ }
90
+ return choices
91
+ }
92
+
93
+ shouldDisplayLayer(layer: any) {
94
+ return (
95
+ layer.hasRecords ||
96
+ layer.hasFeatures ||
97
+ layer.hasVectorTiles ||
98
+ layer.hasMapTiles
99
+ )
100
+ }
101
+
102
+ onLayerTypeSelect(layerName: string, selectedType: any) {
103
+ this.selectedLayerTypes[layerName] = selectedType
104
+ ? selectedType
105
+ : this.getLayerChoices(layerName)[0]?.value
106
+ }
107
+
108
+ async addLayer(layer: string, layerType: any) {
109
+ try {
110
+ const ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
111
+ let layerUrl: string
112
+
113
+ if (layerType === 'vectorTiles') {
114
+ layerUrl = await ogcEndpoint.getVectorTilesetUrl(layer)
115
+ } else if (layerType === 'mapTiles') {
116
+ layerUrl = await ogcEndpoint.getMapTilesetUrl(layer)
117
+ } else {
118
+ layerUrl = await ogcEndpoint.getCollectionItemsUrl(layer, {
119
+ outputFormat: 'json',
120
+ })
121
+ }
122
+
123
+ const layerToAdd: MapContextLayerModel = {
124
+ name: layer,
125
+ url: layerUrl,
126
+ type: MapContextLayerTypeEnum.OGCAPI,
127
+ layerType: layerType,
128
+ }
129
+ this.layerAdded.emit({ ...layerToAdd, title: layer })
130
+ } catch (error) {
131
+ const err = error as Error
132
+ console.error('Error adding layer:', err.message)
77
133
  }
78
- this.layerAdded.emit({ ...layerToAdd, title: layer })
79
134
  }
80
135
  }
@@ -38,6 +38,7 @@ export interface MapContextLayerOgcapiModel {
38
38
  type: 'ogcapi'
39
39
  url: string
40
40
  name: string
41
+ layerType: 'feature' | 'vectorTiles' | 'mapTiles' | 'record'
41
42
  }
42
43
 
43
44
  interface LayerXyzModel {
@@ -25,6 +25,10 @@ import WMTS from 'ol/source/WMTS'
25
25
  import { Geometry } from 'ol/geom'
26
26
  import Feature from 'ol/Feature'
27
27
  import { WfsEndpoint, WmtsEndpoint } from '@camptocamp/ogc-client'
28
+ import OGCVectorTile from 'ol/source/OGCVectorTile.js'
29
+ import { MVT } from 'ol/format'
30
+ import VectorTileLayer from 'ol/layer/VectorTile'
31
+ import OGCMapTile from 'ol/source/OGCMapTile.js'
28
32
 
29
33
  export const DEFAULT_BASELAYER_CONTEXT: MapContextLayerXyzModel = {
30
34
  type: MapContextLayerTypeEnum.XYZ,
@@ -78,14 +82,28 @@ export class MapContextService {
78
82
  const style = this.styleService.styles.default
79
83
  switch (type) {
80
84
  case MapContextLayerTypeEnum.OGCAPI:
81
- return new VectorLayer({
82
- source: new VectorSource({
83
- format: new GeoJSON(),
84
- url: layerModel.url,
85
- }),
86
- style,
87
- })
88
-
85
+ if (layerModel.layerType === 'vectorTiles') {
86
+ return new VectorTileLayer({
87
+ source: new OGCVectorTile({
88
+ url: layerModel.url,
89
+ format: new MVT(),
90
+ }),
91
+ })
92
+ } else if (layerModel.layerType === 'mapTiles') {
93
+ return new TileLayer({
94
+ source: new OGCMapTile({
95
+ url: layerModel.url,
96
+ }),
97
+ })
98
+ } else {
99
+ return new VectorLayer({
100
+ source: new VectorSource({
101
+ format: new GeoJSON(),
102
+ url: layerModel.url,
103
+ }),
104
+ style,
105
+ })
106
+ }
89
107
  case MapContextLayerTypeEnum.XYZ:
90
108
  return new TileLayer({
91
109
  source: new XYZ({
@@ -196,7 +196,8 @@ export class MapViewComponent implements OnInit, OnDestroy {
196
196
  } else if (
197
197
  (link.type === 'service' &&
198
198
  (link.accessServiceProtocol === 'wfs' ||
199
- link.accessServiceProtocol === 'esriRest')) ||
199
+ link.accessServiceProtocol === 'esriRest' ||
200
+ link.accessServiceProtocol === 'ogcFeatures')) ||
200
201
  link.type === 'download'
201
202
  ) {
202
203
  return this.dataService.readAsGeoJson(link).pipe(
@@ -8,7 +8,6 @@ import { DatavizConfigurationModel } from '../../../../../../libs/common/domain/
8
8
  import {
9
9
  CatalogRecord,
10
10
  UserFeedback,
11
- UserFeedbackViewModel,
12
11
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
12
  import { AvatarServiceInterface } from '../../../../../../libs/api/repository/src'
14
13
 
@@ -95,6 +95,6 @@ export class DownloadsListComponent {
95
95
  }
96
96
 
97
97
  isFromWfs(link: DatasetDistribution) {
98
- return link.type === 'service' && link.accessServiceProtocol === 'wfs'
98
+ return link.type === 'download' && link.accessServiceProtocol === 'wfs'
99
99
  }
100
100
  }
@@ -1,25 +1,43 @@
1
1
  <a
2
2
  [href]="link.url"
3
3
  target="_blank"
4
- class="flex flex-col justify-between group h-40 grow py-5 px-5 bg-white rounded border-gray-300 filter card-shadow overflow-hidden lg:w-80"
4
+ class="flex flex-col justify-between group grow py-5 px-5 bg-white rounded border-gray-300 filter card-shadow overflow-hidden"
5
+ [ngClass]="{ 'h-40': !compact }"
6
+ [title]="title"
5
7
  >
6
- <div class="max-h-24 overflow-hidden text-ellipsis">
7
- <p
8
- class="font-title font-medium text-21 text-black break-words mb-1 line-clamp-2"
9
- >
10
- {{ link.name }}
11
- </p>
12
- <p class="font-medium text-sm break-words">
13
- {{ link.description }}
14
- </p>
15
- <p
16
- *ngIf="!link.name && !link.description"
17
- class="font-medium text-sm break-words truncate"
18
- >
19
- {{ link.url }}
20
- </p>
21
- </div>
22
- <div>
23
- <mat-icon class="material-symbols-outlined card-icon">open_in_new</mat-icon>
24
- </div>
8
+ <ng-container *ngIf="!compact; else compactTpl">
9
+ <div class="max-h-24 overflow-hidden text-ellipsis">
10
+ <p
11
+ class="font-title font-medium text-21 text-black break-words mb-1 line-clamp-2"
12
+ >
13
+ {{ link.name }}
14
+ </p>
15
+ <p class="font-medium text-sm break-words">
16
+ {{ link.description }}
17
+ </p>
18
+ <p
19
+ *ngIf="!link.name && !link.description"
20
+ class="font-medium text-sm break-words truncate"
21
+ >
22
+ {{ link.url }}
23
+ </p>
24
+ </div>
25
+ <div>
26
+ <mat-icon class="material-symbols-outlined card-icon"
27
+ >open_in_new</mat-icon
28
+ >
29
+ </div>
30
+ </ng-container>
31
+ <ng-template #compactTpl>
32
+ <div class="flex items-center justify-between gap-4">
33
+ <p
34
+ class="overflow-hidden font-title font-medium text-21 text-black text-ellipsis whitespace-nowrap"
35
+ >
36
+ {{ link.name || link.description }}
37
+ </p>
38
+ <mat-icon class="material-symbols-outlined card-icon flex-shrink-0"
39
+ >open_in_new</mat-icon
40
+ >
41
+ </div>
42
+ </ng-template>
25
43
  </a>
@@ -1,12 +1,24 @@
1
1
  import { Component, ChangeDetectionStrategy, Input } from '@angular/core'
2
2
  import { DatasetDistribution } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+ import { MatIconModule } from '@angular/material/icon'
4
+ import { CommonModule } from '@angular/common'
3
5
 
4
6
  @Component({
5
7
  selector: 'gn-ui-link-card',
6
8
  templateUrl: './link-card.component.html',
7
9
  styleUrls: ['./link-card.component.css'],
8
10
  changeDetection: ChangeDetectionStrategy.OnPush,
11
+ standalone: true,
12
+ imports: [CommonModule, MatIconModule],
9
13
  })
10
14
  export class LinkCardComponent {
11
15
  @Input() link: DatasetDistribution
16
+ @Input() compact = false
17
+
18
+ get title() {
19
+ if (this.link.name && this.link.description) {
20
+ return `${this.link.name} | ${this.link.description}`
21
+ }
22
+ return this.link.name || this.link.description || ''
23
+ }
12
24
  }
@@ -17,9 +17,11 @@ const DEFAULT_PARAMS = {
17
17
  })
18
18
  export class RecordApiFormComponent {
19
19
  @Input() set apiLink(value: DatasetServiceDistribution) {
20
- this.apiBaseUrl = value ? value.url.href : undefined
21
20
  this.outputFormats = [{ value: 'json', label: 'JSON' }]
22
- this.parseOutputFormats()
21
+ if (value) {
22
+ this.apiBaseUrl = value.url.href
23
+ this.parseOutputFormats()
24
+ }
23
25
  this.resetUrl()
24
26
  }
25
27
  offset$ = new BehaviorSubject('')
@@ -57,7 +57,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
57
57
  DownloadItemComponent,
58
58
  DownloadsListComponent,
59
59
  ApiCardComponent,
60
- LinkCardComponent,
61
60
  RelatedRecordCardComponent,
62
61
  MetadataContactComponent,
63
62
  MetadataCatalogComponent,
@@ -80,7 +79,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
80
79
  DownloadItemComponent,
81
80
  DownloadsListComponent,
82
81
  ApiCardComponent,
83
- LinkCardComponent,
84
82
  RelatedRecordCardComponent,
85
83
  MetadataContactComponent,
86
84
  MetadataCatalogComponent,
@@ -19,3 +19,4 @@ export * from './lib/text-area/text-area.component'
19
19
  export * from './lib/text-input/text-input.component'
20
20
  export * from './lib/ui-inputs.module'
21
21
  export * from './lib/viewport-intersector/viewport-intersector.component'
22
+ export * from './lib/previous-next-buttons/previous-next-buttons.component'
@@ -0,0 +1,6 @@
1
+ :host {
2
+ --gn-ui-button-rounded: 100%;
3
+ --gn-ui-button-width: 8px;
4
+ --gn-ui-button-height: 8px;
5
+ --gn-ui-button-padding: 12px;
6
+ }
@@ -0,0 +1,26 @@
1
+ <div class="flex flex-row gap-x-4 items-center">
2
+ <gn-ui-button
3
+ data-test="previousButton"
4
+ [type]="isFirst ? 'default' : 'outline'"
5
+ [disabled]="isFirst"
6
+ (buttonClick)="previousButtonClicked()"
7
+ >
8
+ <mat-icon
9
+ class="material-symbols-outlined text-[14px] text-center pt-[5px]"
10
+ >
11
+ arrow_back
12
+ </mat-icon>
13
+ </gn-ui-button>
14
+ <gn-ui-button
15
+ data-test="nextButton"
16
+ [type]="isLast ? 'default' : 'outline'"
17
+ [disabled]="isLast"
18
+ (buttonClick)="nextButtonClicked()"
19
+ >
20
+ <mat-icon
21
+ class="material-symbols-outlined text-[14px] text-center pt-[5px]"
22
+ >
23
+ arrow_forward
24
+ </mat-icon>
25
+ </gn-ui-button>
26
+ </div>
@@ -0,0 +1,32 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ } from '@angular/core'
8
+ import { ButtonComponent } from '../button/button.component'
9
+ import { MatIconModule } from '@angular/material/icon'
10
+
11
+ @Component({
12
+ selector: 'gn-ui-previous-next-buttons',
13
+ templateUrl: './previous-next-buttons.component.html',
14
+ styleUrls: ['./previous-next-buttons.component.css'],
15
+ changeDetection: ChangeDetectionStrategy.OnPush,
16
+ standalone: true,
17
+ imports: [ButtonComponent, MatIconModule],
18
+ })
19
+ export class PreviousNextButtonsComponent {
20
+ @Input() isFirst: boolean
21
+ @Input() isLast: boolean
22
+
23
+ @Output() directionButtonClicked: EventEmitter<string> = new EventEmitter()
24
+
25
+ previousButtonClicked() {
26
+ this.directionButtonClicked.next('previous')
27
+ }
28
+
29
+ nextButtonClicked() {
30
+ this.directionButtonClicked.next('next')
31
+ }
32
+ }
@@ -6,4 +6,5 @@ export * from './lib/form-field-wrapper/form-field-wrapper.component'
6
6
  export * from './lib/interactive-table/interactive-table-column/interactive-table-column.component'
7
7
  export * from './lib/interactive-table/interactive-table.component'
8
8
  export * from './lib/sticky-header/sticky-header.component'
9
+ export * from './lib/block-list/block-list.component'
9
10
  export * from './lib/ui-layout.module'
@@ -0,0 +1,23 @@
1
+ :host .block-list-container ::ng-deep > * {
2
+ flex-shrink: 0;
3
+ }
4
+
5
+ :host {
6
+ position: relative;
7
+ }
8
+
9
+ .list-page-dot {
10
+ width: 6px;
11
+ height: 6px;
12
+ border-radius: 6px;
13
+ position: relative;
14
+ }
15
+
16
+ .list-page-dot:after {
17
+ content: '';
18
+ position: absolute;
19
+ left: -7px;
20
+ top: -7px;
21
+ width: 20px;
22
+ height: 20px;
23
+ }
@@ -0,0 +1,20 @@
1
+ <div
2
+ class="block-list-container flex flex-col"
3
+ #blockContainer
4
+ [ngClass]="containerClass"
5
+ [ngStyle]="{ minHeight: minHeight + 'px' }"
6
+ >
7
+ <ng-content></ng-content>
8
+ </div>
9
+ <div
10
+ *ngIf="pagesCount > 1"
11
+ class="absolute flex flex-row justify-center gap-[14px] p-1"
12
+ [ngClass]="paginationContainerClass"
13
+ >
14
+ <button
15
+ *ngFor="let page of pages"
16
+ class="list-page-dot"
17
+ (click)="goToPage(page)"
18
+ [ngClass]="currentPage === page ? 'bg-primary' : 'bg-gray-400'"
19
+ ></button>
20
+ </div>
@@ -0,0 +1,84 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectionStrategy,
4
+ ChangeDetectorRef,
5
+ Component,
6
+ ContentChildren,
7
+ ElementRef,
8
+ Input,
9
+ QueryList,
10
+ ViewChild,
11
+ } from '@angular/core'
12
+ import { CommonModule } from '@angular/common'
13
+
14
+ @Component({
15
+ selector: 'gn-ui-block-list',
16
+ templateUrl: './block-list.component.html',
17
+ styleUrls: ['./block-list.component.css'],
18
+ changeDetection: ChangeDetectionStrategy.OnPush,
19
+ standalone: true,
20
+ imports: [CommonModule],
21
+ })
22
+ export class BlockListComponent implements AfterViewInit {
23
+ @Input() pageSize = 5
24
+ @Input() containerClass = ''
25
+ @Input() paginationContainerClass = 'w-full bottom-0 top-auto'
26
+ @ContentChildren('block', { read: ElementRef }) blocks: QueryList<
27
+ ElementRef<HTMLElement>
28
+ >
29
+ @ViewChild('blockContainer') blockContainer: ElementRef<HTMLElement>
30
+
31
+ protected minHeight = 0
32
+
33
+ protected currentPage = 0
34
+ protected get pages() {
35
+ return new Array(this.pagesCount).fill(0).map((_, i) => i)
36
+ }
37
+
38
+ get isFirstPage() {
39
+ return this.currentPage === 0
40
+ }
41
+ get isLastPage() {
42
+ return this.currentPage === this.pagesCount - 1
43
+ }
44
+ get pagesCount() {
45
+ return this.blocks ? Math.ceil(this.blocks.length / this.pageSize) : 1
46
+ }
47
+
48
+ constructor(private changeDetector: ChangeDetectorRef) {}
49
+
50
+ ngAfterViewInit() {
51
+ this.blocks.changes.subscribe(this.refreshBlocksVisibility)
52
+ this.refreshBlocksVisibility()
53
+
54
+ // we store the first height as the min-height of the list container
55
+ this.minHeight = this.blockContainer.nativeElement.clientHeight
56
+ this.changeDetector.detectChanges()
57
+ }
58
+
59
+ protected refreshBlocksVisibility = () => {
60
+ this.blocks.forEach((block, index) => {
61
+ block.nativeElement.style.display =
62
+ index >= this.currentPage * this.pageSize &&
63
+ index < (this.currentPage + 1) * this.pageSize
64
+ ? null
65
+ : 'none'
66
+ })
67
+ }
68
+
69
+ public goToPage(index: number) {
70
+ this.currentPage = Math.max(Math.min(index, this.pagesCount - 1), 0)
71
+ this.changeDetector.detectChanges()
72
+ this.refreshBlocksVisibility()
73
+ }
74
+
75
+ public previousPage() {
76
+ if (this.isFirstPage) return
77
+ this.goToPage(this.currentPage - 1)
78
+ }
79
+
80
+ public nextPage() {
81
+ if (this.isLastPage) return
82
+ this.goToPage(this.currentPage + 1)
83
+ }
84
+ }
@@ -1,20 +1,23 @@
1
1
  :host .carousel-container ::ng-deep > * {
2
2
  flex-shrink: 0;
3
3
  }
4
+
4
5
  :host {
5
6
  position: relative;
6
7
  }
8
+
7
9
  .carousel-step-dot {
8
10
  width: 6px;
9
11
  height: 6px;
10
12
  border-radius: 6px;
11
13
  position: relative;
12
14
  }
15
+
13
16
  .carousel-step-dot:after {
14
17
  content: '';
15
18
  position: absolute;
16
- left: -4px;
17
- top: -4px;
18
- width: 14px;
19
- height: 14px;
19
+ left: -7px;
20
+ top: -7px;
21
+ width: 20px;
22
+ height: 20px;
20
23
  }
@@ -1,17 +1,17 @@
1
- <div #carouselOverflowContainer class="overflow-hidden h-full w-full">
2
- <div class="carousel-container flex flex-row" [ngClass]="containerClass">
1
+ <div #carouselOverflowContainer class="w-full">
2
+ <div class="carousel-container flex" [ngClass]="containerClass">
3
3
  <ng-content></ng-content>
4
4
  </div>
5
5
  </div>
6
6
  <div
7
7
  *ngIf="steps.length > 1"
8
- class="absolute right-0 top-0 flex flex-row justify-center gap-[10px] p-1"
8
+ class="absolute flex flex-row justify-center gap-[14px] p-1"
9
9
  [ngClass]="stepsContainerClass"
10
10
  >
11
11
  <button
12
12
  *ngFor="let step of steps; let i = index"
13
13
  class="carousel-step-dot"
14
14
  (click)="scrollToStep(i)"
15
- [ngClass]="selectedStep === i ? 'bg-secondary' : 'bg-gray-400'"
15
+ [ngClass]="currentStep === i ? 'bg-primary' : 'bg-gray-400'"
16
16
  ></button>
17
17
  </div>