From b799e33db3a1ebe9543300d925f1838b8778d6dd Mon Sep 17 00:00:00 2001
From: Olivia Guyot
Date: Mon, 9 Dec 2024 17:54:37 +0100
Subject: [PATCH] feat(layout): use pagination controls in components with
lists
---
.../src/e2e/datasetDetailPage.cy.ts | 4 +-
.../organization-details.component.html | 21 ++---
.../organization-details.component.spec.ts | 58 ++-----------
.../organization-details.component.ts | 83 ++++++++-----------
.../record-apis/record-apis.component.html | 12 +--
.../record-apis/record-apis.component.ts | 26 +-----
.../record-otherlinks.component.html | 6 +-
.../record-otherlinks.component.ts | 33 ++------
.../app/records/records-list.component.html | 6 +-
.../records/records-list.component.spec.ts | 7 +-
.../src/app/records/records-list.component.ts | 44 ++++++++--
.../organisations.component.html | 6 +-
.../organisations.component.spec.ts | 24 ++----
.../organisations/organisations.component.ts | 35 ++++++--
.../search/src/lib/state/search.facade.ts | 8 --
.../search/src/lib/state/selectors.spec.ts | 78 -----------------
.../feature/search/src/lib/state/selectors.ts | 18 ----
libs/ui/layout/src/index.ts | 1 +
.../lib/block-list/block-list.component.css | 16 ----
.../lib/block-list/block-list.component.html | 16 +---
.../block-list/block-list.component.spec.ts | 14 ++--
.../block-list.component.stories.ts | 2 +-
.../lib/block-list/block-list.component.ts | 31 ++++---
.../src/lib/carousel/carousel.component.html | 16 +---
.../lib/carousel/carousel.component.spec.ts | 22 ++---
.../src/lib/carousel/carousel.component.ts | 41 ++++-----
26 files changed, 212 insertions(+), 416 deletions(-)
diff --git a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts
index 088b06fb4e..ec2ee577dc 100644
--- a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts
+++ b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts
@@ -529,12 +529,12 @@ describe('dataset pages', () => {
})
it('should not display carousel dot button for 4 link cards', () => {
cy.get('datahub-record-otherlinks')
- .find('.carousel-step-dot')
+ .find('.pagination-dot')
.should('exist')
})
it('should not display carousel dot button for 2 API cards', () => {
cy.get('datahub-record-apis')
- .find('.carousel-step-dot')
+ .find('.pagination-dot')
.should('not.exist')
})
})
diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.html b/apps/datahub/src/app/organization/organization-details/organization-details.component.html
index 6c27e7f32c..68d4b78084 100644
--- a/apps/datahub/src/app/organization/organization-details/organization-details.component.html
+++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.html
@@ -77,9 +77,7 @@
- 1"
- class="flex flex-row justify-center gap-[14px] p-1 mx-auto"
- [ngClass]="paginationContainerClass"
+
-
-
+
diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts
index 339dec1b85..4d43c8f2ac 100644
--- a/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts
+++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.spec.ts
@@ -1,9 +1,4 @@
-import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- DebugElement,
- NO_ERRORS_SCHEMA,
-} from '@angular/core'
+import { ChangeDetectionStrategy, DebugElement } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SearchFacade } from '@geonetwork-ui/feature/search'
import { TranslateModule } from '@ngx-translate/core'
@@ -14,22 +9,6 @@ import {
datasetRecordsFixture,
someOrganizationsFixture,
} from '@geonetwork-ui/common/fixtures'
-import { AsyncPipe, NgForOf, NgIf } from '@angular/common'
-import {
- ButtonComponent,
- PreviousNextButtonsComponent,
-} from '@geonetwork-ui/ui/inputs'
-import {
- BlockListComponent,
- CarouselComponent,
- MaxLinesComponent,
-} from '@geonetwork-ui/ui/layout'
-import { LetDirective } from '@ngrx/component'
-import { LinkCardComponent, UiElementsModule } from '@geonetwork-ui/ui/elements'
-import { UiSearchModule } from '@geonetwork-ui/ui/search'
-import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz'
-import { RouterLink } from '@angular/router'
-import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets'
import { Organization } from '@geonetwork-ui/common/domain/model/record'
import { RouterTestingModule } from '@angular/router/testing'
import { By } from '@angular/platform-browser'
@@ -37,10 +16,6 @@ import { ROUTER_ROUTE_SEARCH } from '@geonetwork-ui/feature/router'
let getHTMLElement: (dataTest: string) => HTMLElement | undefined
-const changeDetectorRefMock: Partial = {
- markForCheck: jest.fn(),
-}
-
class OrganisationsServiceMock {
getFiltersForOrgs = jest.fn((orgs) =>
of({
@@ -58,7 +33,7 @@ const manyDatasets = datasetRecordsFixture().concat(datasetRecordsFixture()[0])
const organizationIsLoading = new BehaviorSubject(false)
const totalPages = new BehaviorSubject(10)
-const currentPage = new BehaviorSubject(0)
+const currentPage = new BehaviorSubject(1)
const results = new BehaviorSubject(manyDatasets)
const desiredPageSize = 3
@@ -72,11 +47,9 @@ class SearchFacadeMock {
results$ = results.asObservable()
isLoading$ = organizationIsLoading.asObservable()
totalPages$ = totalPages.asObservable()
- isBeginningOfResults$ = of(currentPage.getValue() === 1)
- isEndOfResults$ = of(totalPages.getValue() === currentPage.getValue())
currentPage$ = currentPage.asObservable()
- paginate = jest.fn(() => {
- currentPage.next(currentPage.getValue() + 1)
+ paginate = jest.fn((newPage) => {
+ currentPage.next(newPage)
return new SearchFacadeMock()
})
}
@@ -89,25 +62,8 @@ describe('OrganizationDetailsComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [],
- schemas: [NO_ERRORS_SCHEMA],
imports: [
- AsyncPipe,
- NgIf,
- ButtonComponent,
- TranslateModule,
- CarouselComponent,
- BlockListComponent,
- LetDirective,
- LinkCardComponent,
- NgForOf,
- PreviousNextButtonsComponent,
- UiElementsModule,
- UiSearchModule,
- MaxLinesComponent,
- UiDatavizModule,
- RouterLink,
- UiWidgetsModule,
+ OrganizationDetailsComponent,
TranslateModule.forRoot(),
RouterTestingModule,
],
@@ -120,10 +76,6 @@ describe('OrganizationDetailsComponent', () => {
provide: SearchFacade,
useClass: SearchFacadeMock,
},
- {
- provide: ChangeDetectorRef,
- useValue: changeDetectorRefMock,
- },
],
})
.overrideComponent(OrganizationDetailsComponent, {
diff --git a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts
index a089aca958..17245bf9f0 100644
--- a/apps/datahub/src/app/organization/organization-details/organization-details.component.ts
+++ b/apps/datahub/src/app/organization/organization-details/organization-details.component.ts
@@ -4,28 +4,23 @@ import {
Input,
OnDestroy,
OnInit,
- ViewChild,
} from '@angular/core'
import { CommonModule } from '@angular/common'
import {
CatalogRecord,
Organization,
} from '@geonetwork-ui/common/domain/model/record'
-import {
- ButtonComponent,
- PreviousNextButtonsComponent,
-} from '@geonetwork-ui/ui/inputs'
import { TranslateModule } from '@ngx-translate/core'
import {
- BlockListComponent,
- CarouselComponent,
MaxLinesComponent,
+ Paginable,
+ PaginationDotsComponent,
+ PreviousNextButtonsComponent,
} from '@geonetwork-ui/ui/layout'
import { LetDirective } from '@ngrx/component'
import {
ErrorComponent,
ErrorType,
- LinkCardComponent,
RelatedRecordCardComponent,
UiElementsModule,
} from '@geonetwork-ui/ui/elements'
@@ -57,12 +52,8 @@ import { startWith } from 'rxjs/operators'
standalone: true,
imports: [
CommonModule,
- ButtonComponent,
TranslateModule,
- CarouselComponent,
- BlockListComponent,
LetDirective,
- LinkCardComponent,
PreviousNextButtonsComponent,
UiElementsModule,
UiSearchModule,
@@ -73,33 +64,25 @@ import { startWith } from 'rxjs/operators'
ErrorComponent,
SpinningLoaderComponent,
RelatedRecordCardComponent,
+ PaginationDotsComponent,
],
})
-export class OrganizationDetailsComponent implements OnInit, OnDestroy {
+export class OrganizationDetailsComponent
+ implements OnInit, OnDestroy, Paginable
+{
protected readonly ErrorType = ErrorType
protected readonly ROUTER_ROUTE_SEARCH = ROUTER_ROUTE_SEARCH
- protected get pages() {
- return new Array(this.totalPages).fill(0).map((_, i) => i + 1)
- }
-
subscriptions$: Subscription = new Subscription()
isSearchFacadeLoading = true
- totalPages = 0
- currentPage = 1
- isFirstPage = this.currentPage === 1
- isLastPage = false
-
currentOrganization$ = new BehaviorSubject(null)
@Input() set organization(value: Organization) {
this.currentOrganization$.next(value)
}
@Input() paginationContainerClass = 'w-full bottom-0 top-auto'
- @ViewChild(BlockListComponent) list: BlockListComponent
-
lastPublishedDatasets$: Observable =
this.currentOrganization$.pipe(
switchMap((organization) =>
@@ -129,17 +112,31 @@ export class OrganizationDetailsComponent implements OnInit, OnDestroy {
}
get hasPagination() {
- return this.totalPages > 1
+ return this.pagesCount > 1
}
- changeStepOrPage(direction: string) {
- if (direction === 'next') {
- this.searchFacade.paginate(this.currentPage + 1)
- } else {
- this.searchFacade.paginate(this.currentPage - 1)
- }
- }
+ pagesCount_ = 0
+ currentPage_ = 1
+ // Paginable API
+ get currentPage() {
+ return this.currentPage_
+ }
+ get pagesCount() {
+ return this.pagesCount_
+ }
+ get isFirstPage() {
+ return this.currentPage === 1
+ }
+ get isLastPage() {
+ return this.currentPage === this.pagesCount
+ }
+ goToPrevPage() {
+ this.searchFacade.paginate(this.currentPage - 1)
+ }
+ goToNextPage() {
+ this.searchFacade.paginate(this.currentPage + 1)
+ }
goToPage(page: number) {
this.searchFacade.paginate(page)
}
@@ -149,24 +146,12 @@ export class OrganizationDetailsComponent implements OnInit, OnDestroy {
combineLatest([
this.searchFacade.isLoading$.pipe(distinctUntilChanged()),
this.searchFacade.totalPages$.pipe(distinctUntilChanged()),
- this.searchFacade.isBeginningOfResults$.pipe(distinctUntilChanged()),
- this.searchFacade.isEndOfResults$.pipe(distinctUntilChanged()),
this.searchFacade.currentPage$.pipe(distinctUntilChanged()),
- ]).subscribe(
- ([
- isSearchFacadeLoading,
- totalPages,
- isBeginningOfResults,
- isEndOfResults,
- currentPage,
- ]) => {
- this.isSearchFacadeLoading = isSearchFacadeLoading
- this.totalPages = totalPages
- this.isFirstPage = isBeginningOfResults
- this.isLastPage = isEndOfResults
- this.currentPage = currentPage
- }
- )
+ ]).subscribe(([isSearchFacadeLoading, totalPages, currentPage]) => {
+ this.isSearchFacadeLoading = isSearchFacadeLoading
+ this.pagesCount_ = totalPages
+ this.currentPage_ = currentPage
+ })
)
}
diff --git a/apps/datahub/src/app/record/record-apis/record-apis.component.html b/apps/datahub/src/app/record/record-apis/record-apis.component.html
index dd22ded982..3ad41c9fba 100644
--- a/apps/datahub/src/app/record/record-apis/record-apis.component.html
+++ b/apps/datahub/src/app/record/record-apis/record-apis.component.html
@@ -6,13 +6,15 @@
record.metadata.api
1"
+ [listComponent]="carousel"
>
-
+
1
- }
-
updateView() {
this.changeDetector.detectChanges()
}
- get isFirstStep() {
- return this.carousel?.isFirstStep
- }
-
- get isLastStep() {
- return this.carousel?.isLastStep
- }
-
openRecordApiForm(link: DatasetServiceDistribution) {
this.selectedApiLink = link
this.setStyle(link)
@@ -88,12 +78,4 @@ export class RecordApisComponent implements OnInit {
this.maxHeight = link === undefined ? '0px' : '500px'
this.opacity = link === undefined ? 0 : 1
}
-
- changeStepOrPage(direction: string) {
- if (direction === 'next') {
- this.carousel?.slideToNext()
- } else {
- this.carousel?.slideToPrevious()
- }
- }
}
diff --git a/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.html b/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.html
index b97bc8a202..90ca03c0d7 100644
--- a/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.html
+++ b/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.html
@@ -6,10 +6,8 @@
record.metadata.links
1"
+ [listComponent]="paginableElement"
>
diff --git a/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.ts b/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.ts
index cef2c555c7..799d6dea65 100644
--- a/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.ts
+++ b/apps/datahub/src/app/record/record-otherlinks/record-otherlinks.component.ts
@@ -6,8 +6,12 @@ import {
ViewChild,
} from '@angular/core'
import { MdViewFacade } from '@geonetwork-ui/feature/record'
-import { BlockListComponent, CarouselComponent } from '@geonetwork-ui/ui/layout'
-import { PreviousNextButtonsComponent } from '@geonetwork-ui/ui/inputs'
+import {
+ BlockListComponent,
+ CarouselComponent,
+ Paginable,
+ PreviousNextButtonsComponent,
+} from '@geonetwork-ui/ui/layout'
import { CommonModule } from '@angular/common'
import { LinkCardComponent } from '@geonetwork-ui/ui/elements'
import { LetDirective } from '@ngrx/component'
@@ -34,34 +38,15 @@ export class RecordOtherlinksComponent implements AfterViewInit {
@ViewChild(CarouselComponent) carousel: CarouselComponent
@ViewChild(BlockListComponent) list: BlockListComponent
+ get paginableElement(): Paginable {
+ return this.carousel || this.list
+ }
constructor(
public facade: MdViewFacade,
private changeDetector: ChangeDetectorRef
) {}
- get isFirstStepOrPage() {
- return this.carousel?.isFirstStep ?? this.list?.isFirstPage ?? true
- }
-
- get isLastStepOrPage() {
- return this.carousel?.isLastStep ?? this.list?.isLastPage ?? false
- }
-
- get hasPagination() {
- return (this.carousel?.stepsCount || this.list?.pagesCount) > 1
- }
-
- changeStepOrPage(direction: string) {
- if (direction === 'next') {
- this.list?.nextPage()
- this.carousel?.slideToNext()
- } else {
- this.carousel?.slideToPrevious()
- this.list?.previousPage()
- }
- }
-
updateView() {
this.changeDetector.detectChanges()
}
diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html
index 215e2e950f..b5be0a5672 100644
--- a/apps/metadata-editor/src/app/records/records-list.component.html
+++ b/apps/metadata-editor/src/app/records/records-list.component.html
@@ -7,10 +7,6 @@
>
-
+
diff --git a/apps/metadata-editor/src/app/records/records-list.component.spec.ts b/apps/metadata-editor/src/app/records/records-list.component.spec.ts
index 2a277e8594..f7e8f9a501 100644
--- a/apps/metadata-editor/src/app/records/records-list.component.spec.ts
+++ b/apps/metadata-editor/src/app/records/records-list.component.spec.ts
@@ -10,7 +10,7 @@ import { Router } from '@angular/router'
import { BehaviorSubject } from 'rxjs'
import { datasetRecordsFixture } from '@geonetwork-ui/common/fixtures'
import { MockBuilder } from 'ng-mocks'
-import { PaginationButtonsComponent } from '@geonetwork-ui/ui/elements'
+import { PaginationButtonsComponent } from '@geonetwork-ui/ui/layout'
const results = [{ md: true }]
const currentPage = 5
@@ -97,8 +97,7 @@ describe('RecordsListComponent', () => {
})
it('displays pagination', () => {
expect(pagination).toBeTruthy()
- expect(pagination.currentPage).toEqual(currentPage)
- expect(pagination.totalPages).toEqual(totalPages)
+ expect(pagination.listComponent).toBe(component)
})
describe('when click on a record', () => {
const uniqueIdentifier = 123
@@ -128,7 +127,7 @@ describe('RecordsListComponent', () => {
})
describe('when click on pagination', () => {
beforeEach(() => {
- pagination.newCurrentPageEvent.emit(3)
+ component.goToPage(3)
})
it('paginates', () => {
expect(searchService.setPage).toHaveBeenCalledWith(3)
diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts
index 8f53f6d992..958b943f88 100644
--- a/apps/metadata-editor/src/app/records/records-list.component.ts
+++ b/apps/metadata-editor/src/app/records/records-list.component.ts
@@ -8,12 +8,10 @@ import {
SearchService,
} from '@geonetwork-ui/feature/search'
import { UiSearchModule } from '@geonetwork-ui/ui/search'
-import {
- PaginationButtonsComponent,
- UiElementsModule,
-} from '@geonetwork-ui/ui/elements'
+import { UiElementsModule } from '@geonetwork-ui/ui/elements'
import { TranslateModule } from '@ngx-translate/core'
import { UiInputsModule } from '@geonetwork-ui/ui/inputs'
+import { Paginable, PaginationButtonsComponent } from '@geonetwork-ui/ui/layout'
export const allSearchFields = [
'uuid',
@@ -41,7 +39,7 @@ export const allSearchFields = [
PaginationButtonsComponent,
],
})
-export class RecordsListComponent implements OnInit {
+export class RecordsListComponent implements OnInit, Paginable {
constructor(
private router: Router,
public searchFacade: SearchFacade,
@@ -51,10 +49,13 @@ export class RecordsListComponent implements OnInit {
ngOnInit(): void {
this.searchFacade.setConfigRequestFields(allSearchFields)
this.searchFacade.setPageSize(15)
- }
- paginate(page: number) {
- this.searchService.setPage(page)
+ this.searchFacade.currentPage$.subscribe((page) => {
+ this.currentPage_ = page
+ })
+ this.searchFacade.totalPages$.subscribe((total) => {
+ this.totalPages_ = total
+ })
}
editRecord(record: CatalogRecord) {
@@ -64,4 +65,31 @@ export class RecordsListComponent implements OnInit {
duplicateRecord(record: CatalogRecord) {
this.router.navigate(['/duplicate', record.uniqueIdentifier])
}
+
+ // these are 0 based
+ totalPages_: number
+ currentPage_: number
+
+ // Paginable API
+ get isFirstPage() {
+ return this.currentPage_ === 1
+ }
+ get isLastPage() {
+ return this.currentPage_ === this.totalPages_
+ }
+ get pagesCount() {
+ return this.totalPages_
+ }
+ get currentPage() {
+ return this.currentPage_
+ }
+ goToPage(page: number) {
+ this.searchService.setPage(page)
+ }
+ goToNextPage() {
+ this.searchService.setPage(this.currentPage_ + 1)
+ }
+ goToPrevPage() {
+ this.searchService.setPage(this.currentPage_ - 1)
+ }
}
diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.html b/libs/feature/catalog/src/lib/organisations/organisations.component.html
index 3d253d16f3..0e6e6c1c2b 100644
--- a/libs/feature/catalog/src/lib/organisations/organisations.component.html
+++ b/libs/feature/catalog/src/lib/organisations/organisations.component.html
@@ -26,9 +26,5 @@
-
+
diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts b/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts
index 8844d1ccb0..ea9ed82c75 100644
--- a/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts
+++ b/libs/feature/catalog/src/lib/organisations/organisations.component.spec.ts
@@ -15,7 +15,6 @@ import {
OrganisationsFilterComponent,
OrganisationsResultComponent,
} from '@geonetwork-ui/ui/catalog'
-import { PaginationComponent } from '@geonetwork-ui/ui/elements'
class OrganisationsServiceMock {
organisations$ = of(someOrganizationsFixture())
@@ -67,12 +66,7 @@ describe('OrganisationsComponent', () => {
describe('on component init', () => {
let orgPreviewComponents: OrganisationPreviewComponent[]
let orgResultComponent: OrganisationsResultComponent
- let paginationComponentDE: DebugElement
- let setCurrentPageSpy
let setSortBySpy
- beforeEach(() => {
- paginationComponentDE = de.query(By.directive(PaginationComponent))
- })
describe('pass organisations to ui preview components', () => {
beforeEach(() => {
orgPreviewComponents = de
@@ -88,17 +82,16 @@ describe('OrganisationsComponent', () => {
})
describe('pass params to ui pagination component', () => {
it('should init ui pagination component with currentPage = 1', () => {
- expect(paginationComponentDE.componentInstance.currentPage).toEqual(1)
+ expect(component.currentPage).toEqual(1)
})
it('should init ui pagination component with correct value for total nPages', () => {
- expect(paginationComponentDE.componentInstance.nPages).toEqual(
+ expect(component.pagesCount).toEqual(
Math.ceil(someOrganizationsFixture().length / ITEMS_ON_PAGE)
)
})
- describe('navigate to second page (and trigger newCurrentPageEvent output)', () => {
+ describe('navigate to second page', () => {
beforeEach(() => {
- setCurrentPageSpy = jest.spyOn(component, 'setCurrentPage')
- paginationComponentDE.triggerEventHandler('newCurrentPageEvent', 2)
+ component.goToPage(2)
fixture.detectChanges()
orgPreviewComponents = de
.queryAll(By.directive(OrganisationPreviewComponent))
@@ -107,11 +100,8 @@ describe('OrganisationsComponent', () => {
afterEach(() => {
jest.restoreAllMocks()
})
- it('should call setcurrentPage() with correct value', () => {
- expect(setCurrentPageSpy).toHaveBeenCalledWith(2)
- })
it('should set currentPage in ui component to correct value', () => {
- expect(paginationComponentDE.componentInstance.currentPage).toEqual(2)
+ expect(component.currentPage).toEqual(2)
})
it('should pass first organisation of second page (sorted by name-asc) to first ui preview component', () => {
expect(orgPreviewComponents[0].organization.name).toEqual(
@@ -127,12 +117,12 @@ describe('OrganisationsComponent', () => {
it('should not change currentPage when sorting results', () => {
component['setSortBy'](['desc', 'recordCount'])
fixture.detectChanges()
- expect(paginationComponentDE.componentInstance.currentPage).toEqual(2)
+ expect(component.currentPage).toEqual(2)
})
it('should set currentPage to 1 when filtering to display results', () => {
component['setFilterBy']('Data')
fixture.detectChanges()
- expect(paginationComponentDE.componentInstance.currentPage).toEqual(1)
+ expect(component.currentPage).toEqual(1)
})
})
})
diff --git a/libs/feature/catalog/src/lib/organisations/organisations.component.ts b/libs/feature/catalog/src/lib/organisations/organisations.component.ts
index 565a631c68..e7517f6f1e 100644
--- a/libs/feature/catalog/src/lib/organisations/organisations.component.ts
+++ b/libs/feature/catalog/src/lib/organisations/organisations.component.ts
@@ -14,16 +14,14 @@ import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/orga
import { SortByField } from '@geonetwork-ui/common/domain/model/search'
import { createFuzzyFilter } from '@geonetwork-ui/util/shared'
import { ORGANIZATION_PAGE_URL_TOKEN } from '../organization-url.token'
-import {
- ContentGhostComponent,
- PaginationComponent,
-} from '@geonetwork-ui/ui/elements'
+import { ContentGhostComponent } from '@geonetwork-ui/ui/elements'
import { CommonModule } from '@angular/common'
import {
OrganisationPreviewComponent,
OrganisationsFilterComponent,
OrganisationsResultComponent,
} from '@geonetwork-ui/ui/catalog'
+import { Paginable, PaginationComponent } from '@geonetwork-ui/ui/layout'
@Component({
selector: 'gn-ui-organisations',
@@ -40,7 +38,7 @@ import {
PaginationComponent,
],
})
-export class OrganisationsComponent {
+export class OrganisationsComponent implements Paginable {
@Input() itemsOnPage = 12
@Output() orgSelect = new EventEmitter()
@@ -89,10 +87,6 @@ export class OrganisationsComponent {
)
)
- protected setCurrentPage(page: number): void {
- this.currentPage$.next(page)
- }
-
protected setFilterBy(value: string): void {
this.currentPage$.next(1)
this.filterBy$.next(value)
@@ -140,4 +134,27 @@ export class OrganisationsComponent {
if (!this.urlTemplate) return null
return this.urlTemplate.replace('${name}', organisation.name)
}
+
+ // Paginable API
+ get isFirstPage() {
+ return this.currentPage === 1
+ }
+ get isLastPage() {
+ return this.currentPage === this.totalPages
+ }
+ get pagesCount() {
+ return this.totalPages
+ }
+ get currentPage() {
+ return this.currentPage$.value
+ }
+ goToPage(index: number) {
+ this.currentPage$.next(index)
+ }
+ goToNextPage() {
+ this.goToPage(this.currentPage + 1)
+ }
+ goToPrevPage() {
+ this.goToPage(this.currentPage - 1)
+ }
}
diff --git a/libs/feature/search/src/lib/state/search.facade.ts b/libs/feature/search/src/lib/state/search.facade.ts
index 44c9c34e1e..1c03fb0848 100644
--- a/libs/feature/search/src/lib/state/search.facade.ts
+++ b/libs/feature/search/src/lib/state/search.facade.ts
@@ -38,8 +38,6 @@ import {
getSearchResultsLoading,
getSearchSortBy,
getSpatialFilterEnabled,
- isBeginningOfResults,
- isEndOfResults,
totalPages,
} from './selectors'
import { FILTER_GEOMETRY } from '../filter-geometry.token'
@@ -60,8 +58,6 @@ export class SearchFacade {
layout$: Observable
sortBy$: Observable
isLoading$: Observable
- isBeginningOfResults$: Observable
- isEndOfResults$: Observable
totalPages$: Observable
currentPage$: Observable
pageSize$: Observable
@@ -101,10 +97,6 @@ export class SearchFacade {
this.isLoading$ = this.store.pipe(select(getSearchResultsLoading, searchId))
this.searchFilters$ = this.store.pipe(select(getSearchFilters, searchId))
this.resultsHits$ = this.store.pipe(select(getSearchResultsHits, searchId))
- this.isBeginningOfResults$ = this.store.pipe(
- select(isBeginningOfResults, searchId)
- )
- this.isEndOfResults$ = this.store.pipe(select(isEndOfResults, searchId))
this.totalPages$ = this.store.pipe(select(totalPages, searchId))
this.currentPage$ = this.store.pipe(select(currentPage, searchId))
this.pageSize$ = this.store.pipe(select(getPageSize, searchId))
diff --git a/libs/feature/search/src/lib/state/selectors.spec.ts b/libs/feature/search/src/lib/state/selectors.spec.ts
index 4f610fc05d..210f3dd5ce 100644
--- a/libs/feature/search/src/lib/state/selectors.spec.ts
+++ b/libs/feature/search/src/lib/state/selectors.spec.ts
@@ -76,84 +76,6 @@ describe('Search Selectors', () => {
})
})
- describe('isBeginningOfResults', () => {
- it('should return true once at the beginning of results list', () => {
- const beginningResult = fromSelectors.isBeginningOfResults.projector({
- ...initialStateSearch,
- params: {
- ...initialStateSearch.params,
- currentPage: 0,
- pageSize: 20,
- },
- results: {
- ...initialStateSearch.results,
- count: 62,
- },
- })
- expect(beginningResult).toEqual(true)
-
- const notBeginningResult = fromSelectors.isBeginningOfResults.projector({
- ...initialStateSearch,
- params: {
- ...initialStateSearch.params,
- currentPage: 3,
- pageSize: 20,
- },
- results: {
- ...initialStateSearch.results,
- count: 62,
- },
- })
- expect(notBeginningResult).toEqual(false)
- })
- })
-
- describe('isEndOfResults', () => {
- it('should return true once at the end of results list', () => {
- const result = fromSelectors.isEndOfResults.projector({
- ...initialStateSearch,
- params: {
- ...initialStateSearch.params,
- currentPage: 0,
- pageSize: 20,
- },
- results: {
- ...initialStateSearch.results,
- count: 62,
- },
- })
- expect(result).toEqual(false)
-
- const endResult = fromSelectors.isEndOfResults.projector({
- ...initialStateSearch,
- params: {
- ...initialStateSearch.params,
- currentPage: 3,
- pageSize: 20,
- },
- results: {
- ...initialStateSearch.results,
- count: 62,
- },
- })
- expect(endResult).toEqual(true)
-
- const exactEndOfResult = fromSelectors.isEndOfResults.projector({
- ...initialStateSearch,
- params: {
- ...initialStateSearch.params,
- currentPage: 2,
- pageSize: 20,
- },
- results: {
- ...initialStateSearch.results,
- count: 60,
- },
- })
- expect(exactEndOfResult).toEqual(true)
- })
- })
-
describe('totalPages', () => {
it('returns correct page amount', () => {
const result = fromSelectors.totalPages.projector({
diff --git a/libs/feature/search/src/lib/state/selectors.ts b/libs/feature/search/src/lib/state/selectors.ts
index b2215ba3fc..338ee281cb 100644
--- a/libs/feature/search/src/lib/state/selectors.ts
+++ b/libs/feature/search/src/lib/state/selectors.ts
@@ -50,24 +50,6 @@ export const getSearchResultsHits = createSelector(
(state: SearchStateSearch) => state.results.count
)
-export const isBeginningOfResults = createSelector(
- getSearchStateSearch,
- (state: SearchStateSearch) => {
- return state.params.currentPage === 0
- }
-)
-
-export const isEndOfResults = createSelector(
- getSearchStateSearch,
- (state: SearchStateSearch) => {
- return (
- state.params.currentPage * state.params.pageSize +
- state.params.pageSize >=
- state.results.count
- )
- }
-)
-
export const totalPages = createSelector(
getSearchStateSearch,
(state: SearchStateSearch) => {
diff --git a/libs/ui/layout/src/index.ts b/libs/ui/layout/src/index.ts
index 8233c4e3f1..c8e05e4498 100644
--- a/libs/ui/layout/src/index.ts
+++ b/libs/ui/layout/src/index.ts
@@ -12,6 +12,7 @@ export * from './lib/sortable-list/sortable-list.component'
export * from './lib/modal-dialog/modal-dialog.component'
export * from './lib/pagination/pagination.component'
export * from './lib/pagination-buttons/pagination-buttons.component'
+export * from './lib/pagination-dots/pagination-dots.component'
export * from './lib/previous-next-buttons/previous-next-buttons.component'
export * from './lib/paginable.interface'
export * from './lib/ui-layout.module'
diff --git a/libs/ui/layout/src/lib/block-list/block-list.component.css b/libs/ui/layout/src/lib/block-list/block-list.component.css
index 0d37f18585..c0add777fc 100644
--- a/libs/ui/layout/src/lib/block-list/block-list.component.css
+++ b/libs/ui/layout/src/lib/block-list/block-list.component.css
@@ -5,19 +5,3 @@
:host {
position: relative;
}
-
-.list-page-dot {
- width: 6px;
- height: 6px;
- border-radius: 6px;
- position: relative;
-}
-
-.list-page-dot:after {
- content: '';
- position: absolute;
- left: -7px;
- top: -7px;
- width: 20px;
- height: 20px;
-}
diff --git a/libs/ui/layout/src/lib/block-list/block-list.component.html b/libs/ui/layout/src/lib/block-list/block-list.component.html
index 17e706ddd6..ca420ca667 100644
--- a/libs/ui/layout/src/lib/block-list/block-list.component.html
+++ b/libs/ui/layout/src/lib/block-list/block-list.component.html
@@ -6,15 +6,7 @@
>
- 1"
- class="absolute flex flex-row justify-center gap-[14px] p-1"
- [ngClass]="paginationContainerClass"
->
-
-
+
diff --git a/libs/ui/layout/src/lib/block-list/block-list.component.spec.ts b/libs/ui/layout/src/lib/block-list/block-list.component.spec.ts
index 2b28278dde..e3a945d87a 100644
--- a/libs/ui/layout/src/lib/block-list/block-list.component.spec.ts
+++ b/libs/ui/layout/src/lib/block-list/block-list.component.spec.ts
@@ -56,7 +56,7 @@ describe('BlockListComponent', () => {
})
describe('click on step', () => {
beforeEach(() => {
- component.goToPage(1)
+ component.goToPage(2)
})
it('updates visibility', () => {
const blocksVisibility = blockEls.map(
@@ -73,7 +73,7 @@ describe('BlockListComponent', () => {
])
})
it('emits the selected page', () => {
- expect(component['currentPage']).toEqual(1)
+ expect(component['currentPage']).toEqual(2)
})
})
describe('custom page size', () => {
@@ -103,7 +103,7 @@ describe('BlockListComponent', () => {
beforeEach(() => {
component.pageSize = 2
component.goToPage(2)
- component.previousPage()
+ component.goToPrevPage()
})
it('changes to previous page', () => {
expect(component['currentPage']).toEqual(1)
@@ -114,7 +114,7 @@ describe('BlockListComponent', () => {
beforeEach(() => {
component.pageSize = 2
component.goToPage(1)
- component.nextPage()
+ component.goToNextPage()
})
it('changes to next page', () => {
expect(component['currentPage']).toEqual(2)
@@ -129,7 +129,7 @@ describe('BlockListComponent', () => {
expect(component.isFirstPage).toBe(true)
})
it('returns false if the current page is not the first one', () => {
- component.goToPage(1)
+ component.goToPage(2)
expect(component.isFirstPage).toBe(false)
})
})
@@ -139,11 +139,11 @@ describe('BlockListComponent', () => {
component.pageSize = 3
})
it('returns true if the current page is the last one', () => {
- component.goToPage(2)
+ component.goToPage(3)
expect(component.isLastPage).toBe(true)
})
it('returns false if the current page is not the last one', () => {
- component.goToPage(1)
+ component.goToPage(2)
expect(component.isLastPage).toBe(false)
})
})
diff --git a/libs/ui/layout/src/lib/block-list/block-list.component.stories.ts b/libs/ui/layout/src/lib/block-list/block-list.component.stories.ts
index 31f06fdd74..7e921a6115 100644
--- a/libs/ui/layout/src/lib/block-list/block-list.component.stories.ts
+++ b/libs/ui/layout/src/lib/block-list/block-list.component.stories.ts
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/angular'
-import { BlockListComponent } from './block-list.component'
import { componentWrapperDecorator } from '@storybook/angular'
+import { BlockListComponent } from './block-list.component'
const meta: Meta = {
component: BlockListComponent,
diff --git a/libs/ui/layout/src/lib/block-list/block-list.component.ts b/libs/ui/layout/src/lib/block-list/block-list.component.ts
index 5a0937c761..8a89f27eea 100644
--- a/libs/ui/layout/src/lib/block-list/block-list.component.ts
+++ b/libs/ui/layout/src/lib/block-list/block-list.component.ts
@@ -10,6 +10,8 @@ import {
ViewChild,
} from '@angular/core'
import { CommonModule } from '@angular/common'
+import { Paginable } from '../paginable.interface'
+import { PaginationDotsComponent } from '../pagination-dots/pagination-dots.component'
@Component({
selector: 'gn-ui-block-list',
@@ -17,9 +19,9 @@ import { CommonModule } from '@angular/common'
styleUrls: ['./block-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [CommonModule],
+ imports: [CommonModule, PaginationDotsComponent],
})
-export class BlockListComponent implements AfterViewInit {
+export class BlockListComponent implements AfterViewInit, Paginable {
@Input() pageSize = 5
@Input() containerClass = ''
@Input() paginationContainerClass = 'w-full bottom-0 top-auto'
@@ -30,20 +32,23 @@ export class BlockListComponent implements AfterViewInit {
protected minHeight = 0
- protected currentPage = 0
+ protected currentPage_ = 0
protected get pages() {
return new Array(this.pagesCount).fill(0).map((_, i) => i)
}
get isFirstPage() {
- return this.currentPage === 0
+ return this.currentPage_ === 0
}
get isLastPage() {
- return this.currentPage === this.pagesCount - 1
+ return this.currentPage_ === this.pagesCount - 1
}
get pagesCount() {
return this.blocks ? Math.ceil(this.blocks.length / this.pageSize) : 1
}
+ get currentPage() {
+ return this.currentPage_ + 1 // this is 1-based
+ }
constructor(private changeDetector: ChangeDetectorRef) {}
@@ -59,25 +64,29 @@ export class BlockListComponent implements AfterViewInit {
protected refreshBlocksVisibility = () => {
this.blocks.forEach((block, index) => {
block.nativeElement.style.display =
- index >= this.currentPage * this.pageSize &&
- index < (this.currentPage + 1) * this.pageSize
+ index >= this.currentPage_ * this.pageSize &&
+ index < (this.currentPage_ + 1) * this.pageSize
? null
: 'none'
})
}
- public goToPage(index: number) {
- this.currentPage = Math.max(Math.min(index, this.pagesCount - 1), 0)
+ // pageIndex is 1-based
+ public goToPage(pageIndex: number) {
+ this.currentPage_ = Math.max(
+ Math.min(pageIndex - 1, this.pagesCount - 1),
+ 0
+ )
this.changeDetector.detectChanges()
this.refreshBlocksVisibility()
}
- public previousPage() {
+ public goToPrevPage() {
if (this.isFirstPage) return
this.goToPage(this.currentPage - 1)
}
- public nextPage() {
+ public goToNextPage() {
if (this.isLastPage) return
this.goToPage(this.currentPage + 1)
}
diff --git a/libs/ui/layout/src/lib/carousel/carousel.component.html b/libs/ui/layout/src/lib/carousel/carousel.component.html
index bc78c8c816..a56a62cf71 100644
--- a/libs/ui/layout/src/lib/carousel/carousel.component.html
+++ b/libs/ui/layout/src/lib/carousel/carousel.component.html
@@ -3,15 +3,7 @@
- 1"
- class="absolute flex flex-row justify-center gap-[14px] p-1"
- [ngClass]="stepsContainerClass"
->
-
-
+
diff --git a/libs/ui/layout/src/lib/carousel/carousel.component.spec.ts b/libs/ui/layout/src/lib/carousel/carousel.component.spec.ts
index 7301cb0d1a..87c18a7f89 100644
--- a/libs/ui/layout/src/lib/carousel/carousel.component.spec.ts
+++ b/libs/ui/layout/src/lib/carousel/carousel.component.spec.ts
@@ -73,7 +73,7 @@ describe('CarouselComponent', () => {
})
describe('click on step', () => {
beforeEach(() => {
- component.scrollToStep(2)
+ component.goToPage(3)
})
it('calls #scrollTo', () => {
expect(component.emblaApi.scrollTo).toHaveBeenCalledWith(2)
@@ -88,30 +88,30 @@ describe('CarouselComponent', () => {
it('emits the current step index', () => {
const spy = jest.fn()
component.currentStepChange.subscribe(spy)
- component.scrollToStep(2)
+ component.goToPage(3)
expect(spy).toHaveBeenCalledWith(2)
expect(spy).toHaveBeenCalledTimes(1)
})
})
- describe('isFirstStep', () => {
+ describe('isFirstPage', () => {
it('returns true if the current step is the first one', () => {
- expect(component.isFirstStep).toBe(true)
+ expect(component.isFirstPage).toBe(true)
})
it('returns false if the current step is not the first one', () => {
- component.scrollToStep(2)
- expect(component.isFirstStep).toBe(false)
+ component.goToPage(3)
+ expect(component.isFirstPage).toBe(false)
})
})
- describe('isLastStep', () => {
+ describe('isLastPage', () => {
it('returns true if the current step is the last one', () => {
- component.scrollToStep(3)
- expect(component.isLastStep).toBe(true)
+ component.goToPage(4)
+ expect(component.isLastPage).toBe(true)
})
it('returns false if the current step is not the last one', () => {
- component.scrollToStep(1)
- expect(component.isLastStep).toBe(false)
+ component.goToPage(2)
+ expect(component.isLastPage).toBe(false)
})
})
})
diff --git a/libs/ui/layout/src/lib/carousel/carousel.component.ts b/libs/ui/layout/src/lib/carousel/carousel.component.ts
index abb751af2a..1e3b212ea7 100644
--- a/libs/ui/layout/src/lib/carousel/carousel.component.ts
+++ b/libs/ui/layout/src/lib/carousel/carousel.component.ts
@@ -11,6 +11,8 @@ import {
} from '@angular/core'
import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel'
import { CommonModule } from '@angular/common'
+import { Paginable } from '../paginable.interface'
+import { PaginationDotsComponent } from '../pagination-dots/pagination-dots.component'
@Component({
selector: 'gn-ui-carousel',
@@ -18,9 +20,9 @@ import { CommonModule } from '@angular/common'
styleUrls: ['./carousel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [CommonModule],
+ imports: [CommonModule, PaginationDotsComponent],
})
-export class CarouselComponent implements AfterViewInit {
+export class CarouselComponent implements AfterViewInit, Paginable {
@ViewChild('carouselOverflowContainer') carouselOverflowContainer: ElementRef
@Input() containerClass = ''
@@ -38,15 +40,30 @@ export class CarouselComponent implements AfterViewInit {
this.changeDetector.detectChanges()
}
- get isFirstStep() {
+ // Paginable API
+ get isFirstPage() {
return this.currentStep === 0
}
- get isLastStep() {
+ get isLastPage() {
return this.currentStep === this.steps.length - 1
}
- get stepsCount() {
+ get currentPage() {
+ return this.currentStep + 1 // this is 1-based
+ }
+ get pagesCount() {
return this.steps.length
}
+ public goToPage(stepIndex: number) {
+ this.emblaApi.scrollTo(stepIndex - 1) // this is 0-based
+ }
+ public goToPrevPage() {
+ if (this.isFirstPage) return
+ this.emblaApi.scrollPrev()
+ }
+ public goToNextPage() {
+ if (this.isLastPage) return
+ this.emblaApi.scrollNext()
+ }
constructor(private changeDetector: ChangeDetectorRef) {}
@@ -63,18 +80,4 @@ export class CarouselComponent implements AfterViewInit {
.on('reInit', this.refreshSteps)
.on('select', this.refreshSteps)
}
-
- public scrollToStep(stepIndex: number) {
- this.emblaApi.scrollTo(stepIndex)
- }
-
- public slideToPrevious() {
- if (this.isFirstStep) return
- this.emblaApi.scrollPrev()
- }
-
- public slideToNext() {
- if (this.isLastStep) return
- this.emblaApi.scrollNext()
- }
}