From fab226912fc7c1008f7d75326772b4372502be41 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 5 Feb 2021 15:47:07 +0100 Subject: [PATCH 01/46] 76654: PaginationService --- .../epeople-registry.component.html | 3 +- .../epeople-registry.component.ts | 96 +++--- .../eperson-form/eperson-form.component.ts | 88 +++--- .../members-list/members-list.component.html | 6 +- .../members-list/members-list.component.ts | 62 ++-- .../subgroups-list.component.html | 6 +- .../subgroup-list/subgroups-list.component.ts | 55 ++-- .../groups-registry.component.html | 3 +- .../groups-registry.component.ts | 117 ++++---- .../bitstream-formats.component.html | 3 +- .../bitstream-formats.component.ts | 46 +-- .../metadata-registry.component.html | 3 +- .../metadata-registry.component.ts | 19 +- .../metadata-schema.component.html | 3 +- .../metadata-schema.component.ts | 26 +- .../browse-by-date-page.component.ts | 39 +-- .../browse-by-metadata-page.component.html | 4 +- .../browse-by-metadata-page.component.ts | 59 +--- .../browse-by-title-page.component.ts | 31 +- .../collection-page.component.html | 3 +- .../collection-page.component.ts | 36 +-- ...ty-page-sub-collection-list.component.html | 3 +- ...nity-page-sub-collection-list.component.ts | 45 ++- ...ity-page-sub-community-list.component.html | 3 +- ...unity-page-sub-community-list.component.ts | 42 +-- .../top-level-community-list.component.html | 3 +- .../top-level-community-list.component.ts | 42 ++- ...rag-and-drop-bitstream-list.component.html | 4 +- ...-drag-and-drop-bitstream-list.component.ts | 4 +- .../my-dspace-configuration.service.ts | 4 +- src/app/core/pagination/pagination.service.ts | 190 ++++++++++++ .../search/search-configuration.service.ts | 50 ++-- src/app/core/shared/search/search.service.ts | 32 +- .../overview/process-overview.component.html | 3 +- .../overview/process-overview.component.ts | 22 +- .../search-navbar/search-navbar.component.ts | 11 +- .../shared/browse-by/browse-by.component.ts | 16 +- .../page-size-selector.component.ts | 20 +- ...-paginated-drag-and-drop-list.component.ts | 27 +- .../pagination/pagination.component.html | 14 +- .../shared/pagination/pagination.component.ts | 273 +++++------------- .../eperson-group-list.component.html | 3 +- .../eperson-group-list.component.ts | 33 ++- .../search-form/search-form.component.ts | 15 +- .../search-settings.component.ts | 16 +- .../date/starts-with-date.component.ts | 5 +- .../starts-with-abstract.component.ts | 3 + .../submission-import-external.component.html | 3 +- .../submission-import-external.component.ts | 58 ++-- 49 files changed, 826 insertions(+), 826 deletions(-) create mode 100644 src/app/core/pagination/pagination.service.ts diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html index f478557e00..8466ff1817 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html @@ -45,8 +45,7 @@ [pageInfoState]="pageInfoState$" [collectionSize]="(pageInfoState$ | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts index 11b146b294..cb8a76d627 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; -import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; @@ -14,15 +14,13 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; -import { - getFirstCompletedRemoteData, - getAllSucceededRemoteData -} from '../../../core/shared/operators'; +import { getAllSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../../core/data/request.service'; import { PageInfo } from '../../../core/shared/page-info.model'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-epeople-registry', @@ -55,7 +53,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { * Pagination config used to display the list of epeople */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'epeople-list-pagination', + id: 'elp', pageSize: 5, currentPage: 1 }); @@ -77,6 +75,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ searchSub: Subscription; + /** + * FindListOptions + */ + findListOptionsSub: Subscription; + /** * List of subscriptions */ @@ -89,6 +92,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private router: Router, private modalService: NgbModal, + private paginationService: PaginationService, public requestService: RequestService) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; @@ -107,7 +111,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ initialisePage() { this.isEPersonFormShown = false; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); + this.search({scope: this.currentSearchScope, query: this.currentSearchQuery}); this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { if (eperson != null && eperson.id) { this.isEPersonFormShown = true; @@ -133,54 +137,54 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { return [epeople]; } })).subscribe((value: PaginatedList) => { - this.ePeopleDto$.next(value); - this.pageInfoState$.next(value.pageInfo); + this.ePeopleDto$.next(value); + this.pageInfoState$.next(value.pageInfo); })); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - if (this.config.currentPage !== event) { - this.config.currentPage = event; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); - } - } - /** * Search in the EPeople by metadata (default) or email * @param data Contains scope and query param */ search(data: any) { - const query: string = data.query; - const scope: string = data.scope; - if (query != null && this.currentSearchQuery !== query) { - this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink()); - this.currentSearchQuery = query; - this.config.currentPage = 1; + if (hasValue(this.findListOptionsSub)) { + this.findListOptionsSub.unsubscribe(); } - if (scope != null && this.currentSearchScope !== scope) { - this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink()); - this.currentSearchScope = scope; - this.config.currentPage = 1; - } - if (hasValue(this.searchSub)) { - this.searchSub.unsubscribe(); - this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); - } - this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize - }).pipe( - getAllSucceededRemoteData(), - ).subscribe((peopleRD) => { - this.ePeople$.next(peopleRD.payload); - this.pageInfoState$.next(peopleRD.payload.pageInfo); + this.findListOptionsSub = this.paginationService.getCurrentPagination(this.config.id, this.config).subscribe((findListOptions) => { + const query: string = data.query; + const scope: string = data.scope; + if (query != null && this.currentSearchQuery !== query) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.config.id); + } + if (scope != null && this.currentSearchScope !== scope) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchScope = scope; + this.paginationService.resetPage(this.config.id); + + } + if (hasValue(this.searchSub)) { + this.searchSub.unsubscribe(); + this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); + } + this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: findListOptions.currentPage, + elementsPerPage: findListOptions.pageSize + }).pipe( + getAllSucceededRemoteData(), + ).subscribe((peopleRD) => { + this.ePeople$.next(peopleRD.payload); + this.pageInfoState$.next(peopleRD.payload.pageInfo); + } + ); + this.subs.push(this.searchSub); } ); - this.subs.push(this.searchSub); } /** @@ -233,7 +237,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { if (hasValue(ePerson.id)) { this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData) => { if (restResponse.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: ePerson.name})); this.reset(); } else { this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage); @@ -273,7 +277,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { this.searchForm.patchValue({ query: '', }); - this.search({ query: '' }); + this.search({query: ''}); } /** diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 3c284735a9..6c66035f14 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -7,8 +7,8 @@ import { DynamicInputModel } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest, Observable, of, Subscription } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { switchMap, take, tap } from 'rxjs/operators'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; @@ -16,9 +16,9 @@ import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData + getRemoteDataPayload } from '../../../../core/shared/operators'; import { hasValue } from '../../../../shared/empty.util'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; @@ -31,6 +31,7 @@ import { ConfirmationModalComponent } from '../../../../shared/confirmation-moda import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../../../core/data/request.service'; import { NoContent } from '../../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; @Component({ selector: 'ds-eperson-form', @@ -118,7 +119,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Observable whether or not the admin is allowed to reset the EPerson's password * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false) */ - canReset$: Observable = of(false); + canReset$: Observable = observableOf(false); /** * Observable whether or not the admin is allowed to delete the EPerson @@ -144,7 +145,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'groups-ePersonMemberOf-list-pagination', + id: 'gem', pageSize: 5, currentPage: 1 }); @@ -167,6 +168,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { private authService: AuthService, private authorizationService: AuthorizationDataService, private modalService: NgbModal, + private paginationService: PaginationService, public requestService: RequestService) { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.epersonInitial = eperson; @@ -184,13 +186,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { - combineLatest( - this.translateService.get(`${this.messagePrefix}.firstName`), - this.translateService.get(`${this.messagePrefix}.lastName`), - this.translateService.get(`${this.messagePrefix}.email`), - this.translateService.get(`${this.messagePrefix}.canLogIn`), - this.translateService.get(`${this.messagePrefix}.requireCertificate`), - this.translateService.get(`${this.messagePrefix}.emailHint`), + observableCombineLatest( + this.translateService.get(`${this.messagePrefix}.firstName`), + this.translateService.get(`${this.messagePrefix}.lastName`), + this.translateService.get(`${this.messagePrefix}.email`), + this.translateService.get(`${this.messagePrefix}.canLogIn`), + this.translateService.get(`${this.messagePrefix}.requireCertificate`), + this.translateService.get(`${this.messagePrefix}.emailHint`), ).subscribe(([firstName, lastName, email, canLogIn, requireCertificate, emailHint]) => { this.firstName = new DynamicInputModel({ id: 'firstName', @@ -222,19 +224,19 @@ export class EPersonFormComponent implements OnInit, OnDestroy { hint: emailHint }); this.canLogIn = new DynamicCheckboxModel( - { - id: 'canLogIn', - label: canLogIn, - name: 'canLogIn', - value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) - }); + { + id: 'canLogIn', + label: canLogIn, + name: 'canLogIn', + value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) + }); this.requireCertificate = new DynamicCheckboxModel( - { - id: 'requireCertificate', - label: requireCertificate, - name: 'requireCertificate', - value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) - }); + { + id: 'requireCertificate', + label: requireCertificate, + name: 'requireCertificate', + value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) + }); this.formModel = [ this.firstName, this.lastName, @@ -258,11 +260,31 @@ export class EPersonFormComponent implements OnInit, OnDestroy { requireCertificate: eperson != null ? eperson.requireCertificate : false }); })); - this.canImpersonate$ = this.epersonService.getActiveEPerson().pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) + + const activeEPerson$ = this.epersonService.getActiveEPerson(); + + this.groups = activeEPerson$.pipe( + switchMap((eperson) => { + return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { + currentPage: 1, + elementsPerPage: this.config.pageSize + })]); + }), + switchMap(([eperson, findListOptions]) => { + return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions); + }) ); - this.canDelete$ = this.epersonService.getActiveEPerson().pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) + // this.subs.push(combineLatest([activeEPerson$, paginationOption$]).subscribe(([eperson, options]) => { + // if (eperson != null) { + // this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options); + // } + // })); + + this.canImpersonate$ = activeEPerson$.pipe( + switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) + ); + this.canDelete$ = activeEPerson$.pipe( + switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) ); }); } @@ -322,10 +344,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { getFirstCompletedRemoteData() ).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', {name: ePersonToCreate.name})); this.submitForm.emit(ePersonToCreate); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: ePersonToCreate.name })); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', {name: ePersonToCreate.name})); this.cancelForm.emit(); } }); @@ -361,10 +383,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { const response = this.epersonService.updateEPerson(editedEperson); response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', {name: editedEperson.name})); this.submitForm.emit(editedEperson); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: editedEperson.name })); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', {name: editedEperson.name})); this.cancelForm.emit(); } }); diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html index 8e2d23f8d5..54308a82ed 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html @@ -29,8 +29,7 @@ [pageInfoState]="(ePeopleSearchDtos | async)" [collectionSize]="(ePeopleSearchDtos | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChangeSearch($event)"> + [hidePagerWhenSinglePage]="true">
@@ -83,8 +82,7 @@ [pageInfoState]="(ePeopleMembersOfGroupDtos | async)" [collectionSize]="(ePeopleMembersOfGroupDtos | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts index f1d25725d6..9f26474094 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts @@ -17,12 +17,14 @@ import { GroupDataService } from '../../../../../core/eperson/group-data.service import { EPerson } from '../../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstCompletedRemoteData, getAllCompletedRemoteData } from '../../../../../core/shared/operators'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { hasValue } from '../../../../../shared/empty.util'; import {EpersonDtoModel} from '../../../../../core/eperson/models/eperson-dto.model'; /** @@ -59,7 +61,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of EPeople that are result of EPeople search */ configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'search-members-list-pagination', + id: 'sml', pageSize: 5, currentPage: 1 }); @@ -67,7 +69,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of EPerson Membes of active group being edited */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'members-list-pagination', + id: 'ml', pageSize: 5, currentPage: 1 }); @@ -90,11 +92,15 @@ export class MembersListComponent implements OnInit, OnDestroy { // current active group being edited groupBeingEdited: Group; + paginationSub: Subscription; + + constructor(private groupDataService: GroupDataService, public ePersonDataService: EPersonDataService, private translateService: TranslateService, private notificationsService: NotificationsService, private formBuilder: FormBuilder, + private paginationService: PaginationService, private router: Router) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; @@ -113,23 +119,6 @@ export class MembersListComponent implements OnInit, OnDestroy { })); } - /** - * Event triggered when the user changes page on search result - * @param event - */ - onPageChangeSearch(event) { - this.configSearch.currentPage = event; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); - } - - /** - * Event triggered when the user changes page on EPerson embers of active group - * @param event - */ - onPageChange(event) { - this.retrieveMembers(event); - } - /** * Retrieve the EPersons that are members of the group * @@ -138,10 +127,15 @@ export class MembersListComponent implements OnInit, OnDestroy { */ private retrieveMembers(page: number) { this.unsubFrom(SubKey.MembersDTO); - this.subs.set(SubKey.MembersDTO, this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { - currentPage: page, - elementsPerPage: this.config.pageSize - }).pipe( + this.subs.set(SubKey.MembersDTO, + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((currentPagination) => { + return this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + } + ); + }), getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { @@ -260,10 +254,13 @@ export class MembersListComponent implements OnInit, OnDestroy { this.unsubFrom(SubKey.SearchResultsDTO); this.subs.set(SubKey.SearchResultsDTO, - this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: this.configSearch.currentPage, - elementsPerPage: this.configSearch.pageSize - }, false).pipe( + this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( + switchMap((paginationOptions) => { + this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: this.configSearch.currentPage, + elementsPerPage: this.configSearch.pageSize + }, false) + }), getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { @@ -292,15 +289,6 @@ export class MembersListComponent implements OnInit, OnDestroy { })); } - /** - * unsub all subscriptions - */ - ngOnDestroy(): void { - for (const key of this.subs.keys()) { - this.unsubFrom(key); - } - } - /** * Shows a notification based on the success/failure of the request * @param messageSuffix Suffix for message diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html index 87cbee0b60..93d968e126 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html @@ -23,8 +23,7 @@ [pageInfoState]="(searchResults$ | async)?.payload" [collectionSize]="(searchResults$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChangeSearch($event)"> + [hidePagerWhenSinglePage]="true">
@@ -77,8 +76,7 @@ [pageInfoState]="(subGroups$ | async)?.payload" [collectionSize]="(subGroups$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts index d754e71e4f..3072c4538c 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts @@ -2,20 +2,21 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs'; -import { map, mergeMap, take } from 'rxjs/operators'; +import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; +import { map, mergeMap, switchMap, take } from 'rxjs/operators'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../../core/data/remote-data'; import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { Group } from '../../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData + getRemoteDataPayload } from '../../../../../core/shared/operators'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; /** * Keys to keep track of specific subscriptions @@ -56,7 +57,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups that are result of groups search */ configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'search-subgroups-list-pagination', + id: 'ssgl', pageSize: 5, currentPage: 1 }); @@ -64,7 +65,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of subgroups of currently active group being edited */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'subgroups-list-pagination', + id: 'sgl', pageSize: 5, currentPage: 1 }); @@ -85,6 +86,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { private translateService: TranslateService, private notificationsService: NotificationsService, private formBuilder: FormBuilder, + private paginationService: PaginationService, private router: Router) { this.currentSearchQuery = ''; } @@ -96,42 +98,27 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => { if (activeGroup != null) { this.groupBeingEdited = activeGroup; - this.retrieveSubGroups(this.config.currentPage); + this.retrieveSubGroups(); } })); } - /** - * Event triggered when the user changes page on search result - * @param event - */ - onPageChangeSearch(event) { - this.configSearch.currentPage = event; - this.search({ query: this.currentSearchQuery }); - } - - /** - * Event triggered when the user changes page on subgroups of active group - * @param event - */ - onPageChange(event) { - this.retrieveSubGroups(event); - } - /** * Retrieve the Subgroups that are members of the group * * @param page the number of the page to retrieve * @private */ - private retrieveSubGroups(page: number) { + private retrieveSubGroups() { this.unsubFrom(SubKey.Members); this.subs.set( SubKey.Members, - this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, { - currentPage: page, - elementsPerPage: this.config.pageSize - } + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((config) => this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, { + currentPage: config.currentPage, + elementsPerPage: config.pageSize + } + )) ).subscribe((rd: RemoteData>) => { this.subGroups$.next(rd); })); @@ -226,10 +213,12 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { this.searchDone = true; this.unsubFrom(SubKey.SearchResults); - this.subs.set(SubKey.SearchResults, this.groupDataService.searchGroups(this.currentSearchQuery, { - currentPage: this.configSearch.currentPage, - elementsPerPage: this.configSearch.pageSize - }).subscribe((rd: RemoteData>) => { + this.subs.set(SubKey.SearchResults, this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( + switchMap((config) => this.groupDataService.searchGroups(this.currentSearchQuery, { + currentPage: config.currentPage, + elementsPerPage: config.pageSize + })) + ).subscribe((rd: RemoteData>) => { this.searchResults$.next(rd); })); } diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html index 7a557d79ac..a760423fc5 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html @@ -35,8 +35,7 @@ [pageInfoState]="pageInfoState$" [collectionSize]="(pageInfoState$ | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts index 305da75eeb..1a7ed6a599 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts @@ -5,15 +5,15 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest as observableCombineLatest, - Subscription, Observable, - of as observableOf + of as observableOf, + Subscription } from 'rxjs'; -import { catchError, map, switchMap, take } from 'rxjs/operators'; +import { catchError, map, switchMap, take, tap } from 'rxjs/operators'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { RequestService } from '../../../core/data/request.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; @@ -24,6 +24,7 @@ import { Group } from '../../../core/eperson/models/group.model'; import { RouteService } from '../../../core/services/route.service'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { + getAllSucceededRemoteData, getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData @@ -33,6 +34,7 @@ import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-groups-registry', @@ -50,7 +52,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'groups-list-pagination', + id: 'gl', pageSize: 5, currentPage: 1 }); @@ -78,6 +80,8 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { */ searchSub: Subscription; + paginationSub: Subscription; + /** * List of subscriptions */ @@ -92,6 +96,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { protected routeService: RouteService, private router: Router, private authorizationService: AuthorizationDataService, + private paginationService: PaginationService, public requestService: RequestService) { this.currentSearchQuery = ''; this.searchForm = this.formBuilder.group(({ @@ -103,20 +108,12 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { this.search({ query: this.currentSearchQuery }); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.search({ query: this.currentSearchQuery }); - } - /** * Search in the groups (searches by group name and by uuid exact match) * @param data Contains query param */ search(data: any) { + const query: string = data.query; if (query != null && this.currentSearchQuery !== query) { this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink()); @@ -128,44 +125,61 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); } - this.searchSub = this.groupService.searchGroups(this.currentSearchQuery.trim(), { - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize - }).pipe( - getAllSucceededRemoteDataPayload(), - switchMap((groups: PaginatedList) => { - if (groups.page.length === 0) { - return observableOf(buildPaginatedList(groups.pageInfo, [])); + this.searchSub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((paginationOptions) => { + const query: string = data.query; + if (query != null && this.currentSearchQuery !== query) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.config.id); } - return observableCombineLatest(groups.page.map((group: Group) => { - if (!this.deletedGroupsIds.includes(group.id)) { - return observableCombineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), - this.hasLinkedDSO(group), - this.getSubgroups(group), - this.getMembers(group) - ]).pipe( - map(([isAuthorized, hasLinkedDSO, subgroups, members]: - [boolean, boolean, RemoteData>, RemoteData>]) => { - const groupDtoModel: GroupDtoModel = new GroupDtoModel(); - groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; - groupDtoModel.group = group; - groupDtoModel.subgroups = subgroups.payload; - groupDtoModel.epersons = members.payload; - return groupDtoModel; - } - ) - ); + if (hasValue(this.searchSub)) { + this.searchSub.unsubscribe(); + this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); + } + return this.groupService.searchGroups(this.currentSearchQuery.trim(), { + currentPage: paginationOptions.currentPage, + elementsPerPage: paginationOptions.pageSize + }).pipe( + getAllSucceededRemoteData() + switchMap((groups: PaginatedList) => { + if (groups.page.length === 0) { + return observableOf(buildPaginatedList(groups.pageInfo, [])); } - })).pipe(map((dtos: GroupDtoModel[]) => { - return buildPaginatedList(groups.pageInfo, dtos); - })); - })).subscribe((value: PaginatedList) => { - this.groupsDto$.next(value); - this.pageInfoState$.next(value.pageInfo); - }); - this.subs.push(this.searchSub); - } + return observableCombineLatest(groups.page.map((group: Group) => { + if (!this.deletedGroupsIds.includes(group.id)) { + return observableCombineLatest([ + this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), + this.hasLinkedDSO(group), + this.getSubgroups(group), + this.getMembers(group) + ]).pipe( + map(([isAuthorized, hasLinkedDSO, subgroups, members]: + [boolean, boolean, RemoteData>, RemoteData>]) => { + const groupDtoModel: GroupDtoModel = new GroupDtoModel(); + groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; + groupDtoModel.group = group; + groupDtoModel.subgroups = subgroups.payload; + groupDtoModel.epersons = members.payload; + return groupDtoModel; + } + ) + ); + } + })).pipe(map((dtos: GroupDtoModel[]) => { + return buildPaginatedList(groups.pageInfo, dtos); + })); + }) + ). + subscribe((value: PaginatedList) => { + this.groupsDto$.next(value); + this.pageInfoState$.next(value.pageInfo); + }); + this.subs.push(this.searchSub); + + } /** * Delete Group @@ -244,6 +258,9 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { } cleanupSubscribes() { + if (hasValue(this.paginationSub)) { + this.paginationSub.unsubscribe(); + } this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html index e5cf7cf5ec..6569b2d4c8 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html @@ -14,8 +14,7 @@ [pageInfoState]="(bitstreamFormats | async)?.payload" [collectionSize]="(bitstreamFormats | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 869e57ef89..7ae117b0a5 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; @@ -12,6 +12,7 @@ import { NotificationsService } from '../../../shared/notifications/notification import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; /** * This component renders a list of bitstream formats @@ -30,7 +31,7 @@ export class BitstreamFormatsComponent implements OnInit { /** * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats */ - pageState: BehaviorSubject; + // pageState: BehaviorSubject; /** * The current pagination configuration for the page used by the FindAll method @@ -45,14 +46,16 @@ export class BitstreamFormatsComponent implements OnInit { * Currently simply renders all bitstream formats */ pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-bitstreamformats-pagination', + id: 'rbp', pageSize: 20 }); constructor(private notificationsService: NotificationsService, private router: Router, private translateService: TranslateService, - private bitstreamFormatService: BitstreamFormatDataService) { + private bitstreamFormatService: BitstreamFormatDataService, + private paginationService: PaginationService, + ) { } /** @@ -80,10 +83,8 @@ export class BitstreamFormatsComponent implements OnInit { this.deselectAll(); - this.router.navigate([], { - queryParams: Object.assign({}, { page: 1 }), - queryParamsHandling: 'merge' - }); }); + this.paginationService.resetPage(this.pageConfig.id); + }); } ); } @@ -141,31 +142,12 @@ export class BitstreamFormatsComponent implements OnInit { }); } - /** - * When the page is changed, make sure to update the list of bitstreams to match the new page - * @param event The page change event - */ - onPageChange(event) { - this.config = Object.assign(new FindListOptions(), this.config, { - currentPage: event, - }); - this.pageConfig.currentPage = event; - this.pageState.next('pageChange'); - } - ngOnInit(): void { - this.pageState = new BehaviorSubject('init'); - this.bitstreamFormats = this.pageState.pipe( - switchMap(() => { - return this.updateFormats() - ; - })); - } - /** - * Finds all formats based on the current config - */ - private updateFormats() { - return this.bitstreamFormatService.findAll(this.config); + this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe( + switchMap((findListOptions: FindListOptions) => { + return this.bitstreamFormatService.findAll(findListOptions); + }) + ); } } diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html index 42b7558397..771f18ba4b 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html @@ -13,8 +13,7 @@ [paginationOptions]="config" [collectionSize]="(metadataSchemas | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts index 41efb7a578..41b14ace88 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -13,6 +13,7 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-metadata-registry', @@ -34,7 +35,7 @@ export class MetadataRegistryComponent { * Pagination config used to display the list of metadata schemas */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-metadataschemas-pagination', + id: 'rm', pageSize: 25 }); @@ -46,26 +47,20 @@ export class MetadataRegistryComponent { constructor(private registryService: RegistryService, private notificationsService: NotificationsService, private router: Router, + private paginationService: PaginationService, private translateService: TranslateService) { this.updateSchemas(); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.forceUpdateSchemas(); - } - /** * Update the list of schemas by fetching it from the rest api or cache */ private updateSchemas() { + this.metadataSchemas = this.needsUpdate$.pipe( filter((update) => update === true), - switchMap(() => this.registryService.getMetadataSchemas(toFindListOptions(this.config))) + switchMap(() => this.paginationService.getCurrentPagination(this.config.id, this.config)), + switchMap((currentPagination) => this.registryService.getMetadataSchemas(toFindListOptions(currentPagination))) ); } @@ -169,7 +164,7 @@ export class MetadataRegistryComponent { const suffix = success ? 'success' : 'failure'; const messages = observableCombineLatest( this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`), - this.translateService.get(`${prefix}.deleted.${suffix}`, { amount: amount }) + this.translateService.get(`${prefix}.deleted.${suffix}`, {amount: amount}) ); messages.subscribe(([head, content]) => { if (success) { diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html index 49ef748349..49fed0c847 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html @@ -19,8 +19,7 @@ [pageInfoState]="fields" [collectionSize]="fields?.totalElements" [hideGear]="false" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts index 572bcc8a51..513f689f84 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -6,6 +6,7 @@ import { combineLatest as observableCombineLatest, combineLatest, Observable, + of as observableOf, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; @@ -17,12 +18,10 @@ import { NotificationsService } from '../../../shared/notifications/notification import { TranslateService } from '@ngx-translate/core'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; -import { - getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload -} from '../../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-metadata-schema', @@ -48,7 +47,7 @@ export class MetadataSchemaComponent implements OnInit { * Pagination config used to display the list of metadata fields */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-metadatafields-pagination', + id: 'rm', pageSize: 25, pageSizeOptions: [25, 50, 100, 200] }); @@ -62,6 +61,7 @@ export class MetadataSchemaComponent implements OnInit { private route: ActivatedRoute, private notificationsService: NotificationsService, private router: Router, + private paginationService: PaginationService, private translateService: TranslateService) { } @@ -81,25 +81,17 @@ export class MetadataSchemaComponent implements OnInit { this.updateFields(); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.forceUpdateFields(); - } - /** * Update the list of fields by fetching it from the rest api or cache */ private updateFields() { - this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe( - switchMap(([schema, update]: [MetadataSchema, boolean]) => { + this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((currentPagination) => combineLatest(this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination))), + switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => { if (update) { this.needsUpdate$.next(false); } - return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), !update, true); + return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(currentPagination), !update, true); }) ); } diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 03c50dd051..7e3979e439 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -8,12 +8,16 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; import { environment } from '../../../environments/environment'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortOptions } from '../../core/cache/models/sort-options.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -37,30 +41,31 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, protected router: Router, + protected paginationService: PaginationService, protected cdRef: ChangeDetectorRef) { - super(route, browseService, dsoService, router); + super(route, browseService, dsoService, paginationService, router); } ngOnInit(): void { this.startsWithType = StartsWithType.date; this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - this.route.data, - (params, queryParams, data ) => { - return Object.assign({}, params, queryParams, data); + observableCombineLatest([this.route.params, this.route.queryParams, this.route.data, + currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, data, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort]; }) - .subscribe((params) => { - const metadataField = params.metadataField || this.defaultMetadataField; - this.browseId = params.id || this.defaultBrowseId; - this.startsWith = +params.startsWith || params.startsWith; - const searchOptions = browseParamsToOptions(params, Object.assign({}), this.sortConfig, this.browseId); - this.updatePageWithItems(searchOptions, this.value); - this.updateParent(params.scope); - this.updateStartsWithOptions(this.browseId, metadataField, params.scope); - })); + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + const metadataField = params.metadataField || this.defaultMetadataField; + this.browseId = params.id || this.defaultBrowseId; + this.startsWith = +params.startsWith || params.startsWith; + const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId); + this.updatePageWithItems(searchOptions, this.value); + this.updateParent(params.scope); + this.updateStartsWithOptions(this.browseId, metadataField, params.scope); + })); } /** diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index 45f2ef3b2a..d770d8cb01 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -33,9 +33,7 @@ [startsWithOptions]="startsWithOptions" [enableArrows]="true" (prev)="goPrev()" - (next)="goNext()" - (pageSizeChange)="pageSizeChange($event)" - (sortDirectionChange)="sortDirectionChange($event)"> + (next)="goNext()"> diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts index 3b67d2e3d0..54359ff0c3 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -4,7 +4,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BrowseService } from '../../core/browse/browse.service'; import { BrowseEntry } from '../../core/shared/browse-entry.model'; @@ -15,6 +15,8 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -48,7 +50,7 @@ export class BrowseByMetadataPageComponent implements OnInit { * The pagination config used to display the values */ paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'browse-by-metadata-pagination', + id: 'bbm', currentPage: 1, pageSize: 20 }); @@ -100,23 +102,24 @@ export class BrowseByMetadataPageComponent implements OnInit { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, + protected paginationService: PaginationService, protected router: Router) { } ngOnInit(): void { this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - (params, queryParams, ) => { - return Object.assign({}, params, queryParams); + observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) - .subscribe((params) => { + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { this.browseId = params.id || this.defaultBrowseId; this.value = +params.value || params.value || ''; this.startsWith = +params.startsWith || params.startsWith; - const searchOptions = browseParamsToOptions(params, this.paginationConfig, this.sortConfig, this.browseId); + const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId); if (isNotEmpty(this.value)) { this.updatePageWithItems(searchOptions, this.value); } else { @@ -203,28 +206,6 @@ export class BrowseByMetadataPageComponent implements OnInit { } } - /** - * Change the page size - * @param size - */ - pageSizeChange(size) { - this.router.navigate([], { - queryParams: Object.assign({ pageSize: size }), - queryParamsHandling: 'merge' - }); - } - - /** - * Change the sorting direction - * @param direction - */ - sortDirectionChange(direction) { - this.router.navigate([], { - queryParams: Object.assign({ sortDirection: direction }), - queryParamsHandling: 'merge' - }); - } - ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } @@ -244,20 +225,8 @@ export function browseParamsToOptions(params: any, metadata?: string): BrowseEntrySearchOptions { return new BrowseEntrySearchOptions( metadata, - Object.assign({}, - paginationConfig, - { - currentPage: +params.page || paginationConfig.currentPage, - pageSize: +params.pageSize || paginationConfig.pageSize - } - ), - Object.assign({}, - sortConfig, - { - direction: params.sortDirection || sortConfig.direction, - field: params.sortField || sortConfig.field - } - ), + paginationConfig, + sortConfig, +params.startsWith || params.startsWith, params.scope ); diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts index 3b3c29c3fd..d62c58d644 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts @@ -1,7 +1,7 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; import { Component } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue } from '../../shared/empty.util'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, browseParamsToOptions @@ -11,6 +11,9 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @Component({ selector: 'ds-browse-by-title-page', @@ -26,26 +29,26 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, + protected paginationService: PaginationService, protected router: Router) { - super(route, browseService, dsoService, router); + super(route, browseService, dsoService, paginationService, router); } ngOnInit(): void { this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - this.route.data, - (params, queryParams, data ) => { - return Object.assign({}, params, queryParams, data); + observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) - .subscribe((params) => { - this.browseId = params.id || this.defaultBrowseId; - this.updatePageWithItems(browseParamsToOptions(params, this.paginationConfig, this.sortConfig, this.browseId), undefined); - this.updateParent(params.scope); - })); + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + this.browseId = params.id || this.defaultBrowseId; + this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined); + this.updateParent(params.scope); + })); this.updateStartsWithTextOptions(); } diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index beb7413415..c2ad063d4a 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -52,8 +52,7 @@ [config]="paginationConfig" [sortConfig]="sortConfig" [objects]="itemRD" - [hideGear]="true" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="true"> this.collectionRD$.pipe( + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); + + this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe( + switchMap(([currentPagination, currentSort ]) => this.collectionRD$.pipe( getFirstSucceededRemoteData(), map((rd) => rd.payload.id), switchMap((id: string) => { return this.searchService.search( new PaginatedSearchOptions({ scope: id, - pagination: dto.paginationConfig, - sort: dto.sortConfig, + pagination: currentPagination, + sort: currentSort, dsoTypes: [DSpaceObjectType.ITEM] })).pipe(toDSpaceObjectListRD()) as Observable>>; }), @@ -103,19 +108,4 @@ export class CollectionPageComponent implements OnInit { return isNotEmpty(object); } - onPaginationChange(event: PaginationChangeEvent) { - this.paginationConfig = Object.assign(new PaginationComponentOptions(), { - currentPage: event.pagination.currentPage || this.paginationConfig.currentPage, - pageSize: event.pagination.pageSize || this.paginationConfig.pageSize, - id: 'collection-page-pagination' - }); - this.sortConfig = Object.assign(new SortOptions('dc.date.accessioned', SortDirection.DESC), { - direction: event.sort.direction || this.sortConfig.direction, - field: event.sort.field || this.sortConfig.field - }); - this.paginationChanges$.next({ - paginationConfig: this.paginationConfig, - sortConfig: this.sortConfig - }); - } } diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html index bf6ce7fd57..9928ebd18a 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html @@ -5,8 +5,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="subCollectionsRD" - [hideGear]="false" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="false"> diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts index 261ae41aa2..4f37934575 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Collection } from '../../core/shared/collection.model'; @@ -10,7 +10,8 @@ import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; -import { takeUntilCompletedRemoteData } from '../../core/shared/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { switchMap } from 'rxjs/operators'; @Component({ selector: 'ds-community-page-sub-collection-list', @@ -29,7 +30,7 @@ export class CommunityPageSubCollectionListComponent implements OnInit { /** * The pagination id */ - pageId = 'community-collections-pagination'; + pageId = 'cmcl'; /** * The sorting configuration @@ -41,7 +42,10 @@ export class CommunityPageSubCollectionListComponent implements OnInit { */ subCollectionsRDObs: BehaviorSubject>> = new BehaviorSubject>>({} as any); - constructor(private cds: CollectionDataService) {} + constructor(private cds: CollectionDataService, + private paginationService: PaginationService, + + ) {} ngOnInit(): void { this.config = new PaginationComponentOptions(); @@ -49,30 +53,25 @@ export class CommunityPageSubCollectionListComponent implements OnInit { this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.updatePage(); + this.initPage(); } /** - * Called when one of the pagination settings is changed - * @param event The new pagination data + * Initialise the list of collections */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); - } + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); - /** - * Update the list of collections - */ - updatePage() { - this.cds.findByParent(this.community.id,{ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).pipe(takeUntilCompletedRemoteData()).subscribe((results) => { + observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findByParent(this.community.id, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: {field: currentSort.field, direction: currentSort.direction} + }); + }) + ).subscribe((results) => { this.subCollectionsRDObs.next(results); }); } diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html index 880ea9cc8e..2d14dce60a 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html @@ -5,8 +5,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="subCommunitiesRD" - [hideGear]="false" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="false"> diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts index c9f72fbc04..ffa2870a90 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; @@ -10,6 +10,8 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { takeUntilCompletedRemoteData } from '../../core/shared/operators'; +import { switchMap } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-community-page-sub-community-list', @@ -31,7 +33,7 @@ export class CommunityPageSubCommunityListComponent implements OnInit { /** * The pagination id */ - pageId = 'community-subCommunities-pagination'; + pageId = 'cmscm'; /** * The sorting configuration @@ -43,7 +45,9 @@ export class CommunityPageSubCommunityListComponent implements OnInit { */ subCommunitiesRDObs: BehaviorSubject>> = new BehaviorSubject>>({} as any); - constructor(private cds: CommunityDataService) { + constructor(private cds: CommunityDataService, + private paginationService: PaginationService + ) { } ngOnInit(): void { @@ -52,25 +56,29 @@ export class CommunityPageSubCommunityListComponent implements OnInit { this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.updatePage(); - } - - /** - * Called when one of the pagination settings is changed - * @param event The new pagination data - */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); + this.initPage(); } /** * Update the list of sub-communities */ - updatePage() { + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); + + observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findByParent(this.community.id, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: { field: currentSort.field, direction: currentSort.direction } + }); + }) + ).subscribe((results) => { + this.subCommunitiesRDObs.next(results); + }); + + this.cds.findByParent(this.community.id, { currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.html b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html index f318a04f38..dbea87a175 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.html +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html @@ -6,8 +6,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="communitiesRD$ | async" - [hideGear]="true" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="true"> diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index b089244008..137675e4fc 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; @@ -10,6 +10,8 @@ import { Community } from '../../core/shared/community.model'; import { fadeInOut } from '../../shared/animations/fade'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { hasValue } from '../../shared/empty.util'; +import { switchMap } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * this component renders the Top-Level Community list @@ -36,7 +38,7 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { /** * The pagination id */ - pageId = 'top-level-pagination'; + pageId = 'tl'; /** * The sorting configuration @@ -48,7 +50,8 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { */ currentPageSubscription: Subscription; - constructor(private cds: CommunityDataService) { + constructor(private cds: CommunityDataService, + private paginationService: PaginationService) { this.config = new PaginationComponentOptions(); this.config.id = this.pageId; this.config.pageSize = 5; @@ -57,31 +60,26 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { } ngOnInit() { - this.updatePage(); + this.initPage(); } - /** - * Called when one of the pagination settings is changed - * @param event The new pagination data - */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); - } /** * Update the list of top communities */ - updatePage() { - this.unsubscribe(); - this.currentPageSubscription = this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).subscribe((results) => { + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); + + this.currentPageSubscription = observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findTop({ + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: {field: currentSort.field, direction: currentSort.direction} + }); + }) + ).subscribe((results) => { this.communitiesRD$.next(results); }); } diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html index 9197b89796..8f0d83bd1f 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html @@ -4,9 +4,7 @@ [hidePaginationDetail]="true" [paginationOptions]="options" [pageInfoState]="(objectsRD$ | async)?.payload" - [collectionSize]="(objectsRD$ | async)?.payload?.totalElements" - [disableRouteParameterUpdate]="true" - (pageChange)="switchPage($event)"> + [collectionSize]="(objectsRD$ | async)?.payload?.totalElements">
} Emits the current pagination settings + */ + getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + const page$ = this.routeService.getQueryParameterValue(`p.${paginationId}`); + const size$ = this.routeService.getQueryParameterValue(`rpp.${paginationId}`); + return observableCombineLatest([page$, size$]).pipe(map(([page, size]) => { + return Object.assign(new PaginationComponentOptions(), defaultPagination, { + currentPage: this.convertToNumeric(page, defaultPagination.currentPage), + pageSize: this.getBestMatchPageSize(size, defaultPagination) + }); + }) + ); + } + + /** + * @returns {Observable} Emits the current sorting settings + */ + getCurrentSort(paginationId: string, defaultSort: SortOptions, ignoreDefault?: boolean): Observable { + if (!ignoreDefault && (isEmpty(defaultSort) || !hasValue(defaultSort))) { + defaultSort = this.defaultSortOptions; + } + const sortDirection$ = this.routeService.getQueryParameterValue(`sd.${paginationId}`); + const sortField$ = this.routeService.getQueryParameterValue(`sf.${paginationId}`); + return observableCombineLatest([sortDirection$, sortField$]).pipe(map(([sortDirection, sortField]) => { + const field = sortField || defaultSort?.field; + const direction = SortDirection[sortDirection] || defaultSort?.direction; + return new SortOptions(field, direction); + }) + ); + } + + getFindListOptions(paginationId: string, defaultFindList: FindListOptions, ignoreDefault?: boolean): Observable { + const paginationComponentOptions = new PaginationComponentOptions(); + paginationComponentOptions.currentPage = defaultFindList.currentPage; + paginationComponentOptions.pageSize = defaultFindList.elementsPerPage; + const currentPagination$ = this.getCurrentPagination(paginationId, paginationComponentOptions); + const currentSortOptions$ = this.getCurrentSort(paginationId, defaultFindList.sort, ignoreDefault); + + return observableCombineLatest([currentPagination$, currentSortOptions$]).pipe( + filter(([currentPagination, currentSortOptions]) => hasValue(currentPagination) && hasValue(currentSortOptions)), + map(([currentPagination, currentSortOptions]) => { + return Object.assign(new FindListOptions(), defaultFindList, { + sort: currentSortOptions, + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + }); + })); + } + + resetPage(paginationId: string) { + this.updateRoute(paginationId, {page: 1}); + } + + getCurrentRouting(paginationId: string) { + return this.getFindListOptions(paginationId, {}, true).pipe( + take(1), + map((findListoptions: FindListOptions) => { + return { + page: findListoptions.currentPage, + pageSize: findListoptions.elementsPerPage, + sortField: findListoptions.sort.field, + sortDir: findListoptions.sort.direction, + }; + }) + ); + } + + updateRoute(paginationId: string, params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, changeLocationNot?: boolean) { + this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); + const parametersWithIdName = this.getParametersWithIdName(paginationId, params); + if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { + const queryParams = Object.assign({}, currentParametersWithIdName, + parametersWithIdName, extraParams); + if (changeLocationNot) { + this.location.go(this.router.createUrlTree([], { + relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' + }).toString()); + } else { + this.router.navigate([], { + queryParams: queryParams, + queryParamsHandling: 'merge' + }); + } + } + }); + } + + updateRouteWithUrl(paginationId: string, url: string[], params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, changeLocationNot?: boolean) { + this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); + const parametersWithIdName = this.getParametersWithIdName(paginationId, params); + if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { + const queryParams = Object.assign({}, currentParametersWithIdName, + parametersWithIdName, extraParams); + if (changeLocationNot) { + this.location.go(this.router.createUrlTree([], { + relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' + }).toString()); + } else { + this.router.navigate(url, { + queryParams: queryParams, + queryParamsHandling: 'merge' + }); + } + } + }); + } + + + getParametersWithIdName(paginationId: string, params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }) { + const paramsWithIdName = {}; + if (hasValue(params.page)) { + paramsWithIdName[`p.${paginationId}`] = `${params.page}`; + } + if (hasValue(params.pageSize)) { + paramsWithIdName[`rpp.${paginationId}`] = `${params.pageSize}`; + } + if (hasValue(params.sortField)) { + paramsWithIdName[`sf.${paginationId}`] = `${params.sortField}`; + } + if (hasValue(params.sortDirection)) { + paramsWithIdName[`sd.${paginationId}`] = `${params.sortDirection}`; + } + return paramsWithIdName; + } + + private convertToNumeric(param, defaultValue) { + let result = defaultValue; + if (isNumeric(param)) { + result = +param; + } + return result; + } + + + private getBestMatchPageSize(pageSize: any, defaultPagination: PaginationComponentOptions): number { + const numberPageSize = this.convertToNumeric(pageSize, defaultPagination.pageSize); + const differenceList = defaultPagination.pageSizeOptions.map((pageSizeOption) => { + return Math.abs(pageSizeOption - numberPageSize); + }); + const minDifference = Math.min.apply(Math, differenceList); + return defaultPagination.pageSizeOptions[differenceList.indexOf(minDifference)]; + } + +} diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index edd3982319..6639b53cb9 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -14,17 +14,21 @@ import { RouteService } from '../../services/route.service'; import { getFirstSucceededRemoteData } from '../operators'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { PaginationService } from '../../pagination/pagination.service'; +import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all actions that have to do with the current search configuration */ @Injectable() export class SearchConfigurationService implements OnDestroy { + + public paginationID = 'spc'; /** * Default pagination settings */ protected defaultPagination = Object.assign(new PaginationComponentOptions(), { - id: 'search-page-configuration', + id: this.paginationID, pageSize: 10, currentPage: 1 }); @@ -75,6 +79,7 @@ export class SearchConfigurationService implements OnDestroy { * @param {ActivatedRoute} route */ constructor(protected routeService: RouteService, + protected paginationService: PaginationService, protected route: ActivatedRoute) { this.initDefaults(); @@ -91,7 +96,7 @@ export class SearchConfigurationService implements OnDestroy { this.paginatedSearchOptions = new BehaviorSubject(defs); this.searchOptions = new BehaviorSubject(defs); this.subs.push(this.subscribeToSearchOptions(defs)); - this.subs.push(this.subscribeToPaginatedSearchOptions(defs)); + this.subs.push(this.subscribeToPaginatedSearchOptions(defs.pagination.id, defs)); } ); } @@ -140,34 +145,15 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current pagination settings */ - getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable { - const page$ = this.routeService.getQueryParameterValue('page'); - const size$ = this.routeService.getQueryParameterValue('pageSize'); - return observableCombineLatest(page$, size$).pipe(map(([page, size]) => { - return Object.assign(new PaginationComponentOptions(), defaultPagination, { - currentPage: page || defaultPagination.currentPage, - pageSize: size || defaultPagination.pageSize - }); - }) - ); + getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + return this.paginationService.getCurrentPagination(paginationId, defaultPagination); } /** * @returns {Observable} Emits the current sorting settings */ - getCurrentSort(defaultSort: SortOptions): Observable { - const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); - const sortField$ = this.routeService.getQueryParameterValue('sortField'); - return observableCombineLatest(sortDirection$, sortField$).pipe(map(([sortDirection, sortField]) => { - // Dirty fix because sometimes the observable value is null somehow - sortField = this.route.snapshot.queryParamMap.get('sortField'); - - const field = sortField || defaultSort.field; - const direction = SortDirection[sortDirection] || defaultSort.direction; - return new SortOptions(field, direction); - } - ) - ); + getCurrentSort(paginationId: string, defaultSort: SortOptions): Observable { + return this.paginationService.getCurrentSort(paginationId, defaultSort); } /** @@ -234,10 +220,10 @@ export class SearchConfigurationService implements OnDestroy { * @param {PaginatedSearchOptions} defaults Default values for when no parameters are available * @returns {Subscription} The subscription to unsubscribe from */ - private subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription { + private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription { return observableMerge( - this.getPaginationPart(defaults.pagination), - this.getSortPart(defaults.sort), + this.getPaginationPart(paginationId, defaults.pagination), + this.getSortPart(paginationId, defaults.sort), this.getConfigurationPart(defaults.configuration), this.getScopePart(defaults.scope), this.getQueryPart(defaults.query), @@ -317,8 +303,8 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current pagination settings as a partial SearchOptions object */ - private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable { - return this.getCurrentPagination(defaultPagination).pipe(map((pagination) => { + private getPaginationPart(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + return this.getCurrentPagination(paginationId, defaultPagination).pipe(map((pagination) => { return { pagination }; })); } @@ -326,8 +312,8 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current sorting settings as a partial SearchOptions object */ - private getSortPart(defaultSort: SortOptions): Observable { - return this.getCurrentSort(defaultSort).pipe(map((sort) => { + private getSortPart(paginationId: string, defaultSort: SortOptions): Observable { + return this.getCurrentSort(paginationId, defaultSort).pipe(map((sort) => { return { sort }; })); } diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index b380a70d44..4dfd1964c3 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -1,7 +1,7 @@ import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; -import { NavigationExtras, Router } from '@angular/router'; -import { first, map, switchMap, take } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { map, switchMap, take } from 'rxjs/operators'; import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { LinkService } from '../../cache/builders/link.service'; import { PaginatedList } from '../../data/paginated-list.model'; @@ -37,6 +37,10 @@ import { ListableObject } from '../../../shared/object-collection/shared/listabl import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator'; import { FacetConfigResponse } from '../../../shared/search/facet-config-response.model'; import { FacetValues } from '../../../shared/search/facet-values.model'; +import { PaginationService } from '../../pagination/pagination.service'; +import { SearchConfigurationService } from './search-configuration.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all general actions that have to do with the search page @@ -75,7 +79,9 @@ export class SearchService implements OnDestroy { private linkService: LinkService, private halService: HALEndpointService, private communityService: CommunityDataService, - private dspaceObjectService: DSpaceObjectDataService + private dspaceObjectService: DSpaceObjectDataService, + private paginationService: PaginationService, + private searchConfigurationService: SearchConfigurationService ) { } @@ -380,20 +386,16 @@ export class SearchService implements OnDestroy { * @param {ViewMode} viewMode Mode to switch to */ setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) { - this.routeService.getQueryParameterValue('pageSize').pipe(first()) - .subscribe((pageSize) => { - let queryParams = { view: viewMode, page: 1 }; + this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1)) + .subscribe((config) => { + let pageParams = { page: 1 }; + const queryParams = { view: viewMode }; if (viewMode === ViewMode.DetailedListElement) { - queryParams = Object.assign(queryParams, {pageSize: '1'}); - } else if (pageSize === '1') { - queryParams = Object.assign(queryParams, {pageSize: '10'}); + pageParams = Object.assign(pageParams, {pageSize: 1}); + } else if (config.pageSize === 1) { + pageParams = Object.assign(pageParams, {pageSize: 10}); } - const navigationExtras: NavigationExtras = { - queryParams: queryParams, - queryParamsHandling: 'merge' - }; - - this.router.navigate(hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], navigationExtras); + this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], pageParams, queryParams); }); } diff --git a/src/app/process-page/overview/process-overview.component.html b/src/app/process-page/overview/process-overview.component.html index 30eb44430e..62b1433b2c 100644 --- a/src/app/process-page/overview/process-overview.component.html +++ b/src/app/process-page/overview/process-overview.component.html @@ -8,8 +8,7 @@ [pageInfoState]="(processesRD$ | async)?.payload" [collectionSize]="(processesRD$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/process-page/overview/process-overview.component.ts b/src/app/process-page/overview/process-overview.component.ts index 541d6c212e..1ea8c5b9c6 100644 --- a/src/app/process-page/overview/process-overview.component.ts +++ b/src/app/process-page/overview/process-overview.component.ts @@ -8,8 +8,9 @@ import { FindListOptions } from '../../core/data/request.models'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { ProcessDataService } from '../../core/data/processes/process-data.service'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-process-overview', @@ -36,7 +37,7 @@ export class ProcessOverviewComponent implements OnInit { * The current pagination configuration for the page */ pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'process-overview-pagination', + id: 'po', pageSize: 20 }); @@ -46,6 +47,7 @@ export class ProcessOverviewComponent implements OnInit { dateFormat = 'yyyy-MM-dd HH:mm:ss'; constructor(protected processService: ProcessDataService, + protected paginationService: PaginationService, protected ePersonService: EPersonDataService) { } @@ -53,23 +55,13 @@ export class ProcessOverviewComponent implements OnInit { this.setProcesses(); } - /** - * When the page is changed, make sure to update the list of processes to match the new page - * @param event The page change event - */ - onPageChange(event) { - this.config = Object.assign(new FindListOptions(), this.config, { - currentPage: event, - }); - this.pageConfig.currentPage = event; - this.setProcesses(); - } - /** * Send a request to fetch all processes for the current page */ setProcesses() { - this.processesRD$ = this.processService.findAll(this.config); + this.processesRD$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe( + switchMap((config) => this.processService.findAll(config)) + ); } /** diff --git a/src/app/search-navbar/search-navbar.component.ts b/src/app/search-navbar/search-navbar.component.ts index 1bedfb73ef..1e509a180b 100644 --- a/src/app/search-navbar/search-navbar.component.ts +++ b/src/app/search-navbar/search-navbar.component.ts @@ -3,6 +3,8 @@ import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { SearchService } from '../core/shared/search/search.service'; import { expandSearchInput } from '../shared/animations/slide'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; /** * The search box in the header that expands on focus and collapses on focus out @@ -24,7 +26,9 @@ export class SearchNavbarComponent { // Search input field @ViewChild('searchInput') searchField: ElementRef; - constructor(private formBuilder: FormBuilder, private router: Router, private searchService: SearchService) { + constructor(private formBuilder: FormBuilder, private router: Router, private searchService: SearchService, + private paginationService: PaginationService, + private searchConfig: SearchConfigurationService) { this.searchForm = this.formBuilder.group(({ query: '', })); @@ -63,9 +67,6 @@ export class SearchNavbarComponent { this.collapse(); const linkToNavigateTo = this.searchService.getSearchLink().split('/'); this.searchForm.reset(); - this.router.navigate(linkToNavigateTo, { - queryParams: Object.assign({}, { page: 1 }, data), - queryParamsHandling: 'merge' - }); + this.paginationService.updateRouteWithUrl(this.searchConfig.paginationID, linkToNavigateTo, {page: 1}, data); } } diff --git a/src/app/shared/browse-by/browse-by.component.ts b/src/app/shared/browse-by/browse-by.component.ts index 8706c39e54..8920726c8b 100644 --- a/src/app/shared/browse-by/browse-by.component.ts +++ b/src/app/shared/browse-by/browse-by.component.ts @@ -7,6 +7,7 @@ import { fadeIn, fadeInOut } from '../animations/fade'; import { Observable } from 'rxjs'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-browse-by', @@ -96,7 +97,9 @@ export class BrowseByComponent implements OnInit { */ public sortDirections = SortDirection; - public constructor(private injector: Injector) { + public constructor(private injector: Injector, + protected paginationService: PaginationService, + ) { } @@ -119,8 +122,7 @@ export class BrowseByComponent implements OnInit { * @param size */ doPageSizeChange(size) { - this.paginationConfig.pageSize = size; - this.pageSizeChange.emit(size); + this.paginationService.updateRoute(this.paginationConfig.id,{pageSize: size}); } /** @@ -128,8 +130,7 @@ export class BrowseByComponent implements OnInit { * @param direction */ doSortDirectionChange(direction) { - this.sortConfig.direction = direction; - this.sortDirectionChange.emit(direction); + this.paginationService.updateRoute(this.paginationConfig.id,{sortDirection: direction}); } /** @@ -141,7 +142,10 @@ export class BrowseByComponent implements OnInit { ngOnInit(): void { this.objectInjector = Injector.create({ - providers: [{ provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }], + providers: [ + { provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }, + { provide: 'paginationId', useFactory: () => (this.paginationConfig.id), deps:[] } + ], parent: this.injector }); } diff --git a/src/app/shared/page-size-selector/page-size-selector.component.ts b/src/app/shared/page-size-selector/page-size-selector.component.ts index dfea7d423f..764a8063db 100644 --- a/src/app/shared/page-size-selector/page-size-selector.component.ts +++ b/src/app/shared/page-size-selector/page-size-selector.component.ts @@ -1,11 +1,12 @@ import { Component, Inject, OnInit } from '@angular/core'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { Observable } from 'rxjs'; -import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { PaginatedSearchOptions } from '../search/paginated-search-options.model'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-page-size-selector', @@ -22,8 +23,10 @@ export class PageSizeSelectorComponent implements OnInit { */ paginationOptions$: Observable; + constructor(private route: ActivatedRoute, private router: Router, + private paginationService: PaginationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) { } @@ -40,13 +43,10 @@ export class PageSizeSelectorComponent implements OnInit { */ reloadRPP(event: Event) { const value = (event.target as HTMLInputElement).value; - const navigationExtras: NavigationExtras = { - queryParams: { - pageSize: value, - page: 1 - }, - queryParamsHandling: 'merge' - }; - this.router.navigate([], navigationExtras); + this.paginationOptions$.pipe( + take(1) + ).subscribe((pagination: PaginationComponentOptions) => { + this.paginationService.updateRoute(pagination.id, {page: 1, pageSize: +value}); + }) ; } } diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts index 4cc647d091..3b5d8bc3de 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts @@ -17,6 +17,7 @@ import { Component, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } fro import { PaginationComponent } from '../pagination/pagination.component'; import { ObjectValuesPipe } from '../utils/object-values-pipe'; import { compareArraysUsing } from '../../+item-page/simple/item-types/shared/item-relationships-utils'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * Operator used for comparing {@link FieldUpdate}s by their field's UUID @@ -81,14 +82,14 @@ export abstract class AbstractPaginatedDragAndDropListComponent { + this.currentPage$.next(currentPagination.currentPage); + }); + } + /** * Initialize the field-updates in the store */ @@ -164,14 +177,6 @@ export abstract class AbstractPaginatedDragAndDropListComponent +
{{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} + {{ 'pagination.showing.detail' | translate:(getShowingDetails(collectionSize)|async)}}
- + - +
@@ -20,15 +20,15 @@
-
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index dfd9ea65ab..26a7f47f52 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -20,6 +20,7 @@ import { EPersonDataService } from '../../../../core/eperson/eperson-data.servic import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { fadeInOut } from '../../../animations/fade'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; export interface SearchEvent { scope: string; @@ -93,13 +94,16 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { */ private subs: Subscription[] = []; + private pageConfigSub: Subscription; + /** * Initialize instance variables and inject the properly DataService * * @param {DSONameService} dsoNameService * @param {Injector} parentInjector */ - constructor(public dsoNameService: DSONameService, private parentInjector: Injector) { + constructor(public dsoNameService: DSONameService, private parentInjector: Injector, + private paginationService: PaginationService) { } /** @@ -112,14 +116,14 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { providers: [], parent: this.parentInjector }).get(provider); - this.paginationOptions.id = uniqueId('eperson-group-list-pagination'); + this.paginationOptions.id = uniqueId('egl'); this.paginationOptions.pageSize = 5; if (this.initSelected) { this.entrySelectedId.next(this.initSelected); } - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); + this.updateList(this.currentSearchScope, this.currentSearchQuery); } /** @@ -151,13 +155,6 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { ); } - /** - * Method called on page change - */ - onPageChange(page: number): void { - this.paginationOptions.currentPage = page; - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); - } /** * Method called on search @@ -165,17 +162,22 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { onSearch(searchEvent: SearchEvent) { this.currentSearchQuery = searchEvent.query; this.currentSearchScope = searchEvent.scope; - this.paginationOptions.currentPage = 1; - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); + this.paginationService.resetPage(this.paginationOptions.id); + this.updateList(this.currentSearchScope, this.currentSearchQuery); } /** * Retrieve a paginate list of eperson or group */ - updateList(config: PaginationComponentOptions, scope: string, query: string): void { + updateList(scope: string, query: string): void { + if (hasValue(this.pageConfigSub)) { + this.pageConfigSub.unsubscribe(); + } + this.pageConfigSub = this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) + .subscribe((paginationOptions) => { const options: FindListOptions = Object.assign({}, new FindListOptions(), { - elementsPerPage: config.pageSize, - currentPage: config.currentPage + elementsPerPage: paginationOptions.pageSize, + currentPage: paginationOptions.currentPage }); const search$: Observable>> = this.isListOfEPerson ? @@ -187,6 +189,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { this.list$.next(list); }) ); + }); } /** diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 97541c4786..594f0c84e2 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -4,6 +4,8 @@ import { Router } from '@angular/router'; import { isNotEmpty } from '../empty.util'; import { SearchService } from '../../core/shared/search/search.service'; import { currentPath } from '../utils/route.utils'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; /** * This component renders a simple item page. @@ -60,7 +62,10 @@ export class SearchFormComponent { */ @Output() submitSearch = new EventEmitter(); - constructor(private router: Router, private searchService: SearchService) { + constructor(private router: Router, private searchService: SearchService, + private paginationService: PaginationService, + private searchConfig: SearchConfigurationService + ) { } /** @@ -85,10 +90,14 @@ export class SearchFormComponent { * @param data Updated parameters */ updateSearch(data: any) { - this.router.navigate(this.getSearchLinkParts(), { - queryParams: Object.assign({}, { page: 1 }, data), + const queryParams = Object.assign({}, data); + queryParams[`p.${this.searchConfig.paginationID}`] = 1; + + this.router.navigate(this.getSearchLinkParts(), { + queryParams: queryParams, queryParamsHandling: 'merge' }); + this.paginationService.updateRouteWithUrl(this.searchConfig.paginationID, this.getSearchLinkParts(), { page: 1 }, data); } /** diff --git a/src/app/shared/search/search-settings/search-settings.component.ts b/src/app/shared/search/search-settings/search-settings.component.ts index 45d7c7b432..6f12c161ea 100644 --- a/src/app/shared/search/search-settings/search-settings.component.ts +++ b/src/app/shared/search/search-settings/search-settings.component.ts @@ -6,6 +6,7 @@ import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { Observable } from 'rxjs'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-search-settings', @@ -30,6 +31,7 @@ export class SearchSettingsComponent implements OnInit { constructor(private service: SearchService, private route: ActivatedRoute, private router: Router, + private paginationService: PaginationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) { } @@ -46,14 +48,10 @@ export class SearchSettingsComponent implements OnInit { */ reloadOrder(event: Event) { const values = (event.target as HTMLInputElement).value.split(','); - const navigationExtras: NavigationExtras = { - queryParams: { - sortDirection: values[1], - sortField: values[0], - page: 1 - }, - queryParamsHandling: 'merge' - }; - this.router.navigate([], navigationExtras); + this.paginationService.updateRoute(this.searchConfigurationService.paginationID, { + sortField: values[0], + sortDirection: values[1] as SortDirection, + page: 1 + }); } } diff --git a/src/app/shared/starts-with/date/starts-with-date.component.ts b/src/app/shared/starts-with/date/starts-with-date.component.ts index 75173212f9..e947fb76a2 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { renderStartsWithFor, StartsWithType } from '../starts-with-decorator'; import { StartsWithAbstractComponent } from '../starts-with-abstract.component'; import { hasValue } from '../../empty.util'; +import { PaginationService } from '../../../core/pagination/pagination.service'; /** * A switchable component rendering StartsWith options for the type "Date". @@ -33,9 +34,11 @@ export class StartsWithDateComponent extends StartsWithAbstractComponent { startsWithYear: number; public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], + @Inject('paginationId') public paginationId: string, + protected paginationService: PaginationService, protected route: ActivatedRoute, protected router: Router) { - super(startsWithOptions, route, router); + super(startsWithOptions, paginationId, paginationService, route, router); } ngOnInit() { diff --git a/src/app/shared/starts-with/starts-with-abstract.component.ts b/src/app/shared/starts-with/starts-with-abstract.component.ts index 229777e96d..b0c7015a12 100644 --- a/src/app/shared/starts-with/starts-with-abstract.component.ts +++ b/src/app/shared/starts-with/starts-with-abstract.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'; import { hasValue } from '../empty.util'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * An abstract component to render StartsWith options @@ -28,6 +29,8 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { subs: Subscription[] = []; public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], + @Inject('paginationId') public paginationId: string, + protected paginationService: PaginationService, protected route: ActivatedRoute, protected router: Router) { } diff --git a/src/app/submission/import-external/submission-import-external.component.html b/src/app/submission/import-external/submission-import-external.component.html index bee5f5d872..3c7ed3cd64 100644 --- a/src/app/submission/import-external/submission-import-external.component.html +++ b/src/app/submission/import-external/submission-import-external.component.html @@ -20,8 +20,7 @@ [context]="context" [importable]="true" [importConfig]="importConfig" - (importObject)="import($event)" - (pageChange)="paginationChange();"> + (importObject)="import($event)"> diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 7a7ec2a4a3..d8801880dc 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; -import { filter, mergeMap, take } from 'rxjs/operators'; +import { filter, mergeMap, take, tap } from 'rxjs/operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceService } from '../../core/data/external-source.service'; @@ -20,6 +20,7 @@ import { fadeIn } from '../../shared/animations/fade'; import { PageInfo } from '../../core/shared/page-info.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { getFinishedRemoteData } from '../../core/shared/operators'; +import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; /** * This component allows to submit a new workspaceitem importing the data from an external source. @@ -44,6 +45,8 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * TRUE if the REST service is called to retrieve the external source items */ public isLoading$: BehaviorSubject = new BehaviorSubject(false); + + public reload$: BehaviorSubject<{query: string, source: string}>; /** * Configuration to use for the import buttons */ @@ -64,7 +67,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * The initial pagination options */ public initialPagination = Object.assign(new PaginationComponentOptions(), { - id: 'submission-external-source-relation-list', + id: 'spc', pageSize: 10 }); /** @@ -119,6 +122,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { ]).pipe( take(1) ).subscribe(([source, query]: [string, string]) => { + this.reload$ = new BehaviorSubject<{query: string; source: string}>({query: query, source: source}); this.retrieveExternalSources(source, query); })); } @@ -133,7 +137,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { queryParams: { source: event.sourceId, query: event.query }, replaceUrl: true } - ).then(() => this.retrieveExternalSources(event.sourceId, event.query)); + ).then(() => this.reload$.next({source: event.sourceId, query: event.query})); } /** @@ -148,13 +152,6 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { modalComp.externalSourceEntry = entry; } - /** - * Retrieve external sources on pagination change - */ - paginationChange() { - this.retrieveExternalSources(this.routeData.sourceId, this.routeData.query); - } - /** * Unsubscribe from all subscriptions */ @@ -170,26 +167,27 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * @param source The source tupe * @param query The query string to search */ - private retrieveExternalSources(source: string, query: string): void { - if (isNotEmpty(source) && isNotEmpty(query)) { - this.routeData.sourceId = source; - this.routeData.query = query; - this.isLoading$.next(true); - this.subs.push( - this.searchConfigService.paginatedSearchOptions.pipe( - filter((searchOptions) => searchOptions.query === query), - take(1), - mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( - getFinishedRemoteData(), - take(1) - )), - take(1) - ).subscribe((rdData) => { - this.entriesRD$.next(rdData); - this.isLoading$.next(false); - }) - ); - } + private retrieveExternalSources(sourcesss: string, querysss: string): void { + this.reload$.subscribe((sourceQueryObject: {source: string, query: string}) => { + const source = sourceQueryObject.source; + const query = sourceQueryObject.query; + if (isNotEmpty(source) && isNotEmpty(query)) { + this.routeData.sourceId = source; + this.routeData.query = query; + this.isLoading$.next(true); + this.subs.push( + this.searchConfigService.paginatedSearchOptions.pipe( + filter((searchOptions) => searchOptions.query === query), + mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( + getFinishedRemoteData(), + )), + ).subscribe((rdData) => { + this.entriesRD$.next(rdData); + this.isLoading$.next(false); + }) + ); + } + }); } } From d07f44ac41a247d5ebef74e57e6756517cd7817d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 23 Feb 2021 11:01:40 +0100 Subject: [PATCH 02/46] 76654: Feedback to pagination --- .../epeople-registry.component.ts | 2 + .../eperson-form/eperson-form.component.ts | 2 + .../members-list/members-list.component.ts | 55 ++++++++++++------- .../subgroup-list/subgroups-list.component.ts | 2 + .../groups-registry.component.ts | 4 ++ .../bitstream-formats.component.ts | 10 +++- .../metadata-registry.component.ts | 4 ++ .../metadata-schema.component.ts | 4 ++ .../browse-by-metadata-page.component.ts | 2 + .../collection-page.component.ts | 5 ++ ...nity-page-sub-collection-list.component.ts | 5 ++ ...unity-page-sub-community-list.component.ts | 5 ++ .../top-level-community-list.component.ts | 2 + .../full-file-section.component.html | 6 +- .../full-file-section.component.ts | 46 ++++++---------- .../my-dspace-configuration.service.spec.ts | 4 +- src/app/core/pagination/pagination.service.ts | 42 ++++++++++---- .../overview/process-overview.component.ts | 3 + .../item-versions.component.html | 3 +- .../item-versions/item-versions.component.ts | 29 ++++------ ...-paginated-drag-and-drop-list.component.ts | 1 + .../pagination/pagination.component.html | 2 +- .../shared/pagination/pagination.component.ts | 20 +------ .../eperson-group-list.component.html | 2 +- .../eperson-group-list.component.spec.ts | 2 - .../eperson-group-list.component.ts | 2 + .../search-facet-option.component.ts | 11 +++- .../search-facet-range-option.component.ts | 7 ++- .../search-facet-selected-option.component.ts | 8 ++- .../search-label/search-label.component.ts | 9 ++- src/modules/app/browser-app.module.ts | 1 + 31 files changed, 184 insertions(+), 116 deletions(-) diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts index cb8a76d627..c82afc0621 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts @@ -254,8 +254,10 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { this.cleanupSubscribes(); + this.paginationService.clearPagination(this.config.id); } + cleanupSubscribes() { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 6c66035f14..8fc0d74aca 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -489,8 +489,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.onCancel(); this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + this.paginationService.clearPagination(this.config.id); } + /** * This method will ensure that the page gets reset and that the cache is cleared */ diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts index 9f26474094..944b2e5dfb 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts @@ -17,9 +17,8 @@ import { GroupDataService } from '../../../../../core/eperson/group-data.service import { EPerson } from '../../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../../core/eperson/models/group.model'; import { - getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData, getAllCompletedRemoteData + getFirstCompletedRemoteData, getAllCompletedRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; @@ -238,28 +237,33 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param data Contains scope and query param */ search(data: any) { - const query: string = data.query; - const scope: string = data.scope; - if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) { - this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited)); - this.currentSearchQuery = query; - this.configSearch.currentPage = 1; - } - if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) { - this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited)); - this.currentSearchScope = scope; - this.configSearch.currentPage = 1; - } - this.searchDone = true; - this.unsubFrom(SubKey.SearchResultsDTO); this.subs.set(SubKey.SearchResultsDTO, this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( switchMap((paginationOptions) => { - this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: this.configSearch.currentPage, - elementsPerPage: this.configSearch.pageSize - }, false) + + const query: string = data.query; + const scope: string = data.scope; + if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.configSearch.id); + } + if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchScope = scope; + this.paginationService.resetPage(this.configSearch.id); + } + this.searchDone = true; + + return this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: paginationOptions.currentPage, + elementsPerPage: paginationOptions.pageSize + }); }), getAllCompletedRemoteData(), map((rd: RemoteData) => { @@ -289,6 +293,17 @@ export class MembersListComponent implements OnInit, OnDestroy { })); } + /** + * unsub all subscriptions + */ + ngOnDestroy(): void { + for (const key of this.subs.keys()) { + this.unsubFrom(key); + } + this.paginationService.clearPagination(this.config.id); + this.paginationService.clearPagination(this.configSearch.id); + } + /** * Shows a notification based on the success/failure of the request * @param messageSuffix Suffix for message diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts index 3072c4538c..13d89d2ce1 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts @@ -244,6 +244,8 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { for (const key of this.subs.keys()) { this.unsubFrom(key); } + this.paginationService.clearPagination(this.config.id); + this.paginationService.clearPagination(this.configSearch.id); } /** diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts index 1a7ed6a599..6083efd6f7 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts @@ -255,12 +255,16 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { this.cleanupSubscribes(); + this.paginationService.clearPagination(this.config.id); } + cleanupSubscribes() { if (hasValue(this.paginationSub)) { this.paginationSub.unsubscribe(); } this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + this.paginationService.clearPagination(this.config.id); } + } diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 7ae117b0a5..d2ae805be7 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; @@ -21,7 +21,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service'; selector: 'ds-bitstream-formats', templateUrl: './bitstream-formats.component.html' }) -export class BitstreamFormatsComponent implements OnInit { +export class BitstreamFormatsComponent implements OnInit, OnDestroy { /** * A paginated list of bitstream formats to be shown on the page @@ -58,6 +58,7 @@ export class BitstreamFormatsComponent implements OnInit { ) { } + /** * Deletes the currently selected formats from the registry and updates the presented list */ @@ -150,4 +151,9 @@ export class BitstreamFormatsComponent implements OnInit { }) ); } + + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.pageConfig.id); + } } diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts index 41b14ace88..8574c4678b 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -174,4 +174,8 @@ export class MetadataRegistryComponent { } }); } + ngOnDestroy(): void { + this.paginationService.clearPagination(this.config.id); + } + } diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts index 513f689f84..8a2086d5e2 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -208,4 +208,8 @@ export class MetadataSchemaComponent implements OnInit { } }); } + ngOnDestroy(): void { + this.paginationService.clearPagination(this.config.id); + } + } diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts index 54359ff0c3..833c3d7d19 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -208,8 +208,10 @@ export class BrowseByMetadataPageComponent implements OnInit { ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + this.paginationService.clearPagination(this.paginationConfig.id); } + } /** diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index e826aee702..74d8c76c5c 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -108,4 +108,9 @@ export class CollectionPageComponent implements OnInit { return isNotEmpty(object); } + ngOnDestroy(): void { + this.paginationService.clearPagination(this.paginationConfig.id); + } + + } diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts index 4f37934575..adb4c32a32 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts @@ -75,4 +75,9 @@ export class CommunityPageSubCollectionListComponent implements OnInit { this.subCollectionsRDObs.next(results); }); } + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.config.id); + } + } diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts index ffa2870a90..2c30ede554 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts @@ -87,4 +87,9 @@ export class CommunityPageSubCommunityListComponent implements OnInit { this.subCommunitiesRDObs.next(results); }); } + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.config.id); + } + } diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index 137675e4fc..5f6306649f 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -98,5 +98,7 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { */ ngOnDestroy() { this.unsubscribe(); + this.paginationService.clearPagination(this.config.id); } + } diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html index 00218b66d1..d593d60ce6 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html @@ -8,8 +8,7 @@ [paginationOptions]="originalOptions" [pageInfoState]="originals" [collectionSize]="originals?.totalElements" - [disableRouteParameterUpdate]="true" - (pageChange)="switchOriginalPage($event)"> + [retainScrollPosition]="true">
@@ -51,8 +50,7 @@ [paginationOptions]="licenseOptions" [pageInfoState]="licenses" [collectionSize]="licenses?.totalElements" - [disableRouteParameterUpdate]="true" - (pageChange)="switchLicensePage($event)"> + [retainScrollPosition]="true">
diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts index ca3d5e65c7..439bb6502f 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts @@ -13,6 +13,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { hasValue, isEmpty } from '../../../../shared/empty.util'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; /** * This component renders the file section of the item @@ -35,23 +36,22 @@ export class FullFileSectionComponent extends FileSectionComponent implements On pageSize = 5; originalOptions = Object.assign(new PaginationComponentOptions(), { - id: 'original-bitstreams-options', + id: 'obo', currentPage: 1, pageSize: this.pageSize }); - originalCurrentPage$ = new BehaviorSubject(1); licenseOptions = Object.assign(new PaginationComponentOptions(), { - id: 'license-bitstreams-options', + id: 'lbo', currentPage: 1, pageSize: this.pageSize }); - licenseCurrentPage$ = new BehaviorSubject(1); constructor( bitstreamDataService: BitstreamDataService, protected notificationsService: NotificationsService, - protected translateService: TranslateService + protected translateService: TranslateService, + protected paginationService: PaginationService ) { super(bitstreamDataService, notificationsService, translateService); } @@ -61,11 +61,11 @@ export class FullFileSectionComponent extends FileSectionComponent implements On } initialize(): void { - this.originals$ = this.originalCurrentPage$.pipe( - switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName( + this.originals$ = this.paginationService.getCurrentPagination(this.originalOptions.id, this.originalOptions).pipe( + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( this.item, 'ORIGINAL', - {elementsPerPage: this.pageSize, currentPage: pageNumber}, + {elementsPerPage: options.pageSize, currentPage: options.currentPage}, true, true, followLink('format') @@ -78,11 +78,11 @@ export class FullFileSectionComponent extends FileSectionComponent implements On ) ); - this.licenses$ = this.licenseCurrentPage$.pipe( - switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName( + this.licenses$ = this.paginationService.getCurrentPagination(this.licenseOptions.id, this.licenseOptions).pipe( + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( this.item, 'LICENSE', - {elementsPerPage: this.pageSize, currentPage: pageNumber}, + {elementsPerPage: options.pageSize, currentPage: options.currentPage}, true, true, followLink('format') @@ -97,25 +97,13 @@ export class FullFileSectionComponent extends FileSectionComponent implements On } - /** - * Update the current page for the original bundle bitstreams - * @param page - */ - switchOriginalPage(page: number) { - this.originalOptions.currentPage = page; - this.originalCurrentPage$.next(page); - } - - /** - * Update the current page for the license bundle bitstreams - * @param page - */ - switchLicensePage(page: number) { - this.licenseOptions.currentPage = page; - this.licenseCurrentPage$.next(page); - } - hasValuesInBundle(bundle: PaginatedList) { return hasValue(bundle) && !isEmpty(bundle.page); } + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.originalOptions.id); + this.paginationService.clearPagination(this.licenseOptions.id); + } + } diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts index 4154a09f15..6cbab8125b 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts @@ -102,7 +102,7 @@ describe('MyDSpaceConfigurationService', () => { describe('when getCurrentSort is called', () => { beforeEach(() => { - service.getCurrentSort({} as any); + // service.getCurrentSort({} as any); }); it('should call getQueryParameterValue on the routeService with parameter name \'sortDirection\'', () => { expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortDirection'); @@ -114,7 +114,7 @@ describe('MyDSpaceConfigurationService', () => { describe('when getCurrentPagination is called', () => { beforeEach(() => { - service.getCurrentPagination({ currentPage: 1, pageSize: 10 } as any); + // service.getCurrentPagination({ currentPage: 1, pageSize: 10 } as any); }); it('should call getQueryParameterValue on the routeService with parameter name \'page\'', () => { expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('page'); diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index 35b38b23f7..0143d20923 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -99,17 +99,20 @@ export class PaginationService { pageSize?: number sortField?: string sortDirection?: SortDirection - }, extraParams?, changeLocationNot?: boolean) { + }, extraParams?, retainScrollPosition?: boolean) { this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); const parametersWithIdName = this.getParametersWithIdName(paginationId, params); if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { const queryParams = Object.assign({}, currentParametersWithIdName, parametersWithIdName, extraParams); - if (changeLocationNot) { - this.location.go(this.router.createUrlTree([], { - relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' - }).toString()); + console.log(retainScrollPosition); + if (retainScrollPosition) { + this.router.navigate([], { + queryParams: queryParams, + queryParamsHandling: 'merge', + fragment: `p-${paginationId}` + }); } else { this.router.navigate([], { queryParams: queryParams, @@ -125,17 +128,20 @@ export class PaginationService { pageSize?: number sortField?: string sortDirection?: SortDirection - }, extraParams?, changeLocationNot?: boolean) { + }, extraParams?, retainScrollPosition?: boolean) { + console.log(retainScrollPosition); this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); const parametersWithIdName = this.getParametersWithIdName(paginationId, params); if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { const queryParams = Object.assign({}, currentParametersWithIdName, parametersWithIdName, extraParams); - if (changeLocationNot) { - this.location.go(this.router.createUrlTree([], { - relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' - }).toString()); + if (retainScrollPosition) { + this.router.navigate(url, { + queryParams: queryParams, + queryParamsHandling: 'merge', + fragment: `p-${paginationId}` + }); } else { this.router.navigate(url, { queryParams: queryParams, @@ -146,6 +152,22 @@ export class PaginationService { }); } + clearPagination(paginationId: string) { + const params = {}; + params[`p.${paginationId}`] = null; + params[`rpp.${paginationId}`] = null; + params[`sf.${paginationId}`] = null; + params[`sd.${paginationId}`] = null; + + this.router.navigate([], { + queryParams: params, + queryParamsHandling: 'merge' + }); + } + + getPageParam(paginationId: string) { + return `p.${paginationId}`; + } getParametersWithIdName(paginationId: string, params: { page?: number diff --git a/src/app/process-page/overview/process-overview.component.ts b/src/app/process-page/overview/process-overview.component.ts index 1ea8c5b9c6..03fcf27222 100644 --- a/src/app/process-page/overview/process-overview.component.ts +++ b/src/app/process-page/overview/process-overview.component.ts @@ -74,5 +74,8 @@ export class ProcessOverviewComponent implements OnInit { map((eperson: EPerson) => eperson.name) ); } + ngOnDestroy(): void { + this.paginationService.clearPagination(this.pageConfig.id); + } } diff --git a/src/app/shared/item/item-versions/item-versions.component.html b/src/app/shared/item/item-versions/item-versions.component.html index 6e93f4c7ca..0061de4b2e 100644 --- a/src/app/shared/item/item-versions/item-versions.component.html +++ b/src/app/shared/item/item-versions/item-versions.component.html @@ -8,8 +8,7 @@ [paginationOptions]="options" [pageInfoState]="versions" [collectionSize]="versions?.totalElements" - [disableRouteParameterUpdate]="true" - (pageChange)="switchPage($event)"> + [retainScrollPosition]="true">
diff --git a/src/app/shared/item/item-versions/item-versions.component.ts b/src/app/shared/item/item-versions/item-versions.component.ts index 9c4682642a..3e515b9452 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -13,6 +13,7 @@ import { PaginatedSearchOptions } from '../../search/paginated-search-options.mo import { AlertType } from '../../alert/aletr-type'; import { followLink } from '../../utils/follow-link-config.model'; import { hasValueOperator } from '../../empty.util'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-item-versions', @@ -76,17 +77,14 @@ export class ItemVersionsComponent implements OnInit { * Start at page 1 and always use the set page size */ options = Object.assign(new PaginationComponentOptions(),{ - id: 'item-versions-options', + id: 'ivo', currentPage: 1, pageSize: this.pageSize }); - /** - * The current page being displayed - */ - currentPage$ = new BehaviorSubject(1); - - constructor(private versionHistoryService: VersionHistoryDataService) { + constructor(private versionHistoryService: VersionHistoryDataService, + private paginationService: PaginationService + ) { } /** @@ -105,10 +103,11 @@ export class ItemVersionsComponent implements OnInit { getRemoteDataPayload(), hasValueOperator(), ); - this.versionsRD$ = observableCombineLatest(versionHistory$, this.currentPage$).pipe( - switchMap(([versionHistory, page]: [VersionHistory, number]) => + const currentPagination = this.paginationService.getCurrentPagination(this.options.id, this.options); + this.versionsRD$ = observableCombineLatest(versionHistory$, currentPagination).pipe( + switchMap(([versionHistory, options]: [VersionHistory, PaginationComponentOptions]) => this.versionHistoryService.getVersions(versionHistory.id, - new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })}), + new PaginatedSearchOptions({pagination: Object.assign({}, options, { currentPage: options.currentPage })}), true, true, followLink('item'), followLink('eperson'))) ); this.hasEpersons$ = this.versionsRD$.pipe( @@ -120,13 +119,9 @@ export class ItemVersionsComponent implements OnInit { ); } - /** - * Update the current page - * @param page - */ - switchPage(page: number) { - this.options.currentPage = page; - this.currentPage$.next(page); + ngOnDestroy(): void { + this.paginationService.clearPagination(this.options.id); } + } diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts index 3b5d8bc3de..b861104197 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts @@ -233,5 +233,6 @@ export abstract class AbstractPaginatedDragAndDropListComponent hasValue(sub)).forEach((sub) => sub.unsubscribe()); + this.paginationService.clearPagination(this.options.id); } } diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 7f7e7acc60..e8382c2eb4 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index cfb6fd147f..d126565e8f 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -20,7 +20,6 @@ import { hasValue } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; -import { isNumeric } from 'rxjs/internal-compatibility'; /** * The default pagination controls component. @@ -104,7 +103,7 @@ export class PaginationComponent implements OnDestroy, OnInit { * In other words, changing pagination won't add or update the url parameters on the current page, and the url * parameters won't affect the pagination of this component */ - @Input() public disableRouteParameterUpdate = false; + @Input() public retainScrollPosition = false; /** * Current page. @@ -125,7 +124,7 @@ export class PaginationComponent implements OnDestroy, OnInit { * ID for the pagination instance. Only useful if you wish to * have more than once instance at a time in a given component. */ - private id: string; + public id: string; /** * A boolean that indicate if is an extra small devices viewport. @@ -176,16 +175,7 @@ export class PaginationComponent implements OnDestroy, OnInit { })); this.checkConfig(this.paginationOptions); this.initializeConfig(); - // Listen to changes - if (!this.disableRouteParameterUpdate) { - this.subs.push( - this.paginationService.getCurrentPagination(this.id, this.paginationOptions).subscribe((queryParams) => { - })); - this.subs.push( - this.paginationService.getCurrentSort(this.id, this.sortOptions).subscribe((queryParams) => { - })); } - } /** * Method provided by Angular. Invoked when the instance is destroyed. @@ -336,11 +326,7 @@ export class PaginationComponent implements OnDestroy, OnInit { * @param params */ private updateParams(params: {}) { - if (!this.disableRouteParameterUpdate) { - this.paginationService.updateRoute(this.id, params); - } else { - this.paginationService.updateRoute(this.id, params, {}, this.disableRouteParameterUpdate); - } + this.paginationService.updateRoute(this.id, params, {}, this.retainScrollPosition); } /** diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html index 1acdb85222..6ee5aad943 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html @@ -5,7 +5,7 @@
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts index 037edbc905..db01146bfe 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts @@ -180,7 +180,6 @@ describe('EpersonGroupListComponent test suite', () => { it('should update list on page change', () => { spyOn(comp, 'updateList'); - comp.onPageChange(2); expect(compAsAny.updateList).toHaveBeenCalled(); }); @@ -257,7 +256,6 @@ describe('EpersonGroupListComponent test suite', () => { it('should update list on page change', () => { spyOn(comp, 'updateList'); - comp.onPageChange(2); expect(compAsAny.updateList).toHaveBeenCalled(); }); diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index 26a7f47f52..8605033b2e 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -200,6 +200,8 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); + this.paginationService.clearPagination(this.paginationOptions.id); } + } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts index c6d7f4ac7c..45c90d8a60 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts @@ -10,6 +10,8 @@ import { SearchConfigurationService } from '../../../../../../core/shared/search import { hasValue } from '../../../../../empty.util'; import { currentPath } from '../../../../../utils/route.utils'; import { getFacetValueForType } from '../../../../search.utils'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; @Component({ selector: 'ds-search-facet-option', @@ -60,10 +62,13 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + paginationId: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, - protected router: Router + protected router: Router, + protected paginationService: PaginationService ) { } @@ -71,6 +76,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.paginationId = this.searchConfigService.paginationID; this.searchLink = this.getSearchLink(); this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked)); this.sub = observableCombineLatest(this.selectedValues$, this.searchConfigService.searchOptions) @@ -101,9 +107,10 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { * @param {string[]} selectedValues The values that are currently selected for this filter */ private updateAddParams(selectedValues: FacetValue[]): void { + const page = this.paginationService.getPageParam(this.searchConfigService.paginationID); this.addQueryParams = { [this.filterConfig.paramName]: [...selectedValues.map((facetValue: FacetValue) => getFacetValueForType(facetValue, this.filterConfig)), this.getFacetValue()], - page: 1 + [page]: 1 }; } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts index 56a075d333..3d8215b210 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts @@ -13,6 +13,7 @@ import { import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; import { hasValue } from '../../../../../empty.util'; import { currentPath } from '../../../../../utils/route.utils'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; const rangeDelimiter = '-'; @@ -65,7 +66,8 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, - protected router: Router + protected router: Router, + protected paginationService: PaginationService ) { } @@ -104,10 +106,11 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { const parts = this.filterValue.value.split(rangeDelimiter); const min = parts.length > 1 ? parts[0].trim() : this.filterValue.value; const max = parts.length > 1 ? parts[1].trim() : this.filterValue.value; + const page = this.paginationService.getPageParam(this.searchConfigService.paginationID); this.changeQueryParams = { [this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: [min], [this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: [max], - page: 1 + [page]: 1 }; } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts index 159effe751..d92455fdd9 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts @@ -9,6 +9,7 @@ import { SearchConfigurationService } from '../../../../../../core/shared/search import { FacetValue } from '../../../../facet-value.model'; import { currentPath } from '../../../../../utils/route.utils'; import { getFacetValueForType } from '../../../../search.utils'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; @Component({ selector: 'ds-search-facet-selected-option', @@ -58,7 +59,8 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, - protected router: Router + protected router: Router, + protected paginationService: PaginationService ) { } @@ -88,14 +90,14 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { * @param {string[]} selectedValues The values that are currently selected for this filter */ private updateRemoveParams(selectedValues: FacetValue[]): void { + const page = this.paginationService.getPageParam(this.searchConfigService.paginationID); this.removeQueryParams = { [this.filterConfig.paramName]: selectedValues .filter((facetValue: FacetValue) => facetValue.label !== this.selectedValue.label) .map((facetValue: FacetValue) => this.getFacetValue(facetValue)), - page: 1 + [page]: 1 }; } - /** * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved * Retrieve facet value related to facet type diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.ts b/src/app/shared/search/search-labels/search-label/search-label.component.ts index 8ae1a8dd1b..b66308a5bd 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.ts @@ -1,10 +1,12 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { Params, Router } from '@angular/router'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../../empty.util'; import { SearchService } from '../../../../core/shared/search/search.service'; import { currentPath } from '../../../utils/route.utils'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; @Component({ selector: 'ds-search-label', @@ -32,6 +34,8 @@ export class SearchLabelComponent implements OnInit { */ constructor( private searchService: SearchService, + private paginationService: PaginationService, + private searchConfigurationService: SearchConfigurationService, private router: Router) { } @@ -50,9 +54,10 @@ export class SearchLabelComponent implements OnInit { map((filters) => { const field: string = Object.keys(filters).find((f) => f === this.key); const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null; + const page = this.paginationService.getPageParam(this.searchConfigurationService.paginationID); return { [field]: isNotEmpty(newValues) ? newValues : null, - page: 1 + [page]: 1 }; }) ); diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 4b6c5c813e..0fb4416376 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -55,6 +55,7 @@ export function getRequest(transferState: TransferState): any { // enableTracing: true, useHash: false, scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', preloadingStrategy: NoPreloading }), StatisticsModule.forRoot(), From b18f9c7c9f16e3d6f1b0ca26636d58235efbf4d3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 4 Mar 2021 17:10:01 +0100 Subject: [PATCH 03/46] Add tests and fix tests --- .../epeople-registry.component.spec.ts | 18 ++- .../eperson-form.component.spec.ts | 19 +++ .../eperson-form/eperson-form.component.ts | 10 +- .../members-list.component.spec.ts | 17 ++ .../subgroups-list.component.spec.ts | 18 +++ .../groups-registry.component.spec.ts | 16 ++ .../groups-registry.component.ts | 94 +++++------ .../bitstream-formats.component.spec.ts | 48 +++++- .../metadata-registry.component.spec.ts | 15 ++ .../metadata-schema.component.spec.ts | 15 ++ .../browse-by-date-page.component.spec.ts | 16 ++ .../browse-by-metadata-page.component.spec.ts | 32 +++- .../browse-by-title-page.component.spec.ts | 15 ++ ...page-sub-collection-list.component.spec.ts | 40 ++--- ...-page-sub-community-list.component.spec.ts | 39 ++--- ...top-level-community-list.component.spec.ts | 36 ++--- ...-and-drop-bitstream-list.component.spec.ts | 18 ++- .../full-file-section.component.spec.ts | 51 ++---- .../my-dspace-configuration.service.spec.ts | 28 ++-- .../my-dspace-configuration.service.ts | 1 + .../pagination/pagination.service.spec.ts | 149 ++++++++++++++++++ src/app/core/pagination/pagination.service.ts | 16 +- .../search-configuration.service.spec.ts | 31 ++-- .../core/shared/search/search.service.spec.ts | 34 +++- .../process-overview.component.spec.ts | 31 ++-- .../search-navbar.component.spec.ts | 26 ++- .../browse-by/browse-by.component.spec.ts | 38 +++-- .../shared/browse-by/browse-by.component.ts | 2 +- .../item-versions.component.spec.ts | 28 ++-- .../collection-select.component.spec.ts | 17 +- .../item-select/item-select.component.spec.ts | 20 ++- .../page-size-selector.component.spec.ts | 8 + ...nated-drag-and-drop-list.component.spec.ts | 31 ++-- .../pagination/pagination.component.spec.ts | 133 ++++++---------- .../shared/pagination/pagination.component.ts | 66 +------- .../eperson-group-list.component.spec.ts | 29 ++-- .../search-form/search-form.component.spec.ts | 23 ++- .../search-facet-option.component.spec.ts | 21 ++- ...earch-facet-range-option.component.spec.ts | 22 ++- ...ch-facet-selected-option.component.spec.ts | 21 ++- .../search-label.component.spec.ts | 19 +++ .../search-settings.component.spec.ts | 13 ++ .../date/starts-with-date.component.spec.ts | 17 ++ .../text/starts-with-text.component.spec.ts | 19 ++- .../search-configuration-service.stub.ts | 2 + ...bmission-import-external.component.spec.ts | 17 +- .../submission-import-external.component.ts | 12 +- 47 files changed, 937 insertions(+), 454 deletions(-) create mode 100644 src/app/core/pagination/pagination.service.spec.ts diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts index c104de0b17..4b9dcef00d 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts @@ -25,6 +25,9 @@ import { NotificationsServiceStub } from '../../../shared/testing/notifications- import { RouterStub } from '../../../shared/testing/router.stub'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { RequestService } from '../../../core/data/request.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('EPeopleRegistryComponent', () => { let component: EPeopleRegistryComponent; @@ -37,6 +40,8 @@ describe('EPeopleRegistryComponent', () => { let authorizationService: AuthorizationDataService; let modalService; + let paginationService; + beforeEach(waitForAsync(() => { mockEPeople = [EPersonMock, EPersonMock2]; ePersonDataServiceStub = { @@ -115,6 +120,16 @@ describe('EPeopleRegistryComponent', () => { }); builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -131,7 +146,8 @@ describe('EPeopleRegistryComponent', () => { { provide: AuthorizationDataService, useValue: authorizationService }, { provide: FormBuilderService, useValue: builderService }, { provide: Router, useValue: new RouterStub() }, - { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) } + { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 1163490e12..e10c1af94c 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -26,6 +26,9 @@ import { AuthorizationDataService } from '../../../../core/data/feature-authoriz import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RequestService } from '../../../../core/data/request.service'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -38,6 +41,10 @@ describe('EPersonFormComponent', () => { let authorizationService: AuthorizationDataService; let groupsDataService: GroupDataService; + let paginationService: PaginationService; + + + beforeEach(waitForAsync(() => { mockEPeople = [EPersonMock, EPersonMock2]; ePersonDataServiceStub = { @@ -104,6 +111,17 @@ describe('EPersonFormComponent', () => { findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), getGroupRegistryRouterLink: '' }); + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {} + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -121,6 +139,7 @@ describe('EPersonFormComponent', () => { { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: AuthService, useValue: authService }, { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: PaginationService, useValue: paginationService }, { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 8fc0d74aca..48f171f966 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -271,14 +271,12 @@ export class EPersonFormComponent implements OnInit, OnDestroy { })]); }), switchMap(([eperson, findListOptions]) => { - return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions); + if (eperson != null) { + return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions); + } + return observableOf(undefined); }) ); - // this.subs.push(combineLatest([activeEPerson$, paginationOption$]).subscribe(([eperson, options]) => { - // if (eperson != null) { - // this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options); - // } - // })); this.canImpersonate$ = activeEPerson$.pipe( switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts index 10735cbde5..ad1455ae0f 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -26,6 +26,10 @@ import { getMockFormBuilderService } from '../../../../../shared/mocks/form-buil import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock'; import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub'; import { RouterMock } from '../../../../../shared/mocks/router.mock'; +import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../core/data/request.models'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; describe('MembersListComponent', () => { let component: MembersListComponent; @@ -39,6 +43,7 @@ describe('MembersListComponent', () => { let allGroups; let epersonMembers; let subgroupMembers; + let paginationService; beforeEach(waitForAsync(() => { activeGroup = GroupMock; @@ -113,6 +118,17 @@ describe('MembersListComponent', () => { }; builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + clearPagination : {}, + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -129,6 +145,7 @@ describe('MembersListComponent', () => { { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: FormBuilderService, useValue: builderService }, { provide: Router, useValue: new RouterMock() }, + { provide: PaginationService, useValue: paginationService }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts index 9841d2b02e..ea65e1b290 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts @@ -35,6 +35,10 @@ import { getMockTranslateService } from '../../../../../shared/mocks/translate.s import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock'; import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub'; import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../core/data/request.models'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; describe('SubgroupsListComponent', () => { let component: SubgroupsListComponent; @@ -47,6 +51,7 @@ describe('SubgroupsListComponent', () => { let subgroups; let allGroups; let routerStub; + let paginationService; beforeEach(waitForAsync(() => { activeGroup = GroupMock; @@ -100,6 +105,18 @@ describe('SubgroupsListComponent', () => { routerStub = new RouterMock(); builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {}, + clearPagination: {} + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -115,6 +132,7 @@ describe('SubgroupsListComponent', () => { { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: FormBuilderService, useValue: builderService }, { provide: Router, useValue: routerStub }, + { provide: PaginationService, useValue: paginationService }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts index dd08ea6772..ff75988607 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts @@ -28,6 +28,10 @@ import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mo import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { routeServiceStub } from '../../../shared/testing/route-service.stub'; import { RouterMock } from '../../../shared/mocks/router.mock'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('GroupRegistryComponent', () => { let component: GroupsRegistryComponent; @@ -39,6 +43,7 @@ describe('GroupRegistryComponent', () => { let mockGroups; let mockEPeople; + let paginationService; beforeEach(waitForAsync(() => { mockGroups = [GroupMock, GroupMock2]; @@ -131,6 +136,16 @@ describe('GroupRegistryComponent', () => { authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: observableOf(true) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {} + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -149,6 +164,7 @@ describe('GroupRegistryComponent', () => { { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: new RouterMock() }, { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: PaginationService, useValue: paginationService }, { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts index 6083efd6f7..cb55f0f2cd 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts @@ -9,7 +9,7 @@ import { of as observableOf, Subscription } from 'rxjs'; -import { catchError, map, switchMap, take, tap } from 'rxjs/operators'; +import { catchError, map, switchMap, take } from 'rxjs/operators'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; @@ -25,9 +25,9 @@ import { RouteService } from '../../../core/services/route.service'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { getAllSucceededRemoteData, - getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, - getFirstSucceededRemoteData + getFirstSucceededRemoteData, + getRemoteDataPayload } from '../../../core/shared/operators'; import { PageInfo } from '../../../core/shared/page-info.model'; import { hasValue } from '../../../shared/empty.util'; @@ -113,72 +113,58 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { * @param data Contains query param */ search(data: any) { - - const query: string = data.query; - if (query != null && this.currentSearchQuery !== query) { - this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink()); - this.currentSearchQuery = query; - this.config.currentPage = 1; - } if (hasValue(this.searchSub)) { this.searchSub.unsubscribe(); this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); } - this.searchSub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( switchMap((paginationOptions) => { const query: string = data.query; if (query != null && this.currentSearchQuery !== query) { - this.router.navigate([], { - queryParamsHandling: 'merge' - }); this.currentSearchQuery = query; - this.paginationService.resetPage(this.config.id); - } - if (hasValue(this.searchSub)) { - this.searchSub.unsubscribe(); - this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); + this.paginationService.updateRouteWithUrl(this.config.id, [], {page: 1}); } return this.groupService.searchGroups(this.currentSearchQuery.trim(), { currentPage: paginationOptions.currentPage, elementsPerPage: paginationOptions.pageSize - }).pipe( - getAllSucceededRemoteData() - switchMap((groups: PaginatedList) => { - if (groups.page.length === 0) { - return observableOf(buildPaginatedList(groups.pageInfo, [])); - } - return observableCombineLatest(groups.page.map((group: Group) => { - if (!this.deletedGroupsIds.includes(group.id)) { - return observableCombineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), - this.hasLinkedDSO(group), - this.getSubgroups(group), - this.getMembers(group) - ]).pipe( - map(([isAuthorized, hasLinkedDSO, subgroups, members]: - [boolean, boolean, RemoteData>, RemoteData>]) => { - const groupDtoModel: GroupDtoModel = new GroupDtoModel(); - groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; - groupDtoModel.group = group; - groupDtoModel.subgroups = subgroups.payload; - groupDtoModel.epersons = members.payload; - return groupDtoModel; - } - ) - ); - } - })).pipe(map((dtos: GroupDtoModel[]) => { - return buildPaginatedList(groups.pageInfo, dtos); - })); - }) - ). - subscribe((value: PaginatedList) => { - this.groupsDto$.next(value); - this.pageInfoState$.next(value.pageInfo); }); - this.subs.push(this.searchSub); + }), + getAllSucceededRemoteData(), + getRemoteDataPayload(), + switchMap((groups: PaginatedList) => { + if (groups.page.length === 0) { + return observableOf(buildPaginatedList(groups.pageInfo, [])); + } + return observableCombineLatest(groups.page.map((group: Group) => { + if (!this.deletedGroupsIds.includes(group.id)) { + return observableCombineLatest([ + this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), + this.hasLinkedDSO(group), + this.getSubgroups(group), + this.getMembers(group) + ]).pipe( + map(([isAuthorized, hasLinkedDSO, subgroups, members]: + [boolean, boolean, RemoteData>, RemoteData>]) => { + const groupDtoModel: GroupDtoModel = new GroupDtoModel(); + groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; + groupDtoModel.group = group; + groupDtoModel.subgroups = subgroups.payload; + groupDtoModel.epersons = members.payload; + return groupDtoModel; + } + ) + ); + } + })).pipe(map((dtos: GroupDtoModel[]) => { + return buildPaginatedList(groups.pageInfo, dtos); + })); + }) + ).subscribe((value: PaginatedList) => { + this.groupsDto$.next(value); + this.pageInfoState$.next(value.pageInfo); + }); + this.subs.push(this.searchSub); } /** diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index 74ca566029..5bed34d46b 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -23,6 +23,10 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('BitstreamFormatsComponent', () => { let comp: BitstreamFormatsComponent; @@ -30,6 +34,7 @@ describe('BitstreamFormatsComponent', () => { let bitstreamFormatService; let scheduler: TestScheduler; let notificationsServiceStub; + let paginationService; const bitstreamFormat1 = new BitstreamFormat(); bitstreamFormat1.uuid = 'test-uuid-1'; @@ -79,6 +84,10 @@ describe('BitstreamFormatsComponent', () => { ]; const mockFormatsRD = createSuccessfulRemoteDataObject(createPaginatedList(mockFormatsList)); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const initAsync = () => { notificationsServiceStub = new NotificationsServiceStub(); @@ -95,13 +104,23 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + + + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: NotificationsService, useValue: notificationsServiceStub } + { provide: NotificationsService, useValue: notificationsServiceStub }, + { provide: PaginationService, useValue: paginationService } ] }).compileComponents(); }; @@ -217,13 +236,23 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); - TestBed.configureTestingModule({ + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + + + + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: NotificationsService, useValue: notificationsServiceStub } + { provide: NotificationsService, useValue: notificationsServiceStub }, + { provide: PaginationService, useValue: paginationService } ] }).compileComponents(); } @@ -263,13 +292,22 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); - TestBed.configureTestingModule({ + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + + + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: NotificationsService, useValue: notificationsServiceStub } + { provide: NotificationsService, useValue: notificationsServiceStub }, + { provide: PaginationService, useValue: paginationService } ] }).compileComponents(); } diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts index a5a65198af..beb900c17f 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts @@ -18,11 +18,16 @@ import { NotificationsServiceStub } from '../../../shared/testing/notifications- import { RestResponse } from '../../../core/cache/response.models'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; describe('MetadataRegistryComponent', () => { let comp: MetadataRegistryComponent; let fixture: ComponentFixture; let registryService: RegistryService; + let paginationService: PaginationService; const mockSchemasList = [ { id: 1, @@ -62,6 +67,15 @@ describe('MetadataRegistryComponent', () => { }; /* tslint:enable:no-empty */ + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -69,6 +83,7 @@ describe('MetadataRegistryComponent', () => { providers: [ { provide: RegistryService, useValue: registryServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts index 8c685c7012..35522462aa 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts @@ -23,6 +23,10 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { VarDirective } from '../../../shared/utils/var.directive'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('MetadataSchemaComponent', () => { let comp: MetadataSchemaComponent; @@ -125,6 +129,16 @@ describe('MetadataSchemaComponent', () => { }) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -134,6 +148,7 @@ describe('MetadataSchemaComponent', () => { { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: Router, useValue: new RouterStub() }, + { provide: PaginationService, useValue: paginationService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts index 87473a876b..50761b9521 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts @@ -18,11 +18,16 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search- import { toRemoteData } from '../+browse-by-metadata-page/browse-by-metadata-page.component.spec'; import { VarDirective } from '../../shared/utils/var.directive'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; +import { PaginationService } from '../../core/pagination/pagination.service'; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; let fixture: ComponentFixture; let route: ActivatedRoute; + let paginationService; const mockCommunity = Object.assign(new Community(), { id: 'test-uuid', @@ -65,6 +70,16 @@ describe('BrowseByDatePageComponent', () => { detectChanges: () => fixture.detectChanges() }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -74,6 +89,7 @@ describe('BrowseByDatePageComponent', () => { { provide: BrowseService, useValue: mockBrowseService }, { provide: DSpaceObjectDataService, useValue: mockDsoService }, { provide: Router, useValue: new RouterMock() }, + { provide: PaginationService, useValue: paginationService }, { provide: ChangeDetectorRef, useValue: mockCdRef } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts index faa75af2f2..1c6336c2c8 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts @@ -14,7 +14,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { PageInfo } from '../../core/shared/page-info.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; -import { SortDirection } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { Item } from '../../core/shared/item.model'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { Community } from '../../core/shared/community.model'; @@ -22,12 +22,18 @@ import { RouterMock } from '../../shared/mocks/router.mock'; import { BrowseEntry } from '../../core/shared/browse-entry.model'; import { VarDirective } from '../../shared/utils/var.directive'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; describe('BrowseByMetadataPageComponent', () => { let comp: BrowseByMetadataPageComponent; let fixture: ComponentFixture; let browseService: BrowseService; let route: ActivatedRoute; + let paginationService: PaginationService; + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); const mockCommunity = Object.assign(new Community(), { id: 'test-uuid', @@ -82,6 +88,12 @@ describe('BrowseByMetadataPageComponent', () => { params: observableOf({}) }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf('') + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -90,6 +102,7 @@ describe('BrowseByMetadataPageComponent', () => { { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, { provide: DSpaceObjectDataService, useValue: mockDsoService }, + { provide: PaginationService, useValue: paginationService }, { provide: Router, useValue: new RouterMock() } ], schemas: [NO_ERRORS_SCHEMA] @@ -133,18 +146,23 @@ describe('BrowseByMetadataPageComponent', () => { let result: BrowseEntrySearchOptions; beforeEach(() => { - const paramsWithPaginationAndScope = { - page: 5, - pageSize: 10, - sortDirection: SortDirection.ASC, - sortField: 'fake-field', + const paramsScope = { scope: 'fake-scope' }; + const paginationOptions = Object.assign(new PaginationComponentOptions(), { + currentPage: 5, + pageSize: 10, + }); + const sortOptions = { + direction: SortDirection.ASC, + field: 'fake-field', + }; - result = browseParamsToOptions(paramsWithPaginationAndScope, Object.assign({}), Object.assign({}), 'author'); + result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author'); }); it('should return BrowseEntrySearchOptions with the correct properties', () => { + expect(result.metadataDefinition).toEqual('author'); expect(result.pagination.currentPage).toEqual(5); expect(result.pagination.pageSize).toEqual(10); diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts index 4f7b2d5255..6ae10c6696 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts @@ -18,6 +18,10 @@ import { BrowseService } from '../../core/browse/browse.service'; import { RouterMock } from '../../shared/mocks/router.mock'; import { VarDirective } from '../../shared/utils/var.directive'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; +import { PaginationService } from '../../core/pagination/pagination.service'; describe('BrowseByTitlePageComponent', () => { let comp: BrowseByTitlePageComponent; @@ -61,6 +65,16 @@ describe('BrowseByTitlePageComponent', () => { data: observableOf({ metadata: 'title' }) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -69,6 +83,7 @@ describe('BrowseByTitlePageComponent', () => { { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, { provide: DSpaceObjectDataService, useValue: mockDsoService }, + { provide: PaginationService, useValue: paginationService }, { provide: Router, useValue: new RouterMock() } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts index 13a91563c8..abb3d957bc 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts @@ -18,6 +18,10 @@ import { PageInfo } from '../../core/shared/page-info.model'; import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; @@ -113,6 +117,17 @@ describe('CommunityPageSubCollectionList Component', () => { } }; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {} + }); + themeService = getMockThemeService(); beforeEach(waitForAsync(() => { @@ -128,6 +143,7 @@ describe('CommunityPageSubCollectionList Component', () => { providers: [ { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService }, { provide: SelectableListService, useValue: {} }, { provide: ThemeService, useValue: themeService }, ], @@ -161,28 +177,4 @@ describe('CommunityPageSubCollectionList Component', () => { const subComHead = fixture.debugElement.queryAll(By.css('h2')); expect(subComHead.length).toEqual(0); }); - - it('should update list of collections on pagination change', () => { - subCollList = collections; - fixture.detectChanges(); - - const pagination = Object.create({ - pagination:{ - id: comp.pageId, - currentPage: 2, - pageSize: 5 - }, - sort: { - field: 'dc.title', - direction: 'ASC' - } - }); - comp.onPaginationChange(pagination); - fixture.detectChanges(); - - const collList = fixture.debugElement.queryAll(By.css('li')); - expect(collList.length).toEqual(2); - expect(collList[0].nativeElement.textContent).toContain('Collection 6'); - expect(collList[1].nativeElement.textContent).toContain('Collection 7'); - }); }); diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts index 21ba1b28b0..1054879f0e 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts @@ -18,6 +18,10 @@ import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { CommunityDataService } from '../../core/data/community-data.service'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; @@ -114,6 +118,16 @@ describe('CommunityPageSubCommunityListComponent Component', () => { } }; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + themeService = getMockThemeService(); beforeEach(waitForAsync(() => { @@ -129,6 +143,7 @@ describe('CommunityPageSubCommunityListComponent Component', () => { providers: [ { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService }, { provide: SelectableListService, useValue: {} }, { provide: ThemeService, useValue: themeService }, ], @@ -163,28 +178,4 @@ describe('CommunityPageSubCommunityListComponent Component', () => { const subComHead = fixture.debugElement.queryAll(By.css('h2')); expect(subComHead.length).toEqual(0); }); - - it('should update list of sub-communities on pagination change', () => { - subCommList = subcommunities; - fixture.detectChanges(); - - const pagination = Object.create({ - pagination:{ - id: comp.pageId, - currentPage: 2, - pageSize: 5 - }, - sort: { - field: 'dc.title', - direction: 'ASC' - } - }); - comp.onPaginationChange(pagination); - fixture.detectChanges(); - - const collList = fixture.debugElement.queryAll(By.css('li')); - expect(collList.length).toEqual(2); - expect(collList[0].nativeElement.textContent).toContain('SubCommunity 6'); - expect(collList[1].nativeElement.textContent).toContain('SubCommunity 7'); - }); }); diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts index 0daa0a0ae0..4a5549cf4a 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts @@ -18,6 +18,10 @@ import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { CommunityDataService } from '../../core/data/community-data.service'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { of as observableOf } from 'rxjs'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; @@ -25,6 +29,7 @@ describe('TopLevelCommunityList Component', () => { let comp: TopLevelCommunityListComponent; let fixture: ComponentFixture; let communityDataServiceStub: any; + let paginationService; let themeService; const topCommList = [Object.assign(new Community(), { @@ -104,6 +109,15 @@ describe('TopLevelCommunityList Component', () => { } }; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf('') + }); + themeService = getMockThemeService(); beforeEach(waitForAsync(() => { @@ -119,6 +133,7 @@ describe('TopLevelCommunityList Component', () => { providers: [ { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService }, { provide: SelectableListService, useValue: {} }, { provide: ThemeService, useValue: themeService }, ], @@ -143,25 +158,4 @@ describe('TopLevelCommunityList Component', () => { expect(subComList[3].nativeElement.textContent).toContain('TopCommunity 4'); expect(subComList[4].nativeElement.textContent).toContain('TopCommunity 5'); }); - - it('should update list of top-communities on pagination change', () => { - const pagination = Object.create({ - pagination: { - id: comp.pageId, - currentPage: 2, - pageSize: 5 - }, - sort: { - field: 'dc.title', - direction: 'ASC' - } - }); - comp.onPaginationChange(pagination); - fixture.detectChanges(); - - const collList = fixture.debugElement.queryAll(By.css('li')); - expect(collList.length).toEqual(2); - expect(collList[0].nativeElement.textContent).toContain('TopCommunity 6'); - expect(collList[1].nativeElement.textContent).toContain('TopCommunity 7'); - }); }); diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts index 717c5873d2..f3c50f4d13 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts @@ -16,6 +16,10 @@ import { ResponsiveColumnSizes } from '../../../../../shared/responsive-table-si import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { createPaginatedList } from '../../../../../shared/testing/utils.test'; import { RequestService } from '../../../../../core/data/request.service'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../core/data/request.models'; describe('PaginatedDragAndDropBitstreamListComponent', () => { let comp: PaginatedDragAndDropBitstreamListComponent; @@ -24,6 +28,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => { let bundleService: BundleDataService; let objectValuesPipe: ObjectValuesPipe; let requestService: RequestService; + let paginationService: PaginationService; const columnSizes = new ResponsiveTableSizes([ new ResponsiveColumnSizes(2, 2, 3, 4, 4), @@ -109,6 +114,16 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => { hasByHref$: observableOf(true) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective], @@ -116,7 +131,8 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => { { provide: ObjectUpdatesService, useValue: objectUpdatesService }, { provide: BundleDataService, useValue: bundleService }, { provide: ObjectValuesPipe, useValue: objectValuesPipe }, - { provide: RequestService, useValue: requestService } + { provide: RequestService, useValue: requestService }, + { provide: PaginationService, useValue: paginationService } ], schemas: [ NO_ERRORS_SCHEMA ] diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts index 1773a0fe74..0723354ef5 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -16,6 +16,10 @@ import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock'; import { By } from '@angular/platform-browser'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../core/data/request.models'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; describe('FullFileSectionComponent', () => { let comp: FullFileSectionComponent; @@ -52,6 +56,16 @@ describe('FullFileSectionComponent', () => { findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -64,7 +78,8 @@ describe('FullFileSectionComponent', () => { declarations: [FullFileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent], providers: [ { provide: BitstreamDataService, useValue: bitstreamDataService }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() } + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] @@ -82,39 +97,5 @@ describe('FullFileSectionComponent', () => { const fileSection = fixture.debugElement.queryAll(By.css('.file-section')); expect(fileSection.length).toEqual(6); }); - - describe('when we press the pageChange button for original bundle', () => { - beforeEach(() => { - comp.switchOriginalPage(2); - fixture.detectChanges(); - }); - - it('should give the value to the currentpage', () => { - expect(comp.originalOptions.currentPage).toBe(2); - }); - it('should call the next function on the originalCurrentPage', (done) => { - comp.originalCurrentPage$.subscribe((event) => { - expect(event).toEqual(2); - done(); - }); - }); - }); - - describe('when we press the pageChange button for license bundle', () => { - beforeEach(() => { - comp.switchLicensePage(2); - fixture.detectChanges(); - }); - - it('should give the value to the currentpage', () => { - expect(comp.licenseOptions.currentPage).toBe(2); - }); - it('should call the next function on the licenseCurrentPage', (done) => { - comp.licenseCurrentPage$.subscribe((event) => { - expect(event).toEqual(2); - done(); - }); - }); - }); }); }); diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts index 6cbab8125b..86489019d9 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts @@ -34,12 +34,18 @@ describe('MyDSpaceConfigurationService', () => { getRouteDataValue: observableOf({}) }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(defaults.pagination), + getCurrentSort: observableOf(defaults.sort), + getRouteParameterValue: observableOf('') + }); + const activatedRoute: any = new ActivatedRouteStub(); const roleService: any = new RoleServiceMock(); beforeEach(() => { - service = new MyDSpaceConfigurationService(roleService, spy, activatedRoute); + service = new MyDSpaceConfigurationService(roleService, spy, paginationService, activatedRoute); }); describe('when the scope is called', () => { @@ -102,25 +108,19 @@ describe('MyDSpaceConfigurationService', () => { describe('when getCurrentSort is called', () => { beforeEach(() => { - // service.getCurrentSort({} as any); + service.getCurrentSort('page-id', defaults.sort); }); - it('should call getQueryParameterValue on the routeService with parameter name \'sortDirection\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortDirection'); - }); - it('should call getQueryParameterValue on the routeService with parameter name \'sortField\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortField'); + it('should call getCurrentSort on the paginationService with the provided id and sort options', () => { + expect((service as any).paginationService.getCurrentSort).toHaveBeenCalledWith('page-id', defaults.sort); }); }); describe('when getCurrentPagination is called', () => { beforeEach(() => { - // service.getCurrentPagination({ currentPage: 1, pageSize: 10 } as any); + service.getCurrentPagination('page-id', defaults.pagination); }); - it('should call getQueryParameterValue on the routeService with parameter name \'page\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('page'); - }); - it('should call getQueryParameterValue on the routeService with parameter name \'pageSize\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('pageSize'); + it('should call getCurrentPagination on the paginationService with the provided id and sort options', () => { + expect((service as any).paginationService.getCurrentPagination).toHaveBeenCalledWith('page-id', defaults.pagination); }); }); @@ -152,7 +152,7 @@ describe('MyDSpaceConfigurationService', () => { describe('when subscribeToPaginatedSearchOptions is called', () => { beforeEach(() => { - (service as any).subscribeToPaginatedSearchOptions(defaults); + (service as any).subscribeToPaginatedSearchOptions('id', defaults); }); it('should call all getters it needs', () => { expect(service.getCurrentPagination).toHaveBeenCalled(); diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.ts index 19eba0fa06..82f76eb776 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.ts @@ -56,6 +56,7 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService { * * @param {roleService} roleService * @param {RouteService} routeService + * @param {PaginationService} paginationService * @param {ActivatedRoute} route */ constructor(protected roleService: RoleService, diff --git a/src/app/core/pagination/pagination.service.spec.ts b/src/app/core/pagination/pagination.service.spec.ts new file mode 100644 index 0000000000..990dcd9b90 --- /dev/null +++ b/src/app/core/pagination/pagination.service.spec.ts @@ -0,0 +1,149 @@ +import { PaginationService } from './pagination.service'; +import { RouterStub } from '../../shared/testing/router.stub'; +import { of as observableOf } from 'rxjs'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; +import { FindListOptions } from '../data/request.models'; + + +describe('PaginationService', () => { + let service: PaginationService; + let router; + let routeService; + + const defaultPagination = new PaginationComponentOptions(); + const defaultSort = new SortOptions('id', SortDirection.DESC); + const defaultFindListOptions = new FindListOptions(); + + beforeEach(() => { + router = new RouterStub(); + routeService = { + getQueryParameterValue: (param) => { + let value; + if (param.startsWith('p.')) { + value = 5; + } + if (param.startsWith('rpp.')) { + value = 10; + } + if (param.startsWith('sd.')) { + value = 'ASC'; + } + if (param.startsWith('sf.')) { + value = 'score'; + } + return observableOf(value); + } + }; + + service = new PaginationService(routeService, router); + }); + + + describe('getCurrentPagination', () => { + it('should retrieve the current pagination info from the routerService', () => { + service.getCurrentPagination('test-id', defaultPagination).subscribe((currentPagination) => { + expect(currentPagination).toEqual(Object.assign(new PaginationComponentOptions(), { + currentPage: 5, + pageSize: 10 + })); + }); + }); + }); + describe('getCurrentSort', () => { + it('should retrieve the current sort info from the routerService', () => { + service.getCurrentSort('test-id', defaultSort).subscribe((currentSort) => { + expect(currentSort).toEqual(Object.assign(new SortOptions('score', SortDirection.ASC ))); + }); + }); + }); + describe('getFindListOptions', () => { + it('should retrieve the current findListOptions info from the routerService', () => { + service.getFindListOptions('test-id', defaultFindListOptions).subscribe((findListOptions) => { + expect(findListOptions).toEqual(Object.assign(new FindListOptions(), + { + sort: new SortOptions('score', SortDirection.ASC ), + currentPage: 5, + elementsPerPage: 10 + })); + }); + }); + }); + describe('resetPage', () => { + it('should call the updateRoute method with the id and page 1', () => { + spyOn(service, 'updateRoute'); + service.resetPage('test'); + + expect(service.updateRoute).toHaveBeenCalledWith('test', {page: 1}); + }); + }); + + describe('updateRoute', () => { + it('should update the route with the provided page params', () => { + service.updateRoute('test', {page: 2, pageSize: 5, sortField: 'title', sortDirection: SortDirection.DESC}); + + const navigateParams = {}; + navigateParams[`p.test`] = `2`; + navigateParams[`rpp.test`] = `5`; + navigateParams[`sf.test`] = `title`; + navigateParams[`sd.test`] = `DESC`; + + expect(router.navigate).toHaveBeenCalledWith([], {queryParams: navigateParams, queryParamsHandling: 'merge'}); + }); + it('should update the route with the provided page params while keeping the existing non provided ones', () => { + service.updateRoute('test', {page: 2}); + + const navigateParams = {}; + navigateParams[`p.test`] = `2`; + navigateParams[`rpp.test`] = `10`; + navigateParams[`sf.test`] = `score`; + navigateParams[`sd.test`] = `ASC`; + + expect(router.navigate).toHaveBeenCalledWith([], {queryParams: navigateParams, queryParamsHandling: 'merge'}); + }); + }); + describe('updateRouteWithUrl', () => { + it('should update the route with the provided page params and url', () => { + service.updateRouteWithUrl('test', ['someUrl'], {page: 2, pageSize: 5, sortField: 'title', sortDirection: SortDirection.DESC}); + + const navigateParams = {}; + navigateParams[`p.test`] = `2`; + navigateParams[`rpp.test`] = `5`; + navigateParams[`sf.test`] = `title`; + navigateParams[`sd.test`] = `DESC`; + + expect(router.navigate).toHaveBeenCalledWith(['someUrl'], {queryParams: navigateParams, queryParamsHandling: 'merge'}); + }); + it('should update the route with the provided page params and url while keeping the existing non provided ones', () => { + service.updateRouteWithUrl('test',['someUrl'], {page: 2}); + + const navigateParams = {}; + navigateParams[`p.test`] = `2`; + navigateParams[`rpp.test`] = `10`; + navigateParams[`sf.test`] = `score`; + navigateParams[`sd.test`] = `ASC`; + + expect(router.navigate).toHaveBeenCalledWith(['someUrl'], {queryParams: navigateParams, queryParamsHandling: 'merge'}); + }); + + }); + describe('clearPagination', () => { + it('should clear the pagination info from the route for the current id', () => { + service.clearPagination('test'); + + const params = {}; + params[`p.test`] = null; + params[`rpp.test`] = null; + params[`sf.test`] = null; + params[`sd.test`] = null; + + expect(router.navigate).toHaveBeenCalledWith([], {queryParams: params, queryParamsHandling: 'merge'}); + }); + }); + describe('getPageParam', () => { + it('should return the name of the page param', () => { + const pageParam = service.getPageParam('test'); + expect(pageParam).toEqual('p.test'); + }); + }); +}); diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index 0143d20923..29e57214c1 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Router } from '@angular/router'; import { RouteService } from '../services/route.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; @@ -9,7 +9,6 @@ import { FindListOptions } from '../data/request.models'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { difference } from '../../shared/object.util'; import { isNumeric } from 'rxjs/internal-compatibility'; -import { Location } from '@angular/common'; @Injectable({ @@ -20,9 +19,7 @@ export class PaginationService { private defaultSortOptions = new SortOptions('id', SortDirection.ASC); constructor(protected routeService: RouteService, - protected route: ActivatedRoute, - protected router: Router, - protected location: Location + protected router: Router ) { } @@ -33,6 +30,7 @@ export class PaginationService { const page$ = this.routeService.getQueryParameterValue(`p.${paginationId}`); const size$ = this.routeService.getQueryParameterValue(`rpp.${paginationId}`); return observableCombineLatest([page$, size$]).pipe(map(([page, size]) => { + console.log(page, size); return Object.assign(new PaginationComponentOptions(), defaultPagination, { currentPage: this.convertToNumeric(page, defaultPagination.currentPage), pageSize: this.getBestMatchPageSize(size, defaultPagination) @@ -80,7 +78,7 @@ export class PaginationService { this.updateRoute(paginationId, {page: 1}); } - getCurrentRouting(paginationId: string) { + private getCurrentRouting(paginationId: string) { return this.getFindListOptions(paginationId, {}, true).pipe( take(1), map((findListoptions: FindListOptions) => { @@ -88,7 +86,7 @@ export class PaginationService { page: findListoptions.currentPage, pageSize: findListoptions.elementsPerPage, sortField: findListoptions.sort.field, - sortDir: findListoptions.sort.direction, + sortDirection: findListoptions.sort.direction, }; }) ); @@ -101,12 +99,12 @@ export class PaginationService { sortDirection?: SortDirection }, extraParams?, retainScrollPosition?: boolean) { this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + console.log('currentFindListOptions',currentFindListOptions ); const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); const parametersWithIdName = this.getParametersWithIdName(paginationId, params); if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { const queryParams = Object.assign({}, currentParametersWithIdName, parametersWithIdName, extraParams); - console.log(retainScrollPosition); if (retainScrollPosition) { this.router.navigate([], { queryParams: queryParams, @@ -169,7 +167,7 @@ export class PaginationService { return `p.${paginationId}`; } - getParametersWithIdName(paginationId: string, params: { + private getParametersWithIdName(paginationId: string, params: { page?: number pageSize?: number sortField?: string diff --git a/src/app/core/shared/search/search-configuration.service.spec.ts b/src/app/core/shared/search/search-configuration.service.spec.ts index 43c2c54427..f1f9dc7b9b 100644 --- a/src/app/core/shared/search/search-configuration.service.spec.ts +++ b/src/app/core/shared/search/search-configuration.service.spec.ts @@ -15,7 +15,7 @@ describe('SearchConfigurationService', () => { 'f.date.max': ['2018'] }; const defaults = new PaginatedSearchOptions({ - pagination: Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }), + pagination: Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 }), sort: new SortOptions('score', SortDirection.DESC), configuration: 'default', query: '', @@ -30,10 +30,17 @@ describe('SearchConfigurationService', () => { getRouteParameterValue: observableOf('') }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(defaults.pagination), + getCurrentSort: observableOf(defaults.sort), + getRouteParameterValue: observableOf('') + }); + + const activatedRoute: any = new ActivatedRouteStub(); beforeEach(() => { - service = new SearchConfigurationService(routeService, activatedRoute); + service = new SearchConfigurationService(routeService, paginationService, activatedRoute); }); describe('when the scope is called', () => { beforeEach(() => { @@ -95,25 +102,19 @@ describe('SearchConfigurationService', () => { describe('when getCurrentSort is called', () => { beforeEach(() => { - service.getCurrentSort({} as any); + service.getCurrentSort(defaults.pagination.id, {} as any); }); - it('should call getQueryParameterValue on the routeService with parameter name \'sortDirection\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortDirection'); - }); - it('should call getQueryParameterValue on the routeService with parameter name \'sortField\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortField'); + it('should call getCurrentSort on the paginationService with the provided id and sort options', () => { + expect((service as any).paginationService.getCurrentSort).toHaveBeenCalledWith(defaults.pagination.id, {}); }); }); describe('when getCurrentPagination is called', () => { beforeEach(() => { - service.getCurrentPagination({ currentPage: 1, pageSize: 10 } as any); + service.getCurrentPagination(defaults.pagination.id, defaults.pagination); }); - it('should call getQueryParameterValue on the routeService with parameter name \'page\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('page'); - }); - it('should call getQueryParameterValue on the routeService with parameter name \'pageSize\'', () => { - expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('pageSize'); + it('should call getCurrentPagination on the paginationService with the provided id and sort options', () => { + expect((service as any).paginationService.getCurrentPagination).toHaveBeenCalledWith(defaults.pagination.id, defaults.pagination); }); }); @@ -145,7 +146,7 @@ describe('SearchConfigurationService', () => { describe('when subscribeToPaginatedSearchOptions is called', () => { beforeEach(() => { - (service as any).subscribeToPaginatedSearchOptions(defaults); + (service as any).subscribeToPaginatedSearchOptions(defaults.pagination.id, defaults); }); it('should call all getters it needs', () => { expect(service.getCurrentPagination).toHaveBeenCalled(); diff --git a/src/app/core/shared/search/search.service.spec.ts b/src/app/core/shared/search/search.service.spec.ts index 06208094bd..9eb650219b 100644 --- a/src/app/core/shared/search/search.service.spec.ts +++ b/src/app/core/shared/search/search.service.spec.ts @@ -22,6 +22,11 @@ import { routeServiceStub } from '../../../shared/testing/route-service.stub'; import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { SearchObjects } from '../../../shared/search/search-objects.model'; +import { PaginationService } from '../../pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../cache/models/sort-options.model'; +import { FindListOptions } from '../../data/request.models'; +import { SearchConfigurationService } from './search-configuration.service'; @Component({ template: '' }) class DummyComponent { @@ -32,6 +37,7 @@ describe('SearchService', () => { let searchService: SearchService; const router = new RouterStub(); const route = new ActivatedRouteStub(); + const searchConfigService = {paginationID: 'page-id'}; beforeEach(() => { TestBed.configureTestingModule({ imports: [ @@ -51,6 +57,8 @@ describe('SearchService', () => { { provide: HALEndpointService, useValue: {} }, { provide: CommunityDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} }, + { provide: PaginationService, useValue: {} }, + { provide: SearchConfigurationService, useValue: searchConfigService }, SearchService ], }); @@ -94,6 +102,18 @@ describe('SearchService', () => { } }; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {} + }); + const searchConfigService = {paginationID: 'page-id'}; + beforeEach(() => { TestBed.configureTestingModule({ imports: [ @@ -113,6 +133,8 @@ describe('SearchService', () => { { provide: HALEndpointService, useValue: halService }, { provide: CommunityDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} }, + { provide: PaginationService, useValue: paginationService }, + { provide: SearchConfigurationService, useValue: searchConfigService }, SearchService ], }); @@ -124,18 +146,14 @@ describe('SearchService', () => { it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => { searchService.setViewMode(ViewMode.ListElement); - expect(router.navigate).toHaveBeenCalledWith(['/search'], { - queryParams: { view: ViewMode.ListElement, page: 1 }, - queryParamsHandling: 'merge' - }); + expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], {page: 1}, { view: ViewMode.ListElement } + ); }); it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => { searchService.setViewMode(ViewMode.GridElement); - expect(router.navigate).toHaveBeenCalledWith(['/search'], { - queryParams: { view: ViewMode.GridElement, page: 1 }, - queryParamsHandling: 'merge' - }); + expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], {page: 1}, { view: ViewMode.GridElement } + ); }); it('should return ViewMode.List when the viewMode is set to ViewMode.List in the ActivatedRoute', () => { diff --git a/src/app/process-page/overview/process-overview.component.spec.ts b/src/app/process-page/overview/process-overview.component.spec.ts index ffbcbb10cf..1da2ac2b5b 100644 --- a/src/app/process-page/overview/process-overview.component.spec.ts +++ b/src/app/process-page/overview/process-overview.component.spec.ts @@ -12,6 +12,11 @@ import { By } from '@angular/platform-browser'; import { ProcessStatus } from '../processes/process-status.model'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createPaginatedList } from '../../shared/testing/utils.test'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; describe('ProcessOverviewComponent', () => { let component: ProcessOverviewComponent; @@ -19,10 +24,15 @@ describe('ProcessOverviewComponent', () => { let processService: ProcessDataService; let ePersonService: EPersonDataService; + let paginationService: PaginationService; let processes: Process[]; let ePerson: EPerson; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + function init() { processes = [ Object.assign(new Process(), { @@ -69,6 +79,12 @@ describe('ProcessOverviewComponent', () => { ePersonService = jasmine.createSpyObj('ePersonService', { findById: createSuccessfulRemoteDataObject$(ePerson) }); + + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + }); } beforeEach(waitForAsync(() => { @@ -78,7 +94,8 @@ describe('ProcessOverviewComponent', () => { imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], providers: [ { provide: ProcessDataService, useValue: processService }, - { provide: EPersonDataService, useValue: ePersonService } + { provide: EPersonDataService, useValue: ePersonService }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -143,16 +160,4 @@ describe('ProcessOverviewComponent', () => { }); }); }); - - describe('onPageChange', () => { - const toPage = 2; - - beforeEach(() => { - component.onPageChange(toPage); - }); - - it('should call a new findAll with the corresponding page', () => { - expect(processService.findAll).toHaveBeenCalledWith(jasmine.objectContaining({ currentPage: toPage })); - }); - }); }); diff --git a/src/app/search-navbar/search-navbar.component.spec.ts b/src/app/search-navbar/search-navbar.component.spec.ts index 9eef81a42e..87b75328bb 100644 --- a/src/app/search-navbar/search-navbar.component.spec.ts +++ b/src/app/search-navbar/search-navbar.component.spec.ts @@ -8,6 +8,11 @@ import { SearchService } from '../core/shared/search/search.service'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; import { SearchNavbarComponent } from './search-navbar.component'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; describe('SearchNavbarComponent', () => { let component: SearchNavbarComponent; @@ -15,6 +20,7 @@ describe('SearchNavbarComponent', () => { let mockSearchService: any; let router: Router; let routerStub; + let paginationService; beforeEach(waitForAsync(() => { mockSearchService = { @@ -26,6 +32,18 @@ describe('SearchNavbarComponent', () => { routerStub = { navigate: (commands) => commands }; + + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf(''), + updateRouteWithUrl: {}, + clearPagination : {} + }); + TestBed.configureTestingModule({ imports: [ FormsModule, @@ -40,7 +58,9 @@ describe('SearchNavbarComponent', () => { declarations: [SearchNavbarComponent], providers: [ { provide: SearchService, useValue: mockSearchService }, - { provide: Router, useValue: routerStub } + { provide: PaginationService, useValue: paginationService }, + { provide: Router, useValue: routerStub }, + { provide: SearchConfigurationService, useValue: {paginationID: 'page-id'} } ] }) .compileComponents(); @@ -88,7 +108,7 @@ describe('SearchNavbarComponent', () => { })); it('to search page with empty query', () => { expect(component.onSubmit).toHaveBeenCalledWith({ query: '' }); - expect(router.navigate).toHaveBeenCalled(); + expect(paginationService.updateRouteWithUrl).toHaveBeenCalled(); }); }); }); @@ -112,7 +132,7 @@ describe('SearchNavbarComponent', () => { })); it('to search page with query', async () => { expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' }); - expect(router.navigate).toHaveBeenCalled(); + expect(paginationService.updateRouteWithUrl).toHaveBeenCalled(); }); }); }); diff --git a/src/app/shared/browse-by/browse-by.component.spec.ts b/src/app/shared/browse-by/browse-by.component.spec.ts index 9f3ceade1a..4c9ac9204d 100644 --- a/src/app/shared/browse-by/browse-by.component.spec.ts +++ b/src/app/shared/browse-by/browse-by.component.spec.ts @@ -18,6 +18,8 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { storeModuleConfig } from '../../app.reducer'; +import { FindListOptions } from '../../core/data/request.models'; +import { PaginationService } from '../../core/pagination/pagination.service'; describe('BrowseByComponent', () => { let comp: BrowseByComponent; @@ -45,6 +47,22 @@ describe('BrowseByComponent', () => { ]; const mockItemsRD$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), mockItems)); + const paginationConfig = Object.assign(new PaginationComponentOptions(), { + id: 'test-pagination', + currentPage: 1, + pageSizeOptions: [5, 10, 15, 20], + pageSize: 15 + }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(paginationConfig), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + updateRoute: {}, + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -63,7 +81,9 @@ describe('BrowseByComponent', () => { BrowserAnimationsModule ], declarations: [], - providers: [], + providers: [ + {provide: PaginationService, useValue: paginationService} + ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); @@ -95,12 +115,8 @@ describe('BrowseByComponent', () => { beforeEach(() => { comp.enableArrows = true; comp.objects$ = mockItemsRD$; - comp.paginationConfig = Object.assign(new PaginationComponentOptions(), { - id: 'test-pagination', - currentPage: 1, - pageSizeOptions: [5, 10, 15, 20], - pageSize: 15 - }); + + comp.paginationConfig = paginationConfig; comp.sortConfig = Object.assign(new SortOptions('dc.title', SortDirection.ASC)); fixture.detectChanges(); }); @@ -136,8 +152,8 @@ describe('BrowseByComponent', () => { fixture.detectChanges(); }); - it('should emit a signal to the EventEmitter', () => { - expect(comp.pageSizeChange.emit).toHaveBeenCalled(); + it('should call the updateRoute method from the paginationService', () => { + expect(paginationService.updateRoute).toHaveBeenCalledWith('test-pagination', {pageSize: paginationConfig.pageSizeOptions[0]}); }); }); @@ -148,8 +164,8 @@ describe('BrowseByComponent', () => { fixture.detectChanges(); }); - it('should emit a signal to the EventEmitter', () => { - expect(comp.sortDirectionChange.emit).toHaveBeenCalled(); + it('should call the updateRoute method from the paginationService', () => { + expect(paginationService.updateRoute).toHaveBeenCalledWith('test-pagination', {sortDirection: 'ASC'}); }); }); }); diff --git a/src/app/shared/browse-by/browse-by.component.ts b/src/app/shared/browse-by/browse-by.component.ts index 8920726c8b..1f05ad2258 100644 --- a/src/app/shared/browse-by/browse-by.component.ts +++ b/src/app/shared/browse-by/browse-by.component.ts @@ -144,7 +144,7 @@ export class BrowseByComponent implements OnInit { this.objectInjector = Injector.create({ providers: [ { provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }, - { provide: 'paginationId', useFactory: () => (this.paginationConfig.id), deps:[] } + { provide: 'paginationId', useFactory: () => (this.paginationConfig?.id), deps:[] } ], parent: this.injector }); diff --git a/src/app/shared/item/item-versions/item-versions.component.spec.ts b/src/app/shared/item/item-versions/item-versions.component.spec.ts index f35ec1993d..33e99eb88f 100644 --- a/src/app/shared/item/item-versions/item-versions.component.spec.ts +++ b/src/app/shared/item/item-versions/item-versions.component.spec.ts @@ -11,11 +11,18 @@ import { VersionHistoryDataService } from '../../../core/data/version-history-da import { By } from '@angular/platform-browser'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { createPaginatedList } from '../../testing/utils.test'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('ItemVersionsComponent', () => { let component: ItemVersionsComponent; let fixture: ComponentFixture; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const versionHistory = Object.assign(new VersionHistory(), { id: '1' }); @@ -52,12 +59,19 @@ describe('ItemVersionsComponent', () => { getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions)) }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf('') + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ItemVersionsComponent, VarDirective], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], providers: [ - { provide: VersionHistoryDataService, useValue: versionHistoryService } + { provide: VersionHistoryDataService, useValue: versionHistoryService }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -107,16 +121,4 @@ describe('ItemVersionsComponent', () => { expect(summary.nativeElement.textContent).toEqual(version.summary); }); }); - - describe('switchPage', () => { - const page = 5; - - beforeEach(() => { - component.switchPage(page); - }); - - it('should set the option\'s currentPage to the new page', () => { - expect(component.options.currentPage).toEqual(page); - }); - }); }); diff --git a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts index fd4a239cc4..60cce1ae09 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -13,6 +13,10 @@ import { CollectionSelectComponent } from './collection-select.component'; import { Collection } from '../../../core/shared/collection.model'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { createPaginatedList } from '../../testing/utils.test'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('CollectionSelectComponent', () => { let comp: CollectionSelectComponent; @@ -36,13 +40,24 @@ describe('CollectionSelectComponent', () => { currentPage: 1 }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockCollectionList[1].id]) }, - { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index f99991f391..2ca3cd4fa8 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -11,14 +11,18 @@ import { HostWindowService } from '../../host-window.service'; import { HostWindowServiceStub } from '../../testing/host-window-service.stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of } from 'rxjs'; +import { of as observableOf, of } from 'rxjs'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { createPaginatedList } from '../../testing/utils.test'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; + let paginationService: PaginationService; const mockItemList = [ Object.assign(new Item(), { @@ -59,13 +63,25 @@ describe('ItemSelectComponent', () => { currentPage: 1 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(mockPaginationOptions), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + + + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, - { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/page-size-selector/page-size-selector.component.spec.ts b/src/app/shared/page-size-selector/page-size-selector.component.spec.ts index f75b5841af..cf7f1d5e11 100644 --- a/src/app/shared/page-size-selector/page-size-selector.component.spec.ts +++ b/src/app/shared/page-size-selector/page-size-selector.component.spec.ts @@ -12,6 +12,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { EnumKeysPipe } from '../utils/enum-keys-pipe'; import { VarDirective } from '../utils/var.directive'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; +import { PaginationService } from '../../core/pagination/pagination.service'; describe('PageSizeSelectorComponent', () => { @@ -33,6 +34,12 @@ describe('PageSizeSelectorComponent', () => { sort }; + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf('') + }); + const activatedRouteStub = { queryParams: observableOf({ query: queryParam, @@ -46,6 +53,7 @@ describe('PageSizeSelectorComponent', () => { declarations: [PageSizeSelectorComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: PaginationService, useValue: paginationService }, { provide: SEARCH_CONFIG_SERVICE, useValue: { diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts index 905b739270..4c29e2af9d 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts @@ -11,6 +11,9 @@ import { PaginationComponent } from '../pagination/pagination.component'; import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; import { createPaginatedList } from '../testing/utils.test'; import { ObjectValuesPipe } from '../utils/object-values-pipe'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; @Component({ selector: 'ds-mock-paginated-drag-drop-abstract', @@ -22,8 +25,9 @@ class MockAbstractPaginatedDragAndDropListComponent extends AbstractPaginatedDra protected elRef: ElementRef, protected objectValuesPipe: ObjectValuesPipe, protected mockUrl: string, - protected mockObjectsRD$: Observable>>) { - super(objectUpdatesService, elRef, objectValuesPipe); + protected paginationService: PaginationService, + protected mockObjectsRD$: Observable>>) { + super(objectUpdatesService, elRef, objectValuesPipe, paginationService); } initializeObjectsRD(): void { @@ -43,10 +47,14 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { const url = 'mock-abstract-paginated-drag-and-drop-list-component'; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const object1 = Object.assign(new DSpaceObject(), { uuid: 'object-1' }); const object2 = Object.assign(new DSpaceObject(), { uuid: 'object-2' }); const objectsRD = createSuccessfulRemoteDataObject(createPaginatedList([object1, object2])); let objectsRD$: BehaviorSubject>>; + let paginationService; const updates = { [object1.uuid]: { field: object1, changeType: undefined }, @@ -69,8 +77,13 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { paginationComponent = jasmine.createSpyObj('paginationComponent', { doPageChange: {} }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getRouteParameterValue: observableOf('') + }); objectsRD$ = new BehaviorSubject(objectsRD); - component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, objectValuesPipe, url, objectsRD$); + component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, objectValuesPipe, url, paginationService, objectsRD$); component.paginationComponent = paginationComponent; component.ngOnInit(); }); @@ -86,18 +99,6 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { }); }); - describe('switchPage', () => { - const page = 3; - - beforeEach(() => { - component.switchPage(page); - }); - - it('should set currentPage$ to the new page', () => { - expect(component.currentPage$.value).toEqual(page); - }); - }); - describe('drop', () => { const event = { previousIndex: 0, diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 684ffb55e8..bad54d6e83 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -31,10 +31,15 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { createTestComponent } from '../testing/utils.test'; import { storeModuleConfig } from '../../app.reducer'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { FindListOptions } from '../../core/data/request.models'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; function expectPages(fixture: ComponentFixture, pagesDef: string[]): void { const de = fixture.debugElement.query(By.css('.pagination')); const pages = de.nativeElement.querySelectorAll('li'); + console.log('pages', pages.length, pagesDef.length); + console.log(pages); expect(pages.length).toEqual(pagesDef.length); @@ -105,14 +110,39 @@ describe('Pagination component', () => { let activatedRouteStub: MockActivatedRoute; let routerStub: RouterMock; + let paginationService: PaginationService; + // Define initial state and test state const _initialState = { width: 1600, height: 770 }; + const pagination = new PaginationComponentOptions(); + pagination.currentPage = 1; + pagination.pageSize = 10; + + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 10 }); + let currentPagination; + let currentSort; + let currentFindListOptions; + // waitForAsync beforeEach beforeEach(waitForAsync(() => { activatedRouteStub = new MockActivatedRoute(); - routerStub = new RouterMock(); - hostWindowServiceStub = new HostWindowServiceMock(_initialState.width); + routerStub = new RouterMock(); hostWindowServiceStub = new HostWindowServiceMock(_initialState.width); + + currentPagination = new BehaviorSubject(pagination); + currentSort = new BehaviorSubject(sort); + currentFindListOptions = new BehaviorSubject(findlistOptions); + + + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: currentPagination, + getCurrentSort: currentSort, + getFindListOptions: currentFindListOptions, + resetPage: {}, + updateRoute: {}, + updateRouteWithUrl: {} + }); TestBed.configureTestingModule({ imports: [ @@ -138,6 +168,7 @@ describe('Pagination component', () => { { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, { provide: HostWindowService, useValue: hostWindowServiceStub }, + { provide: PaginationService, useValue: paginationService }, ChangeDetectorRef, PaginationComponent ], @@ -179,11 +210,17 @@ describe('Pagination component', () => { it('should render and respond to page change', () => { testComp.collectionSize = 30; + testFixture.detectChanges(); + + + currentPagination.next(Object.assign(new PaginationComponentOptions(), pagination, {currentPage: 3})); + testFixture.detectChanges(); - changePage(testFixture, 3); expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']); - changePage(testFixture, 0); + currentPagination.next(Object.assign(new PaginationComponentOptions(), pagination, {currentPage: 2})); + testFixture.detectChanges(); + expectPages(testFixture, ['« Previous', '1', '+2', '3', '» Next']); }); @@ -205,58 +242,41 @@ describe('Pagination component', () => { testFixture.detectChanges(); expectPages(testFixture, ['-« Previous', '+1', '2', '3', '» Next']); - paginationComponent.setPageSize(5); + currentPagination.next(Object.assign(new PaginationComponentOptions(), pagination, {pageSize: 5})); testFixture.detectChanges(); + expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '6', '» Next']); - paginationComponent.setPageSize(10); + currentPagination.next(Object.assign(new PaginationComponentOptions(), pagination, {pageSize: 10})); testFixture.detectChanges(); expectPages(testFixture, ['-« Previous', '+1', '2', '3', '» Next']); - paginationComponent.setPageSize(20); + currentPagination.next(Object.assign(new PaginationComponentOptions(), pagination, {pageSize: 20})); testFixture.detectChanges(); expectPages(testFixture, ['-« Previous', '+1', '2', '» Next']); }); - it('should emit pageChange event with correct value', fakeAsync(() => { - const paginationComponent: PaginationComponent = testFixture.debugElement.query(By.css('ds-pagination')).references.p; - - spyOn(testComp, 'pageChanged'); - - paginationComponent.setPage(3); - tick(); - - expect(testComp.pageChanged).toHaveBeenCalledWith(3); - })); - it('should emit pageSizeChange event with correct value', fakeAsync(() => { const paginationComponent: PaginationComponent = testFixture.debugElement.query(By.css('ds-pagination')).references.p; spyOn(testComp, 'pageSizeChanged'); - paginationComponent.setPageSize(5); + testComp.pageSizeChanged(5); tick(); expect(testComp.pageSizeChanged).toHaveBeenCalledWith(5); })); - it('should set correct page route parameters', fakeAsync(() => { - routerStub = testFixture.debugElement.injector.get(Router) as any; - + it('should call the updateRoute method on the paginationService with the correct params', fakeAsync(() => { testComp.collectionSize = 60; changePage(testFixture, 3); tick(); - expect(routerStub.navigate).toHaveBeenCalledWith([], { - queryParams: { - pageId: 'test', - page: '3', - pageSize: 10, - sortDirection: 'ASC', - sortField: 'dc.title' - }, queryParamsHandling: 'merge' - }); + expect(paginationService.updateRoute).toHaveBeenCalledWith('test', Object.assign({ page: '3'}), {}, false); + changePage(testFixture, 0); + tick(); + expect(paginationService.updateRoute).toHaveBeenCalledWith('test', Object.assign({ page: '2'}), {}, false); })); it('should set correct pageSize route parameters', fakeAsync(() => { @@ -266,58 +286,9 @@ describe('Pagination component', () => { changePageSize(testFixture, '20'); tick(); - expect(routerStub.navigate).toHaveBeenCalledWith([], { - queryParams: { - pageId: 'test', - page: 1, - pageSize: 20, - sortDirection: 'ASC', - sortField: 'dc.title' - }, queryParamsHandling: 'merge' - }); + expect(paginationService.updateRoute).toHaveBeenCalledWith('test', Object.assign({ pageId: 'test', page: 1, pageSize: 20}), {}, false); })); - it('should set correct values', fakeAsync(() => { - const paginationComponent: PaginationComponent = testFixture.debugElement.query(By.css('ds-pagination')).references.p; - routerStub = testFixture.debugElement.injector.get(Router) as any; - - testComp.collectionSize = 60; - - paginationComponent.setPage(3); - expect(paginationComponent.currentPage).toEqual(3); - - paginationComponent.setPageSize(20); - expect(paginationComponent.pageSize).toEqual(20); - })); - - it('should get parameters from route', () => { - - activatedRouteStub = testFixture.debugElement.injector.get(ActivatedRoute) as any; - activatedRouteStub.testParams = { - pageId: 'test', - page: 2, - pageSize: 20 - }; - - testFixture.detectChanges(); - - expectPages(testFixture, ['« Previous', '1', '+2', '3', '4', '5', '» Next']); - expect(testComp.paginationOptions.currentPage).toEqual(2); - expect(testComp.paginationOptions.pageSize).toEqual(20); - - activatedRouteStub.testParams = { - pageId: 'test', - page: 3, - pageSize: 40 - }; - - testFixture.detectChanges(); - - expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']); - expect(testComp.paginationOptions.currentPage).toEqual(3); - expect(testComp.paginationOptions.pageSize).toEqual(40); - }); - it('should respond to windows resize', () => { const paginationComponent: PaginationComponent = testFixture.debugElement.query(By.css('ds-pagination')).references.p; hostWindowServiceStub = testFixture.debugElement.injector.get(HostWindowService) as any; diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index d126565e8f..4a181d7090 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -20,6 +20,7 @@ import { hasValue } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; +import { tap } from 'rxjs/internal/operators/tap'; /** * The default pagination controls component. @@ -194,11 +195,14 @@ export class PaginationComponent implements OnDestroy, OnInit { this.id = this.paginationOptions.id || null; this.pageSizeOptions = this.paginationOptions.pageSizeOptions; this.currentPage$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe( + tap((v) => console.log('currentPage', v)), map((currentPagination) => currentPagination.currentPage) ); this.pageSize$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe( map((currentPagination) => currentPagination.pageSize) ); + this.pageSize$.subscribe((v) => console.log('this.pageSize$', v)); + this.currentPage$.subscribe((v) => console.log('this.currentPage$', v)); let sortOptions; if (this.sortOptions) { @@ -237,6 +241,7 @@ export class PaginationComponent implements OnDestroy, OnInit { */ public doPageChange(page: number) { this.updateParams({page: page.toString()}); + this.emitPaginationChange(); } /** @@ -247,6 +252,7 @@ export class PaginationComponent implements OnDestroy, OnInit { */ public doPageSizeChange(pageSize: number) { this.updateParams({ pageId: this.id, page: 1, pageSize: pageSize }); + this.emitPaginationChange(); } /** @@ -257,6 +263,7 @@ export class PaginationComponent implements OnDestroy, OnInit { */ public doSortDirectionChange(sortDirection: SortDirection) { this.updateParams({ pageId: this.id, page: 1, sortDirection: sortDirection }); + this.emitPaginationChange(); } /** @@ -267,50 +274,6 @@ export class PaginationComponent implements OnDestroy, OnInit { */ public doSortFieldChange(field: string) { this.updateParams({ pageId: this.id, page: 1, sortField: field }); - } - - /** - * Method to set the current page and trigger page change events - * - * @param page - * The new page value - */ - public setPage(page: number) { - this.pageChange.emit(page); - this.emitPaginationChange(); - } - - /** - * Method to set the current page size and trigger page size change events - * - * @param pageSize - * The new page size value. - */ - public setPageSize(pageSize: number) { - this.pageSizeChange.emit(pageSize); - this.emitPaginationChange(); - } - - /** - * Method to set the current sort direction and trigger sort direction change events - * - * @param sortDirection - * The new sort directionvalue. - */ - public setSortDirection(sortDirection: SortDirection) { - this.sortDirectionChange.emit(sortDirection); - this.emitPaginationChange(); - } - - /** - * Method to set the current sort field and trigger sort field change events - * - * @param sortField - * The new sort field. - */ - public setSortField(field: string) { - // this.sortField = field; - this.sortFieldChange.emit(field); this.emitPaginationChange(); } @@ -370,21 +333,6 @@ export class PaginationComponent implements OnDestroy, OnInit { } } - /** - * Method to check if none of the query params necessary for pagination are filled out. - * - * @param paginateOptions - * The paginate options object. - */ - private isEmptyPaginationParams(paginateOptions): boolean { - const properties = ['id', 'currentPage', 'pageSize', 'pageSizeOptions']; - const missing = properties.filter((prop) => { - return !(prop in paginateOptions); - }); - - return properties.length === missing.length; - } - /** * Property to check whether the current pagination object has multiple pages * @returns true if there are multiple pages, else returns false diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts index db01146bfe..a683a536b4 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts @@ -19,6 +19,9 @@ import { PaginationComponentOptions } from '../../../pagination/pagination-compo import { buildPaginatedList } from '../../../../core/data/paginated-list.model'; import { PageInfo } from '../../../../core/shared/page-info.model'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../core/data/request.models'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; describe('EpersonGroupListComponent test suite', () => { let comp: EpersonGroupListComponent; @@ -27,6 +30,7 @@ describe('EpersonGroupListComponent test suite', () => { let de; let groupService: any; let epersonService: any; + let paginationService; const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions(); paginationOptions.id = uniqueId('eperson-group-list-pagination-test'); @@ -60,6 +64,16 @@ describe('EpersonGroupListComponent test suite', () => { const groupPaginatedList = buildPaginatedList(new PageInfo(), [GroupMock, GroupMock]); const groupPaginatedListRD = createSuccessfulRemoteDataObject(groupPaginatedList); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(paginationOptions), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + clearPagination: {} + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -74,6 +88,7 @@ describe('EpersonGroupListComponent test suite', () => { { provide: EPersonDataService, useValue: mockEpersonService }, { provide: GroupDataService, useValue: mockGroupService }, { provide: RequestService, useValue: getMockRequestService() }, + { provide: PaginationService, useValue: paginationService }, EpersonGroupListComponent, ChangeDetectorRef, Injector @@ -177,12 +192,6 @@ describe('EpersonGroupListComponent test suite', () => { a: false })); }); - - it('should update list on page change', () => { - spyOn(comp, 'updateList'); - - expect(compAsAny.updateList).toHaveBeenCalled(); - }); }); describe('when is list of group', () => { @@ -254,12 +263,6 @@ describe('EpersonGroupListComponent test suite', () => { })); }); - it('should update list on page change', () => { - spyOn(comp, 'updateList'); - - expect(compAsAny.updateList).toHaveBeenCalled(); - }); - it('should update list on search triggered', () => { const options: PaginationComponentOptions = comp.paginationOptions; const event: SearchEvent = { @@ -269,7 +272,7 @@ describe('EpersonGroupListComponent test suite', () => { spyOn(comp, 'updateList'); comp.onSearch(event); - expect(compAsAny.updateList).toHaveBeenCalledWith(options, 'metadata', 'test'); + expect(compAsAny.updateList).toHaveBeenCalledWith('metadata', 'test'); }); }); }); diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index f2293afeca..635d11f0d0 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -8,6 +8,12 @@ import { Community } from '../../core/shared/community.model'; import { TranslateModule } from '@ngx-translate/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { SearchService } from '../../core/shared/search/search.service'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; describe('SearchFormComponent', () => { let comp: SearchFormComponent; @@ -15,6 +21,19 @@ describe('SearchFormComponent', () => { let de: DebugElement; let el: HTMLElement; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {} + }); + + const searchConfigService = {paginationID: 'test-id'}; + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [FormsModule, RouterTestingModule, TranslateModule.forRoot()], @@ -22,7 +41,9 @@ describe('SearchFormComponent', () => { { provide: SearchService, useValue: {} - } + }, + { provide: PaginationService, useValue: paginationService }, + { provide: SearchConfigurationService, useValue: searchConfigService } ], declarations: [SearchFormComponent] }).compileComponents(); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts index 4b2e5d2344..a01843248e 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts @@ -15,6 +15,10 @@ import { FacetValue } from '../../../../facet-value.model'; import { FilterType } from '../../../../filter-type.model'; import { SearchFilterConfig } from '../../../../search-filter-config.model'; import { SearchFacetOptionComponent } from './search-facet-option.component'; +import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../../core/data/request.models'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; describe('SearchFacetOptionComponent', () => { let comp: SearchFacetOptionComponent; @@ -81,6 +85,17 @@ describe('SearchFacetOptionComponent', () => { let router; const page = observableOf(0); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + getPageParam: 'p.page-id', + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], @@ -88,8 +103,10 @@ describe('SearchFacetOptionComponent', () => { providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: Router, useValue: new RouterStub() }, + { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: { + paginationID: 'page-id', searchOptions: observableOf({}) } }, @@ -131,7 +148,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'], - page: 1 + ['p.page-id']: 1 }); }); }); @@ -146,7 +163,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`], - page: 1 + ['p.page-id']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts index f5df1f038a..6329655eca 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts @@ -19,6 +19,10 @@ import { RANGE_FILTER_MAX_SUFFIX, RANGE_FILTER_MIN_SUFFIX } from '../../search-range-filter/search-range-filter.component'; +import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../../core/data/request.models'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; describe('SearchFacetRangeOptionComponent', () => { let comp: SearchFacetRangeOptionComponent; @@ -54,6 +58,18 @@ describe('SearchFacetRangeOptionComponent', () => { let router; const page = observableOf(0); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {}, + getPageParam: 'p.page-id', + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], @@ -61,9 +77,11 @@ describe('SearchFacetRangeOptionComponent', () => { providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: Router, useValue: new RouterStub() }, + { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: { - searchOptions: observableOf({}) + searchOptions: observableOf({}), + paginationId: 'page-id' } }, { @@ -116,7 +134,7 @@ describe('SearchFacetRangeOptionComponent', () => { expect(comp.changeQueryParams).toEqual({ [mockFilterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: ['50'], [mockFilterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: ['60'], - page: 1 + ['p.page-id']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts index 69945d86f2..60c4fd5721 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts @@ -14,6 +14,10 @@ import { FacetValue } from '../../../../facet-value.model'; import { FilterType } from '../../../../filter-type.model'; import { SearchFilterConfig } from '../../../../search-filter-config.model'; import { SearchFacetSelectedOptionComponent } from './search-facet-selected-option.component'; +import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../../../core/data/request.models'; +import { PaginationService } from '../../../../../../core/pagination/pagination.service'; describe('SearchFacetSelectedOptionComponent', () => { let comp: SearchFacetSelectedOptionComponent; @@ -106,6 +110,18 @@ describe('SearchFacetSelectedOptionComponent', () => { let router; const page = observableOf(0); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {}, + getPageParam: 'p.page-id' + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], @@ -113,6 +129,7 @@ describe('SearchFacetSelectedOptionComponent', () => { providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: Router, useValue: new RouterStub() }, + { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: { searchOptions: observableOf({}) @@ -156,7 +173,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedValues); expect(comp.removeQueryParams).toEqual({ [mockFilterConfig.paramName]: [value1], - page: 1 + ['p.page-id']: 1 }); }); }); @@ -172,7 +189,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedAuthorityValues); expect(comp.removeQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [`${value1},${operator}`], - page: 1 + ['p.page-id']: 1 }); }); }); diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts index 00868a86a2..fe9959aa3a 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts @@ -11,6 +11,11 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-pag import { SearchServiceStub } from '../../../testing/search-service.stub'; import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub'; import { SearchService } from '../../../../core/shared/search/search.service'; +import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../../core/data/request.models'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; describe('SearchLabelComponent', () => { let comp: SearchLabelComponent; @@ -33,6 +38,18 @@ describe('SearchLabelComponent', () => { filter2 ]; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + updateRouteWithUrl: {}, + getPageParam: 'p.test-id' + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], @@ -40,6 +57,8 @@ describe('SearchLabelComponent', () => { providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, + { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() }, + { provide: PaginationService, useValue: paginationService }, { provide: Router, useValue: {} } // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} } ], diff --git a/src/app/shared/search/search-settings/search-settings.component.spec.ts b/src/app/shared/search/search-settings/search-settings.component.spec.ts index cd4a872815..5ae1f7eaf2 100644 --- a/src/app/shared/search/search-settings/search-settings.component.spec.ts +++ b/src/app/shared/search/search-settings/search-settings.component.spec.ts @@ -16,6 +16,7 @@ import { take } from 'rxjs/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { SidebarService } from '../../sidebar/sidebar.service'; import { SidebarServiceStub } from '../../testing/sidebar-service.stub'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('SearchSettingsComponent', () => { @@ -32,6 +33,8 @@ describe('SearchSettingsComponent', () => { let scopeParam; let paginatedSearchOptions; + let paginationService; + let activatedRouteStub; beforeEach(waitForAsync(() => { @@ -62,6 +65,12 @@ describe('SearchSettingsComponent', () => { }), }; + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + resetPage: {}, + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective], @@ -77,6 +86,10 @@ describe('SearchSettingsComponent', () => { provide: SearchFilterService, useValue: {}, }, + { + provide: PaginationService, + useValue: paginationService, + }, { provide: SEARCH_CONFIG_SERVICE, useValue: { diff --git a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts index 33edcfa5fd..4b4556f511 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts @@ -11,12 +11,17 @@ import { StartsWithDateComponent } from './starts-with-date.component'; import { ActivatedRouteStub } from '../../testing/active-router.stub'; import { EnumKeysPipe } from '../../utils/enum-keys-pipe'; import { RouterStub } from '../../testing/router.stub'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('StartsWithDateComponent', () => { let comp: StartsWithDateComponent; let fixture: ComponentFixture; let route: ActivatedRoute; let router: Router; + let paginationService; const options = [2019, 2018, 2017, 2016, 2015]; @@ -25,13 +30,25 @@ describe('StartsWithDateComponent', () => { queryParams: observableOf({}) }); + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + + paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [StartsWithDateComponent, EnumKeysPipe], providers: [ { provide: 'startsWithOptions', useValue: options }, + { provide: 'paginationId', useValue: 'page-id' }, { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: PaginationService, useValue: paginationService }, { provide: Router, useValue: new RouterStub() } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts index 780c221294..1295f0a33a 100644 --- a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts +++ b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts @@ -8,6 +8,11 @@ import { EnumKeysPipe } from '../../utils/enum-keys-pipe'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { StartsWithTextComponent } from './starts-with-text.component'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../../core/data/request.models'; +import { of as observableOf } from 'rxjs'; +import { PaginationService } from '../../../core/pagination/pagination.service'; describe('StartsWithTextComponent', () => { let comp: StartsWithTextComponent; @@ -17,12 +22,24 @@ describe('StartsWithTextComponent', () => { const options = ['0-9', 'A', 'B', 'C']; + const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); + const sort = new SortOptions('score', SortDirection.DESC); + const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); + const paginationService = jasmine.createSpyObj('PaginationService', { + getCurrentPagination: observableOf(pagination), + getCurrentSort: observableOf(sort), + getFindListOptions: observableOf(findlistOptions), + resetPage: {}, + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [StartsWithTextComponent, EnumKeysPipe], providers: [ - { provide: 'startsWithOptions', useValue: options } + { provide: 'startsWithOptions', useValue: options }, + { provide: 'paginationId', useValue: 'page-id' }, + { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/testing/search-configuration-service.stub.ts b/src/app/shared/testing/search-configuration-service.stub.ts index 4c9402afb1..4b8f1d6f12 100644 --- a/src/app/shared/testing/search-configuration-service.stub.ts +++ b/src/app/shared/testing/search-configuration-service.stub.ts @@ -2,6 +2,8 @@ import { BehaviorSubject, of as observableOf } from 'rxjs'; export class SearchConfigurationServiceStub { + public paginationID = 'test-id'; + private searchOptions: BehaviorSubject = new BehaviorSubject({}); private paginatedSearchOptions: BehaviorSubject = new BehaviorSubject({}); diff --git a/src/app/submission/import-external/submission-import-external.component.spec.ts b/src/app/submission/import-external/submission-import-external.component.spec.ts index 3a69a3727e..c0b85b696b 100644 --- a/src/app/submission/import-external/submission-import-external.component.spec.ts +++ b/src/app/submission/import-external/submission-import-external.component.spec.ts @@ -115,15 +115,22 @@ describe('SubmissionImportExternalComponent test suite', () => { spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValues(observableOf('source'), observableOf('dummy')); fixture.detectChanges(); - expect(compAsAny.retrieveExternalSources).toHaveBeenCalledWith('source', 'dummy'); + expect(compAsAny.retrieveExternalSources).toHaveBeenCalled(); }); it('Should call \'getExternalSourceEntries\' properly', () => { - comp.routeData = { sourceId: '', query: '' }; - scheduler.schedule(() => compAsAny.retrieveExternalSources('orcidV2', 'test')); - scheduler.flush(); + spyOn(routeServiceStub, 'getQueryParameterValue').and.callFake((param) => { + if (param === 'source') { + return observableOf('orcidV2'); + } else if (param === 'query') { + return observableOf('test'); + } + return observableOf({}); + }); + + fixture.detectChanges(); + - expect(comp.routeData).toEqual({ sourceId: 'orcidV2', query: 'test' }); expect(comp.isLoading$.value).toBe(false); expect(compAsAny.externalService.getExternalSourceEntries).toHaveBeenCalled(); }); diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index d8801880dc..2c407a1edb 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -20,7 +20,6 @@ import { fadeIn } from '../../shared/animations/fade'; import { PageInfo } from '../../core/shared/page-info.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { getFinishedRemoteData } from '../../core/shared/operators'; -import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; /** * This component allows to submit a new workspaceitem importing the data from an external source. @@ -46,7 +45,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { */ public isLoading$: BehaviorSubject = new BehaviorSubject(false); - public reload$: BehaviorSubject<{query: string, source: string}>; + public reload$: BehaviorSubject<{query: string, source: string}> = new BehaviorSubject<{query: string; source: string}>({query: '', source: ''}); /** * Configuration to use for the import buttons */ @@ -122,9 +121,10 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { ]).pipe( take(1) ).subscribe(([source, query]: [string, string]) => { - this.reload$ = new BehaviorSubject<{query: string; source: string}>({query: query, source: source}); - this.retrieveExternalSources(source, query); + this.reload$.next({query: query, source: source}); + this.retrieveExternalSources(); })); + this.reload$.subscribe((v) => console.log('this.reload$', v)); } /** @@ -167,8 +167,9 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * @param source The source tupe * @param query The query string to search */ - private retrieveExternalSources(sourcesss: string, querysss: string): void { + private retrieveExternalSources(): void { this.reload$.subscribe((sourceQueryObject: {source: string, query: string}) => { + console.log('ping?', sourceQueryObject); const source = sourceQueryObject.source; const query = sourceQueryObject.query; if (isNotEmpty(source) && isNotEmpty(query)) { @@ -177,6 +178,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.isLoading$.next(true); this.subs.push( this.searchConfigService.paginatedSearchOptions.pipe( + tap((v) => console.log('searchpag?', v)), filter((searchOptions) => searchOptions.query === query), mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( getFinishedRemoteData(), From da43a56a0b8388943a8580247a35fc10709c0ea6 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 5 Mar 2021 15:23:23 +0100 Subject: [PATCH 04/46] fix error when state is undefined --- src/app/core/services/route.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/services/route.service.ts b/src/app/core/services/route.service.ts index 0b00420797..8aee755e3b 100644 --- a/src/app/core/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -18,7 +18,7 @@ import { AddUrlToHistoryAction } from '../history/history.actions'; */ export const routeParametersSelector = createSelector( coreSelector, - (state: CoreState) => state.route.params + (state: CoreState) => hasValue(state) && hasValue(state.route) ? state.route.params : undefined ); /** @@ -26,7 +26,7 @@ export const routeParametersSelector = createSelector( */ export const queryParametersSelector = createSelector( coreSelector, - (state: CoreState) => state.route.queryParams + (state: CoreState) => hasValue(state) && hasValue(state.route) ? state.route.queryParams : undefined ); /** From e73f4ca57a69c1b5e356b7cacba09790dee4b446 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 8 Mar 2021 16:47:16 +0100 Subject: [PATCH 05/46] 76654: Remove console.logs, fix some issues --- ...-drag-and-drop-bitstream-list.component.ts | 5 +- src/app/core/pagination/pagination.service.ts | 186 ++++++++++-------- ...nated-drag-and-drop-list.component.spec.ts | 2 +- ...-paginated-drag-and-drop-list.component.ts | 10 +- .../pagination/pagination.component.spec.ts | 2 - .../shared/pagination/pagination.component.ts | 5 +- .../submission-import-external.component.ts | 3 - 7 files changed, 117 insertions(+), 96 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts index 0941a4dfd6..f3f00abf92 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts @@ -11,6 +11,7 @@ import { followLink } from '../../../../../shared/utils/follow-link-config.model import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe'; import { RequestService } from '../../../../../core/data/request.service'; import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; @Component({ selector: 'ds-paginated-drag-and-drop-bitstream-list', @@ -52,8 +53,8 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate */ initializeObjectsRD(): void { this.objectsRD$ = this.currentPage$.pipe( - switchMap((page: number) => { - const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })}); + switchMap((page: PaginationComponentOptions) => { + const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, page)}); return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe( switchMap((href) => this.requestService.hasByHref$(href)), switchMap(() => this.bundleService.getBitstreams( diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index 29e57214c1..45a1ffee96 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -14,23 +14,31 @@ import { isNumeric } from 'rxjs/internal-compatibility'; @Injectable({ providedIn: 'root', }) +/** + * Service to manage the pagination of different components + */ export class PaginationService { private defaultSortOptions = new SortOptions('id', SortDirection.ASC); + private clearParams = {}; + constructor(protected routeService: RouteService, protected router: Router ) { } /** - * @returns {Observable} Emits the current pagination settings + * Method to retrieve the current pagination settings for an ID based on the router params and default options + * @param paginationId - The id to check the pagination for + * @param defaultPagination - The default pagination values to be used when no route info is present + * @returns {Observable} Retrieves the current pagination settings based on the router params */ getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { const page$ = this.routeService.getQueryParameterValue(`p.${paginationId}`); const size$ = this.routeService.getQueryParameterValue(`rpp.${paginationId}`); - return observableCombineLatest([page$, size$]).pipe(map(([page, size]) => { - console.log(page, size); + return observableCombineLatest([page$, size$]).pipe( + map(([page, size]) => { return Object.assign(new PaginationComponentOptions(), defaultPagination, { currentPage: this.convertToNumeric(page, defaultPagination.currentPage), pageSize: this.getBestMatchPageSize(size, defaultPagination) @@ -40,7 +48,11 @@ export class PaginationService { } /** - * @returns {Observable} Emits the current sorting settings + * Method to retrieve the current sort options for an ID based on the router params and default options + * @param paginationId - The id to check the sort options for + * @param defaultSort - The default sort options to be used when no route info is present + * @param ignoreDefault - Indicate whether the default should be ignored + * @returns {Observable} Retrieves the current sort options based on the router params */ getCurrentSort(paginationId: string, defaultSort: SortOptions, ignoreDefault?: boolean): Observable { if (!ignoreDefault && (isEmpty(defaultSort) || !hasValue(defaultSort))) { @@ -56,6 +68,13 @@ export class PaginationService { ); } + /** + * Method to retrieve the current find list options for an ID based on the router params and default options + * @param paginationId - The id to check the find list options for + * @param defaultFindList - The default find list options to be used when no route info is present + * @param ignoreDefault - Indicate whether the default should be ignored + * @returns {Observable} Retrieves the current find list options based on the router params + */ getFindListOptions(paginationId: string, defaultFindList: FindListOptions, ignoreDefault?: boolean): Observable { const paginationComponentOptions = new PaginationComponentOptions(); paginationComponentOptions.currentPage = defaultFindList.currentPage; @@ -74,10 +93,94 @@ export class PaginationService { })); } + /** + * Reset the current page for the provided pagination ID to 1. + * @param paginationId - The pagination id for which to reset the page + */ resetPage(paginationId: string) { this.updateRoute(paginationId, {page: 1}); } + + /** + * Update the route with the provided information + * @param paginationId - The pagination ID for which to update the route with info + * @param params - The page related params to update in the route + * @param extraParams - Addition params unrelated to the pagination that need to be added to the route + * @param retainScrollPosition - Scroll to the pagination component after updating the route instead of the top of the page + */ + updateRoute(paginationId: string, params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, retainScrollPosition?: boolean) { + + this.updateRouteWithUrl(paginationId, [], params, extraParams, retainScrollPosition); + } + + /** + * Update the route with the provided information + * @param paginationId - The pagination ID for which to update the route with info + * @param url - The url to navigate to + * @param params - The page related params to update in the route + * @param extraParams - Addition params unrelated to the pagination that need to be added to the route + * @param retainScrollPosition - Scroll to the pagination component after updating the route instead of the top of the page + */ + updateRouteWithUrl(paginationId: string, url: string[], params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, retainScrollPosition?: boolean) { + this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); + const parametersWithIdName = this.getParametersWithIdName(paginationId, params); + if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams) || isNotEmpty(this.clearParams)) { + const queryParams = Object.assign({}, this.clearParams, currentParametersWithIdName, + parametersWithIdName, extraParams); + console.log(queryParams, this.clearParams); + if (retainScrollPosition) { + this.router.navigate(url, { + queryParams: queryParams, + queryParamsHandling: 'merge', + fragment: `p-${paginationId}` + }); + } else { + this.router.navigate(url, { + queryParams: queryParams, + queryParamsHandling: 'merge' + }); + } + this.clearParams = {}; + console.log('postcear', this.clearParams); + } + }); + } + + /** + * Add the params to be cleared to the clearParams variable. + * When the updateRoute or updateRouteWithUrl these params will be removed from the route pagination + * @param paginationId - The ID for which to clear the params + */ + clearPagination(paginationId: string) { + const params = {}; + params[`p.${paginationId}`] = null; + params[`rpp.${paginationId}`] = null; + params[`sf.${paginationId}`] = null; + params[`sd.${paginationId}`] = null; + + Object.assign(this.clearParams, params); + } + + /** + * Retrieve the page parameter for the provided id + * @param paginationId - The ID for which to retrieve the page param + */ + getPageParam(paginationId: string) { + return `p.${paginationId}`; + } + private getCurrentRouting(paginationId: string) { return this.getFindListOptions(paginationId, {}, true).pipe( take(1), @@ -92,81 +195,6 @@ export class PaginationService { ); } - updateRoute(paginationId: string, params: { - page?: number - pageSize?: number - sortField?: string - sortDirection?: SortDirection - }, extraParams?, retainScrollPosition?: boolean) { - this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { - console.log('currentFindListOptions',currentFindListOptions ); - const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); - const parametersWithIdName = this.getParametersWithIdName(paginationId, params); - if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { - const queryParams = Object.assign({}, currentParametersWithIdName, - parametersWithIdName, extraParams); - if (retainScrollPosition) { - this.router.navigate([], { - queryParams: queryParams, - queryParamsHandling: 'merge', - fragment: `p-${paginationId}` - }); - } else { - this.router.navigate([], { - queryParams: queryParams, - queryParamsHandling: 'merge' - }); - } - } - }); - } - - updateRouteWithUrl(paginationId: string, url: string[], params: { - page?: number - pageSize?: number - sortField?: string - sortDirection?: SortDirection - }, extraParams?, retainScrollPosition?: boolean) { - console.log(retainScrollPosition); - this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { - const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); - const parametersWithIdName = this.getParametersWithIdName(paginationId, params); - if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { - const queryParams = Object.assign({}, currentParametersWithIdName, - parametersWithIdName, extraParams); - if (retainScrollPosition) { - this.router.navigate(url, { - queryParams: queryParams, - queryParamsHandling: 'merge', - fragment: `p-${paginationId}` - }); - } else { - this.router.navigate(url, { - queryParams: queryParams, - queryParamsHandling: 'merge' - }); - } - } - }); - } - - clearPagination(paginationId: string) { - const params = {}; - params[`p.${paginationId}`] = null; - params[`rpp.${paginationId}`] = null; - params[`sf.${paginationId}`] = null; - params[`sd.${paginationId}`] = null; - - this.router.navigate([], { - queryParams: params, - queryParamsHandling: 'merge' - }); - } - - getPageParam(paginationId: string) { - return `p.${paginationId}`; - } - private getParametersWithIdName(paginationId: string, params: { page?: number pageSize?: number diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts index 4c29e2af9d..53a4619071 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts @@ -118,7 +118,7 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { it('should send out a dropObject event with the expected processed paginated indexes', () => { expect(component.dropObject.emit).toHaveBeenCalledWith(Object.assign({ - fromIndex: ((component.currentPage$.value - 1) * component.pageSize) + event.previousIndex, + fromIndex: ((component.currentPage$.value.currentPage - 1) * component.pageSize) + event.previousIndex, toIndex: ((hoverPage - 1) * component.pageSize), finish: jasmine.anything() })); diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts index b861104197..f9b9ff8e90 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts @@ -82,7 +82,7 @@ export abstract class AbstractPaginatedDragAndDropListComponent(1); + currentPage$ = new BehaviorSubject(this.options); /** * Whether or not we should display a loading animation @@ -144,7 +144,7 @@ export abstract class AbstractPaginatedDragAndDropListComponent { - this.currentPage$.next(currentPagination.currentPage); + this.currentPage$.next(currentPagination); }); } @@ -187,8 +187,8 @@ export abstract class AbstractPaginatedDragAndDropListComponent) { const dragIndex = event.previousIndex; let dropIndex = event.currentIndex; - const dragPage = this.currentPage$.value - 1; - let dropPage = this.currentPage$.value - 1; + const dragPage = this.currentPage$.value.currentPage - 1; + let dropPage = this.currentPage$.value.currentPage - 1; // Check if the user is hovering over any of the pagination's pages at the time of dropping the object const droppedOnElement = this.elRef.nativeElement.querySelector('.page-item:hover'); diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index bad54d6e83..66308df67f 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -38,8 +38,6 @@ import { BehaviorSubject, of as observableOf } from 'rxjs'; function expectPages(fixture: ComponentFixture, pagesDef: string[]): void { const de = fixture.debugElement.query(By.css('.pagination')); const pages = de.nativeElement.querySelectorAll('li'); - console.log('pages', pages.length, pagesDef.length); - console.log(pages); expect(pages.length).toEqual(pagesDef.length); diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 4a181d7090..520dcda651 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -176,7 +176,7 @@ export class PaginationComponent implements OnDestroy, OnInit { })); this.checkConfig(this.paginationOptions); this.initializeConfig(); - } + } /** * Method provided by Angular. Invoked when the instance is destroyed. @@ -195,14 +195,11 @@ export class PaginationComponent implements OnDestroy, OnInit { this.id = this.paginationOptions.id || null; this.pageSizeOptions = this.paginationOptions.pageSizeOptions; this.currentPage$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe( - tap((v) => console.log('currentPage', v)), map((currentPagination) => currentPagination.currentPage) ); this.pageSize$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe( map((currentPagination) => currentPagination.pageSize) ); - this.pageSize$.subscribe((v) => console.log('this.pageSize$', v)); - this.currentPage$.subscribe((v) => console.log('this.currentPage$', v)); let sortOptions; if (this.sortOptions) { diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 2c407a1edb..9dabf4ceb7 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -124,7 +124,6 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.reload$.next({query: query, source: source}); this.retrieveExternalSources(); })); - this.reload$.subscribe((v) => console.log('this.reload$', v)); } /** @@ -169,7 +168,6 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { */ private retrieveExternalSources(): void { this.reload$.subscribe((sourceQueryObject: {source: string, query: string}) => { - console.log('ping?', sourceQueryObject); const source = sourceQueryObject.source; const query = sourceQueryObject.query; if (isNotEmpty(source) && isNotEmpty(query)) { @@ -178,7 +176,6 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.isLoading$.next(true); this.subs.push( this.searchConfigService.paginatedSearchOptions.pipe( - tap((v) => console.log('searchpag?', v)), filter((searchOptions) => searchOptions.query === query), mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( getFinishedRemoteData(), From b4e40c820f0de94691888bb73d41efc8e4751700 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 11 Mar 2021 12:47:44 +0100 Subject: [PATCH 06/46] Fix issue with clear pagination test --- .../pagination/pagination.service.spec.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/app/core/pagination/pagination.service.spec.ts b/src/app/core/pagination/pagination.service.spec.ts index 990dcd9b90..f689f4f0f0 100644 --- a/src/app/core/pagination/pagination.service.spec.ts +++ b/src/app/core/pagination/pagination.service.spec.ts @@ -128,16 +128,25 @@ describe('PaginationService', () => { }); describe('clearPagination', () => { - it('should clear the pagination info from the route for the current id', () => { + it('should clear the pagination next time the updateRoute/updateRouteWithUrl method is called', () => { service.clearPagination('test'); - const params = {}; - params[`p.test`] = null; - params[`rpp.test`] = null; - params[`sf.test`] = null; - params[`sd.test`] = null; + const resetParams = {}; + resetParams[`p.test`] = null; + resetParams[`rpp.test`] = null; + resetParams[`sf.test`] = null; + resetParams[`sd.test`] = null; - expect(router.navigate).toHaveBeenCalledWith([], {queryParams: params, queryParamsHandling: 'merge'}); + + const navigateParams = {}; + navigateParams[`p.another-id`] = `5`; + navigateParams[`rpp.another-id`] = `10`; + navigateParams[`sf.another-id`] = `score`; + navigateParams[`sd.another-id`] = `ASC`; + + service.updateRoute('another-id', {}); + + expect(router.navigate).toHaveBeenCalledWith([], {queryParams: Object.assign({}, resetParams, navigateParams), queryParamsHandling: 'merge'}); }); }); describe('getPageParam', () => { From 11728695498cbe6faf0a59d56379c63d97d2266e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 11 Mar 2021 15:45:04 +0100 Subject: [PATCH 07/46] Fix LGTM issues --- .../epeople-registry/eperson-form/eperson-form.component.ts | 2 +- .../group-form/members-list/members-list.component.ts | 1 - .../+browse-by-title-page/browse-by-title-page.component.ts | 2 +- src/app/+collection-page/collection-page.component.ts | 2 +- .../file-section/full-file-section.component.ts | 2 +- src/app/core/shared/search/search-configuration.service.ts | 1 - src/app/core/shared/search/search.service.ts | 1 - src/app/shared/item/item-versions/item-versions.component.ts | 2 +- src/app/shared/pagination/pagination.component.ts | 1 - .../search-facet-option/search-facet-option.component.ts | 1 - .../search/search-labels/search-label/search-label.component.ts | 2 +- .../shared/search/search-settings/search-settings.component.ts | 2 +- .../import-external/submission-import-external.component.ts | 2 +- 13 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 48f171f966..f2b18ab638 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -8,7 +8,7 @@ import { } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { switchMap, take, tap } from 'rxjs/operators'; +import { switchMap, take } from 'rxjs/operators'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts index 944b2e5dfb..837649857a 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts @@ -23,7 +23,6 @@ import { import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { PaginationService } from '../../../../../core/pagination/pagination.service'; -import { hasValue } from '../../../../../shared/empty.util'; import {EpersonDtoModel} from '../../../../../core/eperson/models/eperson-dto.model'; /** diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts index d62c58d644..de9fb2daf0 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts @@ -1,7 +1,7 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; import { Component } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { hasValue } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, browseParamsToOptions diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 74d8c76c5c..855a7c3301 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs'; -import { filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators'; +import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; import { SearchService } from '../core/shared/search/search.service'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts index 439bb6502f..214484120e 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { Bitstream } from '../../../../core/shared/bitstream.model'; diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index 6639b53cb9..798a0de287 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -15,7 +15,6 @@ import { getFirstSucceededRemoteData } from '../operators'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { PaginationService } from '../../pagination/pagination.service'; -import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all actions that have to do with the current search configuration diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 4dfd1964c3..054bde4c08 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -40,7 +40,6 @@ import { FacetValues } from '../../../shared/search/facet-values.model'; import { PaginationService } from '../../pagination/pagination.service'; import { SearchConfigurationService } from './search-configuration.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all general actions that have to do with the search page diff --git a/src/app/shared/item/item-versions/item-versions.component.ts b/src/app/shared/item/item-versions/item-versions.component.ts index 3e515b9452..645eed5b90 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { Version } from '../../../core/shared/version.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { VersionHistory } from '../../../core/shared/version-history.model'; import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; import { map, startWith, switchMap } from 'rxjs/operators'; diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 520dcda651..1b9adf8922 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -20,7 +20,6 @@ import { hasValue } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; -import { tap } from 'rxjs/internal/operators/tap'; /** * The default pagination controls component. diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts index 45c90d8a60..11056a232c 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts @@ -11,7 +11,6 @@ import { hasValue } from '../../../../../empty.util'; import { currentPath } from '../../../../../utils/route.utils'; import { getFacetValueForType } from '../../../../search.utils'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; -import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; @Component({ selector: 'ds-search-facet-option', diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.ts b/src/app/shared/search/search-labels/search-label/search-label.component.ts index b66308a5bd..74526ad2ad 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { Params, Router } from '@angular/router'; -import { map, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../../empty.util'; import { SearchService } from '../../../../core/shared/search/search.service'; import { currentPath } from '../../../utils/route.utils'; diff --git a/src/app/shared/search/search-settings/search-settings.component.ts b/src/app/shared/search/search-settings/search-settings.component.ts index 6f12c161ea..2241fc6c6a 100644 --- a/src/app/shared/search/search-settings/search-settings.component.ts +++ b/src/app/shared/search/search-settings/search-settings.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { SearchService } from '../../../core/shared/search/search.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { Observable } from 'rxjs'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 9dabf4ceb7..faaf83389b 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; -import { filter, mergeMap, take, tap } from 'rxjs/operators'; +import { filter, mergeMap, take } from 'rxjs/operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceService } from '../../core/data/external-source.service'; From 659117dfbd3818f8c2093731ec94f1242ba93d58 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 18 Mar 2021 14:55:39 +0100 Subject: [PATCH 08/46] Fix import itssue --- src/app/shared/item/item-versions/item-versions.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/item/item-versions/item-versions.component.ts b/src/app/shared/item/item-versions/item-versions.component.ts index 0d90f9ffee..752cd55b5b 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { Version } from '../../../core/shared/version.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { VersionHistory } from '../../../core/shared/version-history.model'; import { getAllSucceededRemoteData, From afe345ebacabebc8f1f64955c821970175ac24bd Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 29 Mar 2021 15:31:27 +0200 Subject: [PATCH 09/46] 76654: Implement feedback --- .../epeople-registry.component.spec.ts | 11 +--- .../epeople-registry.component.ts | 65 ++++++++----------- .../eperson-form.component.spec.ts | 14 +--- .../members-list.component.spec.ts | 12 +--- .../subgroups-list.component.spec.ts | 13 +--- .../groups-registry.component.spec.ts | 12 +--- .../bitstream-formats.component.spec.ts | 27 ++------ .../bitstream-formats.component.ts | 5 -- .../metadata-registry.component.spec.ts | 12 +--- .../metadata-schema.component.spec.ts | 11 +--- .../browse-by-date-page.component.spec.ts | 11 +--- .../browse-by-date-page.component.ts | 11 ++-- .../browse-by-metadata-page.component.html | 4 +- .../browse-by-metadata-page.component.spec.ts | 12 +--- .../browse-by-metadata-page.component.ts | 21 ++++-- .../browse-by-title-page.component.spec.ts | 11 +--- .../browse-by-title-page.component.ts | 10 +-- ...page-sub-collection-list.component.spec.ts | 12 +--- ...-page-sub-community-list.component.spec.ts | 11 +--- ...top-level-community-list.component.spec.ts | 10 +-- ...-and-drop-bitstream-list.component.spec.ts | 13 +--- .../full-file-section.component.spec.ts | 11 +--- .../my-dspace-configuration.service.spec.ts | 11 ++-- src/app/core/pagination/pagination.service.ts | 6 ++ .../search-configuration.service.spec.ts | 9 +-- .../core/shared/search/search.service.spec.ts | 12 +--- .../process-overview.component.spec.ts | 13 +--- .../search-navbar.component.spec.ts | 12 +--- .../browse-by/browse-by.component.spec.ts | 11 +--- .../item-versions.component.spec.ts | 10 +-- .../collection-select.component.spec.ts | 12 +--- .../item-select/item-select.component.spec.ts | 12 +--- .../page-size-selector.component.spec.ts | 7 +- ...nated-drag-and-drop-list.component.spec.ts | 9 +-- .../pagination/pagination.component.spec.ts | 3 +- .../shared/pagination/pagination.component.ts | 11 ++-- .../eperson-group-list.component.spec.ts | 11 +--- .../search-form/search-form.component.spec.ts | 12 +--- .../search-facet-option.component.spec.ts | 14 +--- ...earch-facet-range-option.component.spec.ts | 14 +--- ...ch-facet-selected-option.component.spec.ts | 14 +--- .../search-label.component.spec.ts | 14 +--- .../search-settings.component.spec.ts | 7 +- .../date/starts-with-date.component.spec.ts | 11 +--- .../text/starts-with-text.component.spec.ts | 11 +--- .../shared/testing/pagination-service.stub.ts | 25 +++++++ .../submission-import-external.component.ts | 44 +++++++------ 47 files changed, 203 insertions(+), 431 deletions(-) create mode 100644 src/app/shared/testing/pagination-service.stub.ts diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts index 4b9dcef00d..57070a9e88 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts @@ -28,6 +28,7 @@ import { RequestService } from '../../../core/data/request.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('EPeopleRegistryComponent', () => { let component: EPeopleRegistryComponent; @@ -121,15 +122,7 @@ describe('EPeopleRegistryComponent', () => { builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts index c82afc0621..365a62bd4e 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts @@ -70,11 +70,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { currentSearchQuery: string; currentSearchScope: string; - /** - * The subscription for the search method - */ - searchSub: Subscription; - /** * FindListOptions */ @@ -150,39 +145,35 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { if (hasValue(this.findListOptionsSub)) { this.findListOptionsSub.unsubscribe(); } - this.findListOptionsSub = this.paginationService.getCurrentPagination(this.config.id, this.config).subscribe((findListOptions) => { - const query: string = data.query; - const scope: string = data.scope; - if (query != null && this.currentSearchQuery !== query) { - this.router.navigate([], { - queryParamsHandling: 'merge' - }); - this.currentSearchQuery = query; - this.paginationService.resetPage(this.config.id); - } - if (scope != null && this.currentSearchScope !== scope) { - this.router.navigate([], { - queryParamsHandling: 'merge' - }); - this.currentSearchScope = scope; - this.paginationService.resetPage(this.config.id); - - } - if (hasValue(this.searchSub)) { - this.searchSub.unsubscribe(); - this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); - } - this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: findListOptions.currentPage, - elementsPerPage: findListOptions.pageSize - }).pipe( - getAllSucceededRemoteData(), - ).subscribe((peopleRD) => { - this.ePeople$.next(peopleRD.payload); - this.pageInfoState$.next(peopleRD.payload.pageInfo); + this.findListOptionsSub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((findListOptions) => { + const query: string = data.query; + const scope: string = data.scope; + if (query != null && this.currentSearchQuery !== query) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.config.id); } - ); - this.subs.push(this.searchSub); + if (scope != null && this.currentSearchScope !== scope) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchScope = scope; + this.paginationService.resetPage(this.config.id); + + } + return this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: findListOptions.currentPage, + elementsPerPage: findListOptions.pageSize + }); + } + ), + getAllSucceededRemoteData(), + ).subscribe((peopleRD) => { + this.ePeople$.next(peopleRD.payload); + this.pageInfoState$.next(peopleRD.payload.pageInfo); } ); } diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index e10c1af94c..94aab9809a 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -29,6 +29,7 @@ import { RequestService } from '../../../../core/data/request.service'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -41,7 +42,7 @@ describe('EPersonFormComponent', () => { let authorizationService: AuthorizationDataService; let groupsDataService: GroupDataService; - let paginationService: PaginationService; + let paginationService; @@ -112,16 +113,7 @@ describe('EPersonFormComponent', () => { getGroupRegistryRouterLink: '' }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {} - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts index ad1455ae0f..8a9cc94e13 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -30,6 +30,7 @@ import { PaginationComponentOptions } from '../../../../../shared/pagination/pag import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../core/data/request.models'; import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; describe('MembersListComponent', () => { let component: MembersListComponent; @@ -119,16 +120,7 @@ describe('MembersListComponent', () => { builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - clearPagination : {}, - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts index ea65e1b290..087b9ebc39 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts @@ -39,6 +39,7 @@ import { PaginationComponentOptions } from '../../../../../shared/pagination/pag import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../core/data/request.models'; import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; describe('SubgroupsListComponent', () => { let component: SubgroupsListComponent; @@ -106,17 +107,7 @@ describe('SubgroupsListComponent', () => { builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {}, - clearPagination: {} - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts index ff75988607..88f2a53b3c 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts @@ -32,6 +32,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('GroupRegistryComponent', () => { let component: GroupsRegistryComponent; @@ -136,16 +137,7 @@ describe('GroupRegistryComponent', () => { authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: observableOf(true) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {} - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index 5bed34d46b..8cfba1d37b 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -27,6 +27,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('BitstreamFormatsComponent', () => { let comp: BitstreamFormatsComponent; @@ -104,14 +105,7 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); - - + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -236,14 +230,7 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); - - + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -292,13 +279,7 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared') }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); - + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index d2ae805be7..cbbcbe07a4 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -28,11 +28,6 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy { */ bitstreamFormats: Observable>>; - /** - * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats - */ - // pageState: BehaviorSubject; - /** * The current pagination configuration for the page used by the FindAll method * Currently simply renders all bitstream formats diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts index beb900c17f..0253725cb9 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts @@ -22,12 +22,13 @@ import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('MetadataRegistryComponent', () => { let comp: MetadataRegistryComponent; let fixture: ComponentFixture; let registryService: RegistryService; - let paginationService: PaginationService; + let paginationService; const mockSchemasList = [ { id: 1, @@ -67,14 +68,7 @@ describe('MetadataRegistryComponent', () => { }; /* tslint:enable:no-empty */ - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - }); + paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts index 35522462aa..6eb3c5b1a4 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts @@ -27,6 +27,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('MetadataSchemaComponent', () => { let comp: MetadataSchemaComponent; @@ -129,15 +130,7 @@ describe('MetadataSchemaComponent', () => { }) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts index 50761b9521..a5cc69e430 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts @@ -22,6 +22,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../core/data/request.models'; import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; @@ -70,15 +71,7 @@ describe('BrowseByDatePageComponent', () => { detectChanges: () => fixture.detectChanges() }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 7e3979e439..a9eaa09e2f 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -17,7 +17,7 @@ import { environment } from '../../../environments/environment'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -47,13 +47,14 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { } ngOnInit(): void { + const sortConfig = new SortOptions('default', SortDirection.ASC); this.startsWithType = StartsWithType.date; - this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); - const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); - const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); + this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); + this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( observableCombineLatest([this.route.params, this.route.queryParams, this.route.data, - currentPagination$, currentSort$]).pipe( + this.currentPagination$, this.currentSort$]).pipe( map(([routeParams, queryParams, data, currentPage, currentSort]) => { return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort]; }) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index d770d8cb01..2321da0204 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -27,8 +27,8 @@ title="{{'browse.title' | translate:{collection: (parent$ | async)?.payload?.name || '', field: 'browse.metadata.' + browseId | translate, value: (value)? '"' + value + '"': ''} }}" parentname="{{(parent$ | async)?.payload?.name || ''}}" [objects$]="(items$ !== undefined)? items$ : browseEntries$" - [paginationConfig]="paginationConfig" - [sortConfig]="sortConfig" + [paginationConfig]="(currentPagination$ |async)" + [sortConfig]="(currentSort$ |async)" [type]="startsWithType" [startsWithOptions]="startsWithOptions" [enableArrows]="true" diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts index 1c6336c2c8..60d2fa549b 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts @@ -24,16 +24,14 @@ import { VarDirective } from '../../shared/utils/var.directive'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('BrowseByMetadataPageComponent', () => { let comp: BrowseByMetadataPageComponent; let fixture: ComponentFixture; let browseService: BrowseService; let route: ActivatedRoute; - let paginationService: PaginationService; - - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); + let paginationService; const mockCommunity = Object.assign(new Community(), { id: 'test-uuid', @@ -88,11 +86,7 @@ describe('BrowseByMetadataPageComponent', () => { params: observableOf({}) }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf('') - }); + paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts index 833c3d7d19..9ebc341856 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -16,7 +16,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -56,9 +56,14 @@ export class BrowseByMetadataPageComponent implements OnInit { }); /** - * The sorting config used to sort the values (defaults to Ascending) + * The pagination observable */ - sortConfig: SortOptions = new SortOptions('default', SortDirection.ASC); + currentPagination$: Observable; + + /** + * The sorting config observable + */ + currentSort$: Observable; /** * List of subscriptions @@ -107,11 +112,12 @@ export class BrowseByMetadataPageComponent implements OnInit { } ngOnInit(): void { - this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); - const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); - const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); + const sortConfig = new SortOptions('default', SortDirection.ASC); + this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); + this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( - observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( map(([routeParams, queryParams, currentPage, currentSort]) => { return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) @@ -161,6 +167,7 @@ export class BrowseByMetadataPageComponent implements OnInit { * @param value The value of the browse-entry to display items for */ updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string) { + console.log('updatePAge', searchOptions); this.items$ = this.browseService.getBrowseItemsFor(value, searchOptions); } diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts index 6ae10c6696..d44c667044 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts @@ -22,6 +22,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../core/data/request.models'; import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('BrowseByTitlePageComponent', () => { let comp: BrowseByTitlePageComponent; @@ -65,15 +66,7 @@ describe('BrowseByTitlePageComponent', () => { data: observableOf({ metadata: 'title' }) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts index de9fb2daf0..381684f9f0 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts @@ -35,12 +35,12 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { } ngOnInit(): void { - this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); - const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); - const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); + const sortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); + this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( - observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( map(([routeParams, queryParams, currentPage, currentSort]) => { return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts index abb3d957bc..93a6c6fbb1 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts @@ -24,6 +24,7 @@ import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('CommunityPageSubCollectionList Component', () => { let comp: CommunityPageSubCollectionListComponent; @@ -117,16 +118,7 @@ describe('CommunityPageSubCollectionList Component', () => { } }; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {} - }); + const paginationService = new PaginationServiceStub(); themeService = getMockThemeService(); diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts index 1054879f0e..e573259b63 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts @@ -24,6 +24,7 @@ import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('CommunityPageSubCommunityListComponent Component', () => { let comp: CommunityPageSubCommunityListComponent; @@ -118,15 +119,7 @@ describe('CommunityPageSubCommunityListComponent Component', () => { } }; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(); themeService = getMockThemeService(); diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts index 4a5549cf4a..00408e4696 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.spec.ts @@ -24,6 +24,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { PaginationService } from '../../core/pagination/pagination.service'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('TopLevelCommunityList Component', () => { let comp: TopLevelCommunityListComponent; @@ -109,14 +110,7 @@ describe('TopLevelCommunityList Component', () => { } }; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf('') - }); + paginationService = new PaginationServiceStub(); themeService = getMockThemeService(); diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts index f3c50f4d13..807d9f8357 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts @@ -20,6 +20,7 @@ import { PaginationService } from '../../../../../core/pagination/pagination.ser import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../core/data/request.models'; +import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; describe('PaginatedDragAndDropBitstreamListComponent', () => { let comp: PaginatedDragAndDropBitstreamListComponent; @@ -28,7 +29,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => { let bundleService: BundleDataService; let objectValuesPipe: ObjectValuesPipe; let requestService: RequestService; - let paginationService: PaginationService; + let paginationService; const columnSizes = new ResponsiveTableSizes([ new ResponsiveColumnSizes(2, 2, 3, 4, 4), @@ -114,15 +115,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => { hasByHref$: observableOf(true) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts index 0723354ef5..9b225632df 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -20,6 +20,7 @@ import { PaginationComponentOptions } from '../../../../shared/pagination/pagina import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../core/data/request.models'; import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; describe('FullFileSectionComponent', () => { let comp: FullFileSectionComponent; @@ -56,15 +57,7 @@ describe('FullFileSectionComponent', () => { findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts index 86489019d9..87a2f8a9dd 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts @@ -9,6 +9,8 @@ import { ActivatedRouteStub } from '../shared/testing/active-router.stub'; import { RoleServiceMock } from '../shared/mocks/role-service.mock'; import { cold, hot } from 'jasmine-marbles'; import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type'; +import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; +import { PaginationService } from '../core/pagination/pagination.service'; describe('MyDSpaceConfigurationService', () => { let service: MyDSpaceConfigurationService; @@ -34,18 +36,13 @@ describe('MyDSpaceConfigurationService', () => { getRouteDataValue: observableOf({}) }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(defaults.pagination), - getCurrentSort: observableOf(defaults.sort), - getRouteParameterValue: observableOf('') - }); - + const paginationService = new PaginationServiceStub(); const activatedRoute: any = new ActivatedRouteStub(); const roleService: any = new RoleServiceMock(); beforeEach(() => { - service = new MyDSpaceConfigurationService(roleService, spy, paginationService, activatedRoute); + service = new MyDSpaceConfigurationService(roleService, spy, paginationService as any, activatedRoute); }); describe('when the scope is called', () => { diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index 45a1ffee96..8ed81ba8d4 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -16,6 +16,12 @@ import { isNumeric } from 'rxjs/internal-compatibility'; }) /** * Service to manage the pagination of different components + * The pagination information will be stored in the route based on a paginationID. + * The following params are used for the different kind of pagination information: + * - For the page: p.{paginationID} + * - For the page size: rpp.{paginationID} + * - For the sort direction: sd.{paginationID} + * - For the sort field: sf.{paginationID} */ export class PaginationService { diff --git a/src/app/core/shared/search/search-configuration.service.spec.ts b/src/app/core/shared/search/search-configuration.service.spec.ts index f1f9dc7b9b..061182c2fc 100644 --- a/src/app/core/shared/search/search-configuration.service.spec.ts +++ b/src/app/core/shared/search/search-configuration.service.spec.ts @@ -5,6 +5,7 @@ import { SortDirection, SortOptions } from '../../cache/models/sort-options.mode import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model'; import { SearchFilter } from '../../../shared/search/search-filter.model'; import { of as observableOf } from 'rxjs'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('SearchConfigurationService', () => { let service: SearchConfigurationService; @@ -30,17 +31,13 @@ describe('SearchConfigurationService', () => { getRouteParameterValue: observableOf('') }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(defaults.pagination), - getCurrentSort: observableOf(defaults.sort), - getRouteParameterValue: observableOf('') - }); + const paginationService = new PaginationServiceStub(); const activatedRoute: any = new ActivatedRouteStub(); beforeEach(() => { - service = new SearchConfigurationService(routeService, paginationService, activatedRoute); + service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute); }); describe('when the scope is called', () => { beforeEach(() => { diff --git a/src/app/core/shared/search/search.service.spec.ts b/src/app/core/shared/search/search.service.spec.ts index 9eb650219b..60cb0a87b9 100644 --- a/src/app/core/shared/search/search.service.spec.ts +++ b/src/app/core/shared/search/search.service.spec.ts @@ -27,6 +27,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { SortDirection, SortOptions } from '../../cache/models/sort-options.model'; import { FindListOptions } from '../../data/request.models'; import { SearchConfigurationService } from './search-configuration.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; @Component({ template: '' }) class DummyComponent { @@ -102,16 +103,7 @@ describe('SearchService', () => { } }; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {} - }); + const paginationService = new PaginationServiceStub(); const searchConfigService = {paginationID: 'page-id'}; beforeEach(() => { diff --git a/src/app/process-page/overview/process-overview.component.spec.ts b/src/app/process-page/overview/process-overview.component.spec.ts index 1da2ac2b5b..98e78f6b36 100644 --- a/src/app/process-page/overview/process-overview.component.spec.ts +++ b/src/app/process-page/overview/process-overview.component.spec.ts @@ -17,6 +17,7 @@ import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../core/data/request.models'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('ProcessOverviewComponent', () => { let component: ProcessOverviewComponent; @@ -24,15 +25,11 @@ describe('ProcessOverviewComponent', () => { let processService: ProcessDataService; let ePersonService: EPersonDataService; - let paginationService: PaginationService; + let paginationService; let processes: Process[]; let ePerson: EPerson; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - function init() { processes = [ Object.assign(new Process(), { @@ -80,11 +77,7 @@ describe('ProcessOverviewComponent', () => { findById: createSuccessfulRemoteDataObject$(ePerson) }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - }); + paginationService = new PaginationServiceStub(); } beforeEach(waitForAsync(() => { diff --git a/src/app/search-navbar/search-navbar.component.spec.ts b/src/app/search-navbar/search-navbar.component.spec.ts index 87b75328bb..ba08c7ca75 100644 --- a/src/app/search-navbar/search-navbar.component.spec.ts +++ b/src/app/search-navbar/search-navbar.component.spec.ts @@ -13,6 +13,7 @@ import { SortDirection, SortOptions } from '../core/cache/models/sort-options.mo import { of as observableOf } from 'rxjs'; import { PaginationService } from '../core/pagination/pagination.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; +import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; describe('SearchNavbarComponent', () => { let component: SearchNavbarComponent; @@ -33,16 +34,7 @@ describe('SearchNavbarComponent', () => { navigate: (commands) => commands }; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf(''), - updateRouteWithUrl: {}, - clearPagination : {} - }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [ diff --git a/src/app/shared/browse-by/browse-by.component.spec.ts b/src/app/shared/browse-by/browse-by.component.spec.ts index 4c9ac9204d..806f4bdb6f 100644 --- a/src/app/shared/browse-by/browse-by.component.spec.ts +++ b/src/app/shared/browse-by/browse-by.component.spec.ts @@ -20,6 +20,7 @@ import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { storeModuleConfig } from '../../app.reducer'; import { FindListOptions } from '../../core/data/request.models'; import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; describe('BrowseByComponent', () => { let comp: BrowseByComponent; @@ -53,15 +54,7 @@ describe('BrowseByComponent', () => { pageSizeOptions: [5, 10, 15, 20], pageSize: 15 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(paginationConfig), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - updateRoute: {}, - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(paginationConfig); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/item/item-versions/item-versions.component.spec.ts b/src/app/shared/item/item-versions/item-versions.component.spec.ts index 33e99eb88f..cc28779537 100644 --- a/src/app/shared/item/item-versions/item-versions.component.spec.ts +++ b/src/app/shared/item/item-versions/item-versions.component.spec.ts @@ -15,14 +15,12 @@ import { PaginationComponentOptions } from '../../pagination/pagination-componen import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('ItemVersionsComponent', () => { let component: ItemVersionsComponent; let fixture: ComponentFixture; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const versionHistory = Object.assign(new VersionHistory(), { id: '1' }); @@ -59,11 +57,7 @@ describe('ItemVersionsComponent', () => { getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions)) }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf('') - }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts index 60cce1ae09..199bc56647 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -17,6 +17,7 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti import { FindListOptions } from '../../../core/data/request.models'; import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('CollectionSelectComponent', () => { let comp: CollectionSelectComponent; @@ -40,16 +41,7 @@ describe('CollectionSelectComponent', () => { currentPage: 1 }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); - + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 2ca3cd4fa8..224fb764b6 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -17,12 +17,13 @@ import { createPaginatedList } from '../../testing/utils.test'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; - let paginationService: PaginationService; + let paginationService; const mockItemList = [ Object.assign(new Item(), { @@ -63,14 +64,7 @@ describe('ItemSelectComponent', () => { currentPage: 1 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(mockPaginationOptions), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + paginationService = new PaginationServiceStub(mockPaginationOptions); diff --git a/src/app/shared/page-size-selector/page-size-selector.component.spec.ts b/src/app/shared/page-size-selector/page-size-selector.component.spec.ts index cf7f1d5e11..77931400a2 100644 --- a/src/app/shared/page-size-selector/page-size-selector.component.spec.ts +++ b/src/app/shared/page-size-selector/page-size-selector.component.spec.ts @@ -13,6 +13,7 @@ import { EnumKeysPipe } from '../utils/enum-keys-pipe'; import { VarDirective } from '../utils/var.directive'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; describe('PageSizeSelectorComponent', () => { @@ -34,11 +35,7 @@ describe('PageSizeSelectorComponent', () => { sort }; - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf('') - }); + const paginationService = new PaginationServiceStub(pagination, sort); const activatedRouteStub = { queryParams: observableOf({ diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts index 53a4619071..0c6cd80a62 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts @@ -14,6 +14,7 @@ import { ObjectValuesPipe } from '../utils/object-values-pipe'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; @Component({ selector: 'ds-mock-paginated-drag-drop-abstract', @@ -47,8 +48,6 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { const url = 'mock-abstract-paginated-drag-and-drop-list-component'; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); const object1 = Object.assign(new DSpaceObject(), { uuid: 'object-1' }); const object2 = Object.assign(new DSpaceObject(), { uuid: 'object-2' }); @@ -77,11 +76,7 @@ describe('AbstractPaginatedDragAndDropListComponent', () => { paginationComponent = jasmine.createSpyObj('paginationComponent', { doPageChange: {} }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getRouteParameterValue: observableOf('') - }); + paginationService = new PaginationServiceStub(); objectsRD$ = new BehaviorSubject(objectsRD); component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, objectValuesPipe, url, paginationService, objectsRD$); component.paginationComponent = paginationComponent; diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 66308df67f..3c50f66158 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -34,6 +34,7 @@ import { storeModuleConfig } from '../../app.reducer'; import { PaginationService } from '../../core/pagination/pagination.service'; import { FindListOptions } from '../../core/data/request.models'; import { BehaviorSubject, of as observableOf } from 'rxjs'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; function expectPages(fixture: ComponentFixture, pagesDef: string[]): void { const de = fixture.debugElement.query(By.css('.pagination')); @@ -108,7 +109,7 @@ describe('Pagination component', () => { let activatedRouteStub: MockActivatedRoute; let routerStub: RouterMock; - let paginationService: PaginationService; + let paginationService; // Define initial state and test state const _initialState = { width: 1600, height: 770 }; diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 1b9adf8922..8f1c6bf26f 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -99,9 +99,8 @@ export class PaginationComponent implements OnDestroy, OnInit { @Input() public hidePagerWhenSinglePage = true; /** - * Option for disabling updating and reading route parameters on pagination changes - * In other words, changing pagination won't add or update the url parameters on the current page, and the url - * parameters won't affect the pagination of this component + * Option for retaining the scroll position upon navigating to an url with updated params. + * After the page update the page will scroll back to the current pagination component. */ @Input() public retainScrollPosition = false; @@ -121,8 +120,8 @@ export class PaginationComponent implements OnDestroy, OnInit { public hostWindow: Observable; /** - * ID for the pagination instance. Only useful if you wish to - * have more than once instance at a time in a given component. + * ID for the pagination instance. This ID is used in the routing to retrieve the pagination options. + * This ID needs to be unique between different pagination components when more than one will be displayed on the same page. */ public id: string; @@ -156,7 +155,7 @@ export class PaginationComponent implements OnDestroy, OnInit { * Name of the field that's used to sort by */ public sortField$; - public defaultSortField = 'id'; + public defaultSortField = 'name'; /** * Array to track all subscriptions and unsubscribe them onDestroy diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts index a683a536b4..73aaab5170 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts @@ -22,6 +22,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../core/data/request.models'; import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../testing/pagination-service.stub'; describe('EpersonGroupListComponent test suite', () => { let comp: EpersonGroupListComponent; @@ -64,15 +65,7 @@ describe('EpersonGroupListComponent test suite', () => { const groupPaginatedList = buildPaginatedList(new PageInfo(), [GroupMock, GroupMock]); const groupPaginatedListRD = createSuccessfulRemoteDataObject(groupPaginatedList); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(paginationOptions), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - clearPagination: {} - }); + paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index 635d11f0d0..1469eac566 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -14,6 +14,7 @@ import { FindListOptions } from '../../core/data/request.models'; import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../core/pagination/pagination.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; describe('SearchFormComponent', () => { let comp: SearchFormComponent; @@ -21,16 +22,7 @@ describe('SearchFormComponent', () => { let de: DebugElement; let el: HTMLElement; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {} - }); + const paginationService = new PaginationServiceStub(); const searchConfigService = {paginationID: 'test-id'}; diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts index a01843248e..4c7798ed0b 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts @@ -19,6 +19,7 @@ import { PaginationComponentOptions } from '../../../../../pagination/pagination import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../../core/data/request.models'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub'; describe('SearchFacetOptionComponent', () => { let comp: SearchFacetOptionComponent; @@ -85,17 +86,8 @@ describe('SearchFacetOptionComponent', () => { let router; const page = observableOf(0); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - getPageParam: 'p.page-id', - }); - + const pagination = Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 }); + const paginationService = new PaginationServiceStub(pagination); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts index 6329655eca..98cb671dba 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts @@ -23,6 +23,7 @@ import { PaginationComponentOptions } from '../../../../../pagination/pagination import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../../core/data/request.models'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub'; describe('SearchFacetRangeOptionComponent', () => { let comp: SearchFacetRangeOptionComponent; @@ -58,17 +59,8 @@ describe('SearchFacetRangeOptionComponent', () => { let router; const page = observableOf(0); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {}, - getPageParam: 'p.page-id', - }); + const pagination = Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 }); + const paginationService = new PaginationServiceStub(pagination); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts index 60c4fd5721..6a37a4d0ba 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts @@ -18,6 +18,7 @@ import { PaginationComponentOptions } from '../../../../../pagination/pagination import { SortDirection, SortOptions } from '../../../../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../../../../core/data/request.models'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub'; describe('SearchFacetSelectedOptionComponent', () => { let comp: SearchFacetSelectedOptionComponent; @@ -110,17 +111,8 @@ describe('SearchFacetSelectedOptionComponent', () => { let router; const page = observableOf(0); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {}, - getPageParam: 'p.page-id' - }); + const pagination = Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 }); + const paginationService = new PaginationServiceStub(pagination); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts index fe9959aa3a..35de768977 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts @@ -16,6 +16,7 @@ import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-o import { FindListOptions } from '../../../../core/data/request.models'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; +import { PaginationServiceStub } from '../../../testing/pagination-service.stub'; describe('SearchLabelComponent', () => { let comp: SearchLabelComponent; @@ -38,17 +39,8 @@ describe('SearchLabelComponent', () => { filter2 ]; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - updateRouteWithUrl: {}, - getPageParam: 'p.test-id' - }); + const pagination = Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 }); + const paginationService = new PaginationServiceStub(pagination); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/search/search-settings/search-settings.component.spec.ts b/src/app/shared/search/search-settings/search-settings.component.spec.ts index 5ae1f7eaf2..dff8935ef5 100644 --- a/src/app/shared/search/search-settings/search-settings.component.spec.ts +++ b/src/app/shared/search/search-settings/search-settings.component.spec.ts @@ -17,6 +17,7 @@ import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.c import { SidebarService } from '../../sidebar/sidebar.service'; import { SidebarServiceStub } from '../../testing/sidebar-service.stub'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('SearchSettingsComponent', () => { @@ -65,11 +66,7 @@ describe('SearchSettingsComponent', () => { }), }; - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - resetPage: {}, - }); + paginationService = new PaginationServiceStub(pagination, sort); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], diff --git a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts index 4b4556f511..dfee88c955 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts @@ -15,6 +15,7 @@ import { PaginationComponentOptions } from '../../pagination/pagination-componen import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { FindListOptions } from '../../../core/data/request.models'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('StartsWithDateComponent', () => { let comp: StartsWithDateComponent; @@ -30,15 +31,7 @@ describe('StartsWithDateComponent', () => { queryParams: observableOf({}) }); - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - - paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - }); + paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts index 1295f0a33a..9f9d9d6d42 100644 --- a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts +++ b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts @@ -13,6 +13,7 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti import { FindListOptions } from '../../../core/data/request.models'; import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('StartsWithTextComponent', () => { let comp: StartsWithTextComponent; @@ -22,15 +23,7 @@ describe('StartsWithTextComponent', () => { const options = ['0-9', 'A', 'B', 'C']; - const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }); - const sort = new SortOptions('score', SortDirection.DESC); - const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 }); - const paginationService = jasmine.createSpyObj('PaginationService', { - getCurrentPagination: observableOf(pagination), - getCurrentSort: observableOf(sort), - getFindListOptions: observableOf(findlistOptions), - resetPage: {}, - }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/app/shared/testing/pagination-service.stub.ts b/src/app/shared/testing/pagination-service.stub.ts new file mode 100644 index 0000000000..f26c368b14 --- /dev/null +++ b/src/app/shared/testing/pagination-service.stub.ts @@ -0,0 +1,25 @@ +import { of as observableOf } from 'rxjs'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; + +export class PaginationServiceStub { + + constructor( + public pagination = Object.assign(new PaginationComponentOptions(), {currentPage: 1, pageSize: 20}), + public sort = new SortOptions('score', SortDirection.DESC), + public findlistOptions = Object.assign(new FindListOptions(), {currentPage: 1, elementsPerPage: 20}), + ) { + } + + getCurrentPagination = jasmine.createSpy('getCurrentPagination').and.returnValue(observableOf(this.pagination)); + getCurrentSort = jasmine.createSpy('getCurrentSort').and.returnValue(observableOf(this.sort)); + getFindListOptions = jasmine.createSpy('getFindListOptions').and.returnValue(observableOf(this.findlistOptions)); + resetPage = jasmine.createSpy('resetPage'); + updateRoute = jasmine.createSpy('updateRoute'); + updateRouteWithUrl = jasmine.createSpy('updateRouteWithUrl'); + clearPagination = jasmine.createSpy('clearPagination'); + getRouteParameterValue = jasmine.createSpy('getRouteParameterValue').and.returnValue(observableOf('')); + getPageParam = jasmine.createSpy('getPageParam').and.returnValue(`p.${this.pagination.id}`); + +} diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index faaf83389b..fe568533e4 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; -import { filter, mergeMap, take } from 'rxjs/operators'; +import { filter, mergeMap, switchMap, take } from 'rxjs/operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceService } from '../../core/data/external-source.service'; import { ExternalSourceData } from './import-external-searchbar/submission-import-external-searchbar.component'; import { RemoteData } from '../../core/data/remote-data'; -import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { ExternalSourceEntry } from '../../core/shared/external-source-entry.model'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { Context } from '../../core/shared/context.model'; @@ -167,25 +167,27 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * @param query The query string to search */ private retrieveExternalSources(): void { - this.reload$.subscribe((sourceQueryObject: {source: string, query: string}) => { - const source = sourceQueryObject.source; - const query = sourceQueryObject.query; - if (isNotEmpty(source) && isNotEmpty(query)) { - this.routeData.sourceId = source; - this.routeData.query = query; - this.isLoading$.next(true); - this.subs.push( - this.searchConfigService.paginatedSearchOptions.pipe( - filter((searchOptions) => searchOptions.query === query), - mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( - getFinishedRemoteData(), - )), - ).subscribe((rdData) => { - this.entriesRD$.next(rdData); - this.isLoading$.next(false); - }) - ); - } + this.reload$.pipe( + switchMap( + (sourceQueryObject: { source: string, query: string }) => { + const source = sourceQueryObject.source; + const query = sourceQueryObject.query; + if (isNotEmpty(source) && isNotEmpty(query)) { + this.routeData.sourceId = source; + this.routeData.query = query; + this.isLoading$.next(true); + return this.searchConfigService.paginatedSearchOptions.pipe( + filter((searchOptions) => searchOptions.query === query), + mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( + getFinishedRemoteData(), + )), + ); + } + } + ), + ).subscribe((rdData) => { + this.entriesRD$.next(rdData); + this.isLoading$.next(false); }); } From a4076ed1fd66c8a76cbca1ca9edfa73c86da1c41 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 29 Mar 2021 16:13:20 +0200 Subject: [PATCH 10/46] Fix lgtm issue --- .../browse-by-metadata-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts index 9ebc341856..f5adefc779 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -16,7 +16,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map, switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-browse-by-metadata-page', From 824ee029b3f0382eebb51090d27cff2bd4d441ac Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 1 Apr 2021 16:28:20 +0200 Subject: [PATCH 11/46] Change p. to page and reverse id and pagination info --- .../pagination/pagination.service.spec.ts | 58 +++++++++---------- src/app/core/pagination/pagination.service.ts | 26 ++++----- .../search-form/search-form.component.ts | 2 +- .../search-facet-option.component.spec.ts | 4 +- ...earch-facet-range-option.component.spec.ts | 2 +- ...ch-facet-selected-option.component.spec.ts | 4 +- .../shared/testing/pagination-service.stub.ts | 2 +- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/app/core/pagination/pagination.service.spec.ts b/src/app/core/pagination/pagination.service.spec.ts index f689f4f0f0..18f94cc84c 100644 --- a/src/app/core/pagination/pagination.service.spec.ts +++ b/src/app/core/pagination/pagination.service.spec.ts @@ -20,16 +20,16 @@ describe('PaginationService', () => { routeService = { getQueryParameterValue: (param) => { let value; - if (param.startsWith('p.')) { + if (param.endsWith('.page')) { value = 5; } - if (param.startsWith('rpp.')) { + if (param.endsWith('.rpp')) { value = 10; } - if (param.startsWith('sd.')) { + if (param.endsWith('.sd')) { value = 'ASC'; } - if (param.startsWith('sf.')) { + if (param.endsWith('.sf')) { value = 'score'; } return observableOf(value); @@ -83,10 +83,10 @@ describe('PaginationService', () => { service.updateRoute('test', {page: 2, pageSize: 5, sortField: 'title', sortDirection: SortDirection.DESC}); const navigateParams = {}; - navigateParams[`p.test`] = `2`; - navigateParams[`rpp.test`] = `5`; - navigateParams[`sf.test`] = `title`; - navigateParams[`sd.test`] = `DESC`; + navigateParams[`test.page`] = `2`; + navigateParams[`test.rpp`] = `5`; + navigateParams[`test.sf`] = `title`; + navigateParams[`test.sd`] = `DESC`; expect(router.navigate).toHaveBeenCalledWith([], {queryParams: navigateParams, queryParamsHandling: 'merge'}); }); @@ -94,10 +94,10 @@ describe('PaginationService', () => { service.updateRoute('test', {page: 2}); const navigateParams = {}; - navigateParams[`p.test`] = `2`; - navigateParams[`rpp.test`] = `10`; - navigateParams[`sf.test`] = `score`; - navigateParams[`sd.test`] = `ASC`; + navigateParams[`test.page`] = `2`; + navigateParams[`test.rpp`] = `10`; + navigateParams[`test.sf`] = `score`; + navigateParams[`test.sd`] = `ASC`; expect(router.navigate).toHaveBeenCalledWith([], {queryParams: navigateParams, queryParamsHandling: 'merge'}); }); @@ -107,10 +107,10 @@ describe('PaginationService', () => { service.updateRouteWithUrl('test', ['someUrl'], {page: 2, pageSize: 5, sortField: 'title', sortDirection: SortDirection.DESC}); const navigateParams = {}; - navigateParams[`p.test`] = `2`; - navigateParams[`rpp.test`] = `5`; - navigateParams[`sf.test`] = `title`; - navigateParams[`sd.test`] = `DESC`; + navigateParams[`test.page`] = `2`; + navigateParams[`test.rpp`] = `5`; + navigateParams[`test.sf`] = `title`; + navigateParams[`test.sd`] = `DESC`; expect(router.navigate).toHaveBeenCalledWith(['someUrl'], {queryParams: navigateParams, queryParamsHandling: 'merge'}); }); @@ -118,10 +118,10 @@ describe('PaginationService', () => { service.updateRouteWithUrl('test',['someUrl'], {page: 2}); const navigateParams = {}; - navigateParams[`p.test`] = `2`; - navigateParams[`rpp.test`] = `10`; - navigateParams[`sf.test`] = `score`; - navigateParams[`sd.test`] = `ASC`; + navigateParams[`test.page`] = `2`; + navigateParams[`test.rpp`] = `10`; + navigateParams[`test.sf`] = `score`; + navigateParams[`test.sd`] = `ASC`; expect(router.navigate).toHaveBeenCalledWith(['someUrl'], {queryParams: navigateParams, queryParamsHandling: 'merge'}); }); @@ -132,17 +132,17 @@ describe('PaginationService', () => { service.clearPagination('test'); const resetParams = {}; - resetParams[`p.test`] = null; - resetParams[`rpp.test`] = null; - resetParams[`sf.test`] = null; - resetParams[`sd.test`] = null; + resetParams[`test.page`] = null; + resetParams[`test.rpp`] = null; + resetParams[`test.sf`] = null; + resetParams[`test.sd`] = null; const navigateParams = {}; - navigateParams[`p.another-id`] = `5`; - navigateParams[`rpp.another-id`] = `10`; - navigateParams[`sf.another-id`] = `score`; - navigateParams[`sd.another-id`] = `ASC`; + navigateParams[`another-id.page`] = `5`; + navigateParams[`another-id.rpp`] = `10`; + navigateParams[`another-id.sf`] = `score`; + navigateParams[`another-id.sd`] = `ASC`; service.updateRoute('another-id', {}); @@ -152,7 +152,7 @@ describe('PaginationService', () => { describe('getPageParam', () => { it('should return the name of the page param', () => { const pageParam = service.getPageParam('test'); - expect(pageParam).toEqual('p.test'); + expect(pageParam).toEqual('test.page'); }); }); }); diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index 8ed81ba8d4..b6ae304491 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -41,8 +41,8 @@ export class PaginationService { * @returns {Observable} Retrieves the current pagination settings based on the router params */ getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { - const page$ = this.routeService.getQueryParameterValue(`p.${paginationId}`); - const size$ = this.routeService.getQueryParameterValue(`rpp.${paginationId}`); + const page$ = this.routeService.getQueryParameterValue(`${paginationId}.page`); + const size$ = this.routeService.getQueryParameterValue(`${paginationId}.rpp`); return observableCombineLatest([page$, size$]).pipe( map(([page, size]) => { return Object.assign(new PaginationComponentOptions(), defaultPagination, { @@ -64,8 +64,8 @@ export class PaginationService { if (!ignoreDefault && (isEmpty(defaultSort) || !hasValue(defaultSort))) { defaultSort = this.defaultSortOptions; } - const sortDirection$ = this.routeService.getQueryParameterValue(`sd.${paginationId}`); - const sortField$ = this.routeService.getQueryParameterValue(`sf.${paginationId}`); + const sortDirection$ = this.routeService.getQueryParameterValue(`${paginationId}.sd`); + const sortField$ = this.routeService.getQueryParameterValue(`${paginationId}.sf`); return observableCombineLatest([sortDirection$, sortField$]).pipe(map(([sortDirection, sortField]) => { const field = sortField || defaultSort?.field; const direction = SortDirection[sortDirection] || defaultSort?.direction; @@ -171,10 +171,10 @@ export class PaginationService { */ clearPagination(paginationId: string) { const params = {}; - params[`p.${paginationId}`] = null; - params[`rpp.${paginationId}`] = null; - params[`sf.${paginationId}`] = null; - params[`sd.${paginationId}`] = null; + params[`${paginationId}.page`] = null; + params[`${paginationId}.rpp`] = null; + params[`${paginationId}.sf`] = null; + params[`${paginationId}.sd`] = null; Object.assign(this.clearParams, params); } @@ -184,7 +184,7 @@ export class PaginationService { * @param paginationId - The ID for which to retrieve the page param */ getPageParam(paginationId: string) { - return `p.${paginationId}`; + return `${paginationId}.page`; } private getCurrentRouting(paginationId: string) { @@ -209,16 +209,16 @@ export class PaginationService { }) { const paramsWithIdName = {}; if (hasValue(params.page)) { - paramsWithIdName[`p.${paginationId}`] = `${params.page}`; + paramsWithIdName[`${paginationId}.page`] = `${params.page}`; } if (hasValue(params.pageSize)) { - paramsWithIdName[`rpp.${paginationId}`] = `${params.pageSize}`; + paramsWithIdName[`${paginationId}.rpp`] = `${params.pageSize}`; } if (hasValue(params.sortField)) { - paramsWithIdName[`sf.${paginationId}`] = `${params.sortField}`; + paramsWithIdName[`${paginationId}.sf`] = `${params.sortField}`; } if (hasValue(params.sortDirection)) { - paramsWithIdName[`sd.${paginationId}`] = `${params.sortDirection}`; + paramsWithIdName[`${paginationId}.sd`] = `${params.sortDirection}`; } return paramsWithIdName; } diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 594f0c84e2..1c9aadf03d 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -91,7 +91,7 @@ export class SearchFormComponent { */ updateSearch(data: any) { const queryParams = Object.assign({}, data); - queryParams[`p.${this.searchConfig.paginationID}`] = 1; + queryParams[`page.${this.searchConfig.paginationID}`] = 1; this.router.navigate(this.getSearchLinkParts(), { queryParams: queryParams, diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts index 4c7798ed0b..a266e4e425 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts @@ -140,7 +140,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'], - ['p.page-id']: 1 + ['page.page-id']: 1 }); }); }); @@ -155,7 +155,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`], - ['p.page-id']: 1 + ['page.page-id']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts index 98cb671dba..2c93b0186b 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts @@ -126,7 +126,7 @@ describe('SearchFacetRangeOptionComponent', () => { expect(comp.changeQueryParams).toEqual({ [mockFilterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: ['50'], [mockFilterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: ['60'], - ['p.page-id']: 1 + ['page.page-id']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts index 6a37a4d0ba..cbb6672916 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts @@ -165,7 +165,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedValues); expect(comp.removeQueryParams).toEqual({ [mockFilterConfig.paramName]: [value1], - ['p.page-id']: 1 + ['page.page-id']: 1 }); }); }); @@ -181,7 +181,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedAuthorityValues); expect(comp.removeQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [`${value1},${operator}`], - ['p.page-id']: 1 + ['page.page-id']: 1 }); }); }); diff --git a/src/app/shared/testing/pagination-service.stub.ts b/src/app/shared/testing/pagination-service.stub.ts index f26c368b14..cd24808161 100644 --- a/src/app/shared/testing/pagination-service.stub.ts +++ b/src/app/shared/testing/pagination-service.stub.ts @@ -20,6 +20,6 @@ export class PaginationServiceStub { updateRouteWithUrl = jasmine.createSpy('updateRouteWithUrl'); clearPagination = jasmine.createSpy('clearPagination'); getRouteParameterValue = jasmine.createSpy('getRouteParameterValue').and.returnValue(observableOf('')); - getPageParam = jasmine.createSpy('getPageParam').and.returnValue(`p.${this.pagination.id}`); + getPageParam = jasmine.createSpy('getPageParam').and.returnValue(`page.${this.pagination.id}`); } From 77a85793b6d688224514558a2de7bf4c0226b286 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:08:52 +0100 Subject: [PATCH 12/46] delete mantis theme --- src/assets/images/dspace-logo-monochrome.svg | 37 -------- .../home-news/home-news.component.html | 21 ----- .../home-news/home-news.component.scss | 17 ---- .../app/+home-page/home-page.component.html | 10 --- .../app/+home-page/home-page.component.scss | 52 ----------- .../simple/item-page.component.html | 9 -- .../publication/publication.component.html | 87 ------------------ .../publication/publication.component.scss | 30 ------- .../journal-issue.component.html | 77 ---------------- .../journal-issue.component.scss | 30 ------- .../journal-volume.component.html | 62 ------------- .../journal-volume.component.scss | 30 ------- .../item-pages/journal/journal.component.html | 70 --------------- .../item-pages/journal/journal.component.scss | 38 -------- .../org-unit/org-unit.component.html | 81 ----------------- .../org-unit/org-unit.component.scss | 30 ------- .../item-pages/person/person.component.html | 88 ------------------- .../item-pages/person/person.component.scss | 38 -------- .../item-pages/project/project.component.html | 84 ------------------ .../item-pages/project/project.component.scss | 30 ------- .../mantis/app/navbar/navbar.component.html | 16 ---- .../mantis/app/navbar/navbar.component.scss | 7 -- .../search-form/search-form.component.html | 21 ----- .../search-facet-option.component.html | 9 -- .../search-facet-range-option.component.html | 8 -- .../search-filter.component.html | 7 -- .../search-filter.component.scss | 10 --- .../search-range-filter.component.scss | 5 -- .../search-filters.component.html | 7 -- .../search-settings.component.html | 24 ----- .../search-settings.component.scss | 10 --- src/themes/mantis/readme.md | 2 - .../styles/_themed_custom_variables.scss | 4 - 33 files changed, 1051 deletions(-) delete mode 100644 src/assets/images/dspace-logo-monochrome.svg delete mode 100644 src/themes/mantis/app/+home-page/home-news/home-news.component.html delete mode 100644 src/themes/mantis/app/+home-page/home-news/home-news.component.scss delete mode 100644 src/themes/mantis/app/+home-page/home-page.component.html delete mode 100644 src/themes/mantis/app/+home-page/home-page.component.scss delete mode 100644 src/themes/mantis/app/+item-page/simple/item-page.component.html delete mode 100644 src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html delete mode 100644 src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss delete mode 100644 src/themes/mantis/app/navbar/navbar.component.html delete mode 100644 src/themes/mantis/app/navbar/navbar.component.scss delete mode 100644 src/themes/mantis/app/shared/search-form/search-form.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filters.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-settings/search-settings.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss delete mode 100644 src/themes/mantis/readme.md delete mode 100644 src/themes/mantis/styles/_themed_custom_variables.scss diff --git a/src/assets/images/dspace-logo-monochrome.svg b/src/assets/images/dspace-logo-monochrome.svg deleted file mode 100644 index 5a2204ad83..0000000000 --- a/src/assets/images/dspace-logo-monochrome.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/src/themes/mantis/app/+home-page/home-news/home-news.component.html b/src/themes/mantis/app/+home-page/home-news/home-news.component.html deleted file mode 100644 index 4da3ae12f7..0000000000 --- a/src/themes/mantis/app/+home-page/home-news/home-news.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
-

DSpace 7

-

DSpace is the world leading open source repository platform that enables - organisations to:

-
-
-
    -
  • easily ingest documents, audio, video, datasets and their corresponding Dublin Core - metadata -
  • -
  • open up this content to local and global audiences, thanks to the OAI-PMH interface and - Google Scholar optimizations -
  • -
  • issue permanent urls and trustworthy identifiers, including optional integrations with - handle.net and DataCite DOI -
  • -
-

Join an international community of leading institutions using DSpace.

-
diff --git a/src/themes/mantis/app/+home-page/home-news/home-news.component.scss b/src/themes/mantis/app/+home-page/home-news/home-news.component.scss deleted file mode 100644 index b82d84a71e..0000000000 --- a/src/themes/mantis/app/+home-page/home-news/home-news.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import 'src/app/+home-page/home-news/home-news.component.scss'; -:host { - --ds-home-news-link-color: #{$green}; - --ds-home-news-link-hover-color: #{darken($green, 15%)}; - - .jumbotron { - background-color: transparent; - } - - a { - color: var(--ds-home-news-link-color); - - @include hover { - color: var(--ds-home-news-link-hover-color); - } - } -} diff --git a/src/themes/mantis/app/+home-page/home-page.component.html b/src/themes/mantis/app/+home-page/home-page.component.html deleted file mode 100644 index 43edbee9ca..0000000000 --- a/src/themes/mantis/app/+home-page/home-page.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
- - -
- Photo by @inspiredimages -
-
- -
diff --git a/src/themes/mantis/app/+home-page/home-page.component.scss b/src/themes/mantis/app/+home-page/home-page.component.scss deleted file mode 100644 index d4350c1e13..0000000000 --- a/src/themes/mantis/app/+home-page/home-page.component.scss +++ /dev/null @@ -1,52 +0,0 @@ -@import 'src/app/+home-page/home-page.component.scss'; - -div.background-image { - color: white; - background-color: var(--bs-info); - position: relative; - background-position-y: -200px; - background-image: url('/assets/images/banner.jpg'); - background-size: cover; - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - background-position-y: 0; - } - - .container { - position: relative; - text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); - - &:before, &:after { - content: ''; - display: block; - width: var(--ds-banner-background-gradient-width); - height: 100%; - top: 0; - position: absolute; - } - - &:before { - background: linear-gradient(to left, var(--ds-banner-text-background), transparent); - left: calc(-1 * var(--ds-banner-background-gradient-width)); - - } - - &:after { - background: linear-gradient(to right, var(--ds-banner-text-background), transparent); - right: calc(-1 * var(--ds-banner-background-gradient-width)); - } - - background-color: var(--ds-banner-text-background); - } - - - small.credits { - a { - color: inherit; - } - - opacity: 0.3; - position: absolute; - right: var(--bs-spacer); - bottom: 0; - } -} diff --git a/src/themes/mantis/app/+item-page/simple/item-page.component.html b/src/themes/mantis/app/+item-page/simple/item-page.component.html deleted file mode 100644 index 83f910e0cd..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-page.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
- -
-
- - -
diff --git a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html b/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html deleted file mode 100644 index 705d40a7c7..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html +++ /dev/null @@ -1,87 +0,0 @@ -a
-
-
- - -
- -
-
- - - - - - - - - - - -
-
- - - - - - < - - -
-
-
-
-
-
-
-
-
- - - - - - -
-
-
diff --git a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss b/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss deleted file mode 100644 index 6d12fcec71..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/+item-page/simple/item-types/publication/publication.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html deleted file mode 100644 index eb872ca175..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ /dev/null @@ -1,77 +0,0 @@ -
-
-
- - -
-

- {{'journalissue.page.titleprefix' | translate}} - -

-
-
- - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - -
-
-
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss deleted file mode 100644 index c0a314d715..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html deleted file mode 100644 index bc11d4ba74..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ /dev/null @@ -1,62 +0,0 @@ -
-
-
- - -
-

- {{'journalvolume.page.titleprefix' | translate}} - -

-
-
- - - - - -
-
- - -
-
-
-
-
-
-
-
-
- - - - -
-
-
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss deleted file mode 100644 index 6e418258d3..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html deleted file mode 100644 index cf4cef27de..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ /dev/null @@ -1,70 +0,0 @@ -
-
-
- -
-

- {{'journal.page.titleprefix' | translate}} - -

-
-
- - - - - - - -
-
- - -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

{{"item.page.journal.search.title" | translate}}

-
- - -
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss deleted file mode 100644 index d2a7c8ca46..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } - - .search-container { - margin-bottom: var(--bs-spacer); - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - width: 100%; - max-width: none; - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html deleted file mode 100644 index 114443468a..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ /dev/null @@ -1,81 +0,0 @@ -
-
-
- - -
-

- {{'orgunit.page.titleprefix' | translate}} - -

-
-
- - - - - - -
-
- - - - -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
- - -
-
diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss deleted file mode 100644 index aff2622323..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html deleted file mode 100644 index 077ac1b9f1..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ /dev/null @@ -1,88 +0,0 @@ -
-
-
- - -
-

- {{'person.page.titleprefix' | translate}} -

-
-
- - - - - - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - -
-
-
-
-
-

{{"item.page.person.search.title" | translate}}

-
- - -
diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss deleted file mode 100644 index 51c15716f9..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/person/person.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } - - .search-container { - margin-bottom: var(--bs-spacer); - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - width: 100%; - max-width: none; - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html deleted file mode 100644 index e5b0884f14..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html +++ /dev/null @@ -1,84 +0,0 @@ -
-
-
- - -
-

- {{'project.page.titleprefix' | translate}} -

-
-
- - - - - - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - - - -
-
-
- diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss deleted file mode 100644 index 076baad1a0..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/project/project.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/navbar/navbar.component.html b/src/themes/mantis/app/navbar/navbar.component.html deleted file mode 100644 index d06eceb222..0000000000 --- a/src/themes/mantis/app/navbar/navbar.component.html +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/src/themes/mantis/app/navbar/navbar.component.scss b/src/themes/mantis/app/navbar/navbar.component.scss deleted file mode 100644 index 1417acff59..0000000000 --- a/src/themes/mantis/app/navbar/navbar.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import 'src/app/navbar/navbar.component.scss'; - -nav.navbar { - border-bottom: 5px $green solid; -} - - diff --git a/src/themes/mantis/app/shared/search-form/search-form.component.html b/src/themes/mantis/app/shared/search-form/search-form.component.html deleted file mode 100644 index ea2f54813e..0000000000 --- a/src/themes/mantis/app/shared/search-form/search-form.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
-
-
- - - - -
-
- diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html deleted file mode 100644 index 86076dfd10..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {{filterValue.value}} - - {{filterValue.count}} - - \ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html deleted file mode 100644 index bdb37cb52d..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html +++ /dev/null @@ -1,8 +0,0 @@ - - {{filterValue.value}} - - {{filterValue.count}} - - \ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html deleted file mode 100644 index 850447a39e..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
{{'search.filters.filter.' + filter.name + '.head'| translate}}
-
- -
-
diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss deleted file mode 100644 index 0e78c64629..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'src/app/shared/search/search-filters/search-filter/search-filter.component.scss'; - -.facet-filter { - background-color: var(--bs-light); - border-radius: var(--bs-border-radius); - - h5 { - font-size: 1.1rem - } -} diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss deleted file mode 100644 index 158a0d3b4e..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import 'src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss'; - -::ng-deep .noUi-connect { - background: var(--bs-info); -} diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html deleted file mode 100644 index b7bb1bf50f..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html +++ /dev/null @@ -1,7 +0,0 @@ -

{{"search.filters.head" | translate}}

-
-
- -
-
-{{"search.filters.reset" | translate}} diff --git a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html b/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html deleted file mode 100644 index 1321ced928..0000000000 --- a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html +++ /dev/null @@ -1,24 +0,0 @@ - -

{{ 'search.sidebar.settings.title' | translate}}

-
-
{{ 'search.sidebar.settings.sort-by' | translate}}
- -
- -
-
{{ 'search.sidebar.settings.rpp' | translate}}
- -
-
\ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss b/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss deleted file mode 100644 index b3ee0ba60e..0000000000 --- a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'src/app/shared/search/search-settings/search-settings.component.scss'; - -.setting-option { - background-color: var(--bs-light); - border-radius: var(--bs-border-radius); - h5 { - font-size: 1.1rem - } -} - diff --git a/src/themes/mantis/readme.md b/src/themes/mantis/readme.md deleted file mode 100644 index 93bdf36f47..0000000000 --- a/src/themes/mantis/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -#Note -For now the existing mantis theme has only been moved to the new themes folder, it has not yet been adapted to work as a dynamic theme. \ No newline at end of file diff --git a/src/themes/mantis/styles/_themed_custom_variables.scss b/src/themes/mantis/styles/_themed_custom_variables.scss deleted file mode 100644 index 60d248c4ae..0000000000 --- a/src/themes/mantis/styles/_themed_custom_variables.scss +++ /dev/null @@ -1,4 +0,0 @@ -:root { - --ds-banner-text-background: rgba(0, 0, 0, 0.35); - --ds-banner-background-gradient-width: 300px; -} From fb90bf64d93a021a197150fcb78f561fd2c4954e Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:13:11 +0100 Subject: [PATCH 13/46] create simple dspace theme based on mantis --- angular.json | 5 + .../dspace/app/navbar/navbar.component.scss | 5 + .../dspace/app/navbar/navbar.component.ts | 15 +++ src/themes/dspace/assets/fonts/.gitkeep | 0 src/themes/dspace/assets/images/.gitkeep | 0 src/themes/dspace/entry-components.ts | 2 + src/themes/dspace/styles/_global-styles.scss | 23 ++++ .../styles/_theme_css_variable_overrides.scss | 9 ++ .../_theme_sass_variable_overrides.scss} | 13 ++- src/themes/dspace/styles/theme.scss | 12 ++ src/themes/dspace/theme.module.ts | 103 ++++++++++++++++++ 11 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/themes/dspace/app/navbar/navbar.component.scss create mode 100644 src/themes/dspace/app/navbar/navbar.component.ts create mode 100644 src/themes/dspace/assets/fonts/.gitkeep create mode 100644 src/themes/dspace/assets/images/.gitkeep create mode 100644 src/themes/dspace/entry-components.ts create mode 100644 src/themes/dspace/styles/_global-styles.scss create mode 100644 src/themes/dspace/styles/_theme_css_variable_overrides.scss rename src/themes/{mantis/styles/_themed_bootstrap_variables.scss => dspace/styles/_theme_sass_variable_overrides.scss} (56%) create mode 100644 src/themes/dspace/styles/theme.scss create mode 100644 src/themes/dspace/theme.module.ts diff --git a/angular.json b/angular.json index 19cbe94be6..00799dc33c 100644 --- a/angular.json +++ b/angular.json @@ -56,6 +56,11 @@ "input": "src/themes/custom/styles/theme.scss", "inject": false, "bundleName": "custom-theme" + }, + { + "input": "src/themes/dspace/styles/theme.scss", + "inject": false, + "bundleName": "dspace-theme" } ], "scripts": [] diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss new file mode 100644 index 0000000000..463a4269ee --- /dev/null +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -0,0 +1,5 @@ +@import 'src/app/navbar/navbar.component.scss'; + +nav.navbar { + border-bottom: 5px var(--bs-green) solid; +} diff --git a/src/themes/dspace/app/navbar/navbar.component.ts b/src/themes/dspace/app/navbar/navbar.component.ts new file mode 100644 index 0000000000..e375011683 --- /dev/null +++ b/src/themes/dspace/app/navbar/navbar.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { NavbarComponent as BaseComponent } from '../../../../app/navbar/navbar.component'; +import { slideMobileNav } from '../../../../app/shared/animations/slide'; + +/** + * Component representing the public navbar + */ +@Component({ + selector: 'ds-navbar', + styleUrls: ['./navbar.component.scss'], + templateUrl: '../../../../app/navbar/navbar.component.html', + animations: [slideMobileNav] +}) +export class NavbarComponent extends BaseComponent { +} diff --git a/src/themes/dspace/assets/fonts/.gitkeep b/src/themes/dspace/assets/fonts/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/dspace/assets/images/.gitkeep b/src/themes/dspace/assets/images/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/dspace/entry-components.ts b/src/themes/dspace/entry-components.ts new file mode 100644 index 0000000000..2386ecb130 --- /dev/null +++ b/src/themes/dspace/entry-components.ts @@ -0,0 +1,2 @@ +export const ENTRY_COMPONENTS = [ +]; diff --git a/src/themes/dspace/styles/_global-styles.scss b/src/themes/dspace/styles/_global-styles.scss new file mode 100644 index 0000000000..1fb60b64a2 --- /dev/null +++ b/src/themes/dspace/styles/_global-styles.scss @@ -0,0 +1,23 @@ +// Add any global css for the theme here + +// imports the base global style +@import '../../../styles/_global-styles.scss'; + +.facet-filter,.setting-option { + background-color: var(--bs-light); + border-radius: var(--bs-border-radius); + + &.p-3 { + // Needs !important because the original bootstrap class uses it + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + + .badge-secondary { + background-color: var(--bs-primary); + } + + h5 { + font-size: 1.1rem + } +} diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss new file mode 100644 index 0000000000..f95ab5c764 --- /dev/null +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -0,0 +1,9 @@ +// Override or add CSS variables for your theme here + +:root { + --ds-banner-text-background: rgba(0, 0, 0, 0.35); + --ds-banner-background-gradient-width: 300px; + --ds-home-news-link-color: $green; + --ds-home-news-link-hover-color: #{darken($green, 15%)}; +} + diff --git a/src/themes/mantis/styles/_themed_bootstrap_variables.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss similarity index 56% rename from src/themes/mantis/styles/_themed_bootstrap_variables.scss rename to src/themes/dspace/styles/_theme_sass_variable_overrides.scss index e606502e09..86d4092a53 100644 --- a/src/themes/mantis/styles/_themed_bootstrap_variables.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -1,13 +1,20 @@ +// DSpace works with CSS variables for its own components, and has a mapping of all bootstrap Sass +// variables to CSS equivalents (see src/styles/_bootstrap_variables_mapping.scss). However Bootstrap +// still uses Sass variables internally. So if you want to override bootstrap (or other sass +// variables) you can do so here. Their CSS counterparts will include the changes you make here + @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,200i,300,300i,400,400i,600,600i,700,700i,900,900i&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese'); $font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -$gray-100: #e8ebf3 !default; // #eee +$gray-100: #e8ebf3 !default; +$gray-400: #ced4da !default; $gray-800: #444444 !default; // #444 $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ $blue: #43515f !default; +//$green: #92C642 !default; $green: #92C642 !default; $cyan: #2e80a3 !default; $yellow: #ec9433 !default; @@ -16,4 +23,8 @@ $dark: #43515f !default; $body-color: $gray-800 !default; +$table-accent-bg: $gray-100 !default; +$table-hover-bg: $gray-400 !default; + $yiq-contrasted-threshold: 170 !default; + diff --git a/src/themes/dspace/styles/theme.scss b/src/themes/dspace/styles/theme.scss new file mode 100644 index 0000000000..e4cc9e45ed --- /dev/null +++ b/src/themes/dspace/styles/theme.scss @@ -0,0 +1,12 @@ +// This file combines the other scss files in to one. You usually shouldn't edit this file directly + +@import './_theme_sass_variable_overrides.scss'; +@import '../../../styles/_variables.scss'; +@import '../../../styles/_mixins.scss'; +@import '../../../styles/helpers/font_awesome_imports.scss'; +@import '../../../../node_modules/bootstrap/scss/bootstrap.scss'; +@import '../../../../node_modules/nouislider/distribute/nouislider.min'; +@import '../../../styles/_custom_variables.scss'; +@import './_theme_css_variable_overrides.scss'; +@import '../../../styles/bootstrap_variables_mapping.scss'; +@import './_global-styles.scss'; diff --git a/src/themes/dspace/theme.module.ts b/src/themes/dspace/theme.module.ts new file mode 100644 index 0000000000..2959728156 --- /dev/null +++ b/src/themes/dspace/theme.module.ts @@ -0,0 +1,103 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AdminRegistriesModule } from '../../app/+admin/admin-registries/admin-registries.module'; +import { AdminSearchModule } from '../../app/+admin/admin-search-page/admin-search.module'; +import { AdminWorkflowModuleModule } from '../../app/+admin/admin-workflow-page/admin-workflow.module'; +import { BitstreamFormatsModule } from '../../app/+admin/admin-registries/bitstream-formats/bitstream-formats.module'; +import { BrowseByModule } from '../../app/+browse-by/browse-by.module'; +import { CollectionFormModule } from '../../app/+collection-page/collection-form/collection-form.module'; +import { CommunityFormModule } from '../../app/+community-page/community-form/community-form.module'; +import { CoreModule } from '../../app/core/core.module'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { EditItemPageModule } from '../../app/+item-page/edit-item-page/edit-item-page.module'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { IdlePreloadModule } from 'angular-idle-preload'; +import { JournalEntitiesModule } from '../../app/entity-groups/journal-entities/journal-entities.module'; +import { MyDspaceSearchModule } from '../../app/+my-dspace-page/my-dspace-search.module'; +import { MenuModule } from '../../app/shared/menu/menu.module'; +import { NavbarModule } from '../../app/navbar/navbar.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ProfilePageModule } from '../../app/profile-page/profile-page.module'; +import { RegisterEmailFormModule } from '../../app/register-email-form/register-email-form.module'; +import { ResearchEntitiesModule } from '../../app/entity-groups/research-entities/research-entities.module'; +import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; +import { SearchPageModule } from '../../app/+search-page/search-page.module'; +import { SharedModule } from '../../app/shared/shared.module'; +import { StatisticsModule } from '../../app/statistics/statistics.module'; +import { StoreModule } from '@ngrx/store'; +import { StoreRouterConnectingModule } from '@ngrx/router-store'; +import { TranslateModule } from '@ngx-translate/core'; +import { HomePageModule } from '../../app/+home-page/home-page.module'; +import { AppModule } from '../../app/app.module'; +import { ItemPageModule } from '../../app/+item-page/item-page.module'; +import { RouterModule } from '@angular/router'; +import { CommunityListPageModule } from '../../app/community-list-page/community-list-page.module'; +import { InfoModule } from '../../app/info/info.module'; +import { StatisticsPageModule } from '../../app/statistics-page/statistics-page.module'; +import { CommunityPageModule } from '../../app/+community-page/community-page.module'; +import { CollectionPageModule } from '../../app/+collection-page/collection-page.module'; +import { SubmissionModule } from '../../app/submission/submission.module'; +import { MyDSpacePageModule } from '../../app/+my-dspace-page/my-dspace-page.module'; +import { NavbarComponent } from './app/navbar/navbar.component'; + +const DECLARATIONS = [ + NavbarComponent +]; + +@NgModule({ + imports: [ + AdminRegistriesModule, + AdminSearchModule, + AdminWorkflowModuleModule, + AppModule, + BitstreamFormatsModule, + BrowseByModule, + CollectionFormModule, + CollectionPageModule, + CommonModule, + CommunityFormModule, + CommunityListPageModule, + CommunityPageModule, + CoreModule, + DragDropModule, + ItemPageModule, + EditItemPageModule, + FormsModule, + HomePageModule, + HttpClientModule, + IdlePreloadModule, + InfoModule, + JournalEntitiesModule, + MenuModule, + MyDspaceSearchModule, + NavbarModule, + NgbModule, + ProfilePageModule, + RegisterEmailFormModule, + ResearchEntitiesModule, + RouterModule, + ScrollToModule, + SearchPageModule, + SharedModule, + StatisticsModule, + StatisticsPageModule, + StoreModule, + StoreRouterConnectingModule, + TranslateModule, + SubmissionModule, + MyDSpacePageModule, + MyDspaceSearchModule, + ], + declarations: DECLARATIONS +}) + + /** + * This module serves as an index for all the components in this theme. + * It should import all other modules, so the compiler knows where to find any components referenced + * from a component in this theme + * It is purposefully not exported, it should never be imported anywhere else, its only purpose is + * to give lazily loaded components a context in which they can be compiled successfully + */ +class ThemeModule { +} From 619f4d74c2a7e115ba1c1a641b50d4dc6839a690 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:30:25 +0100 Subject: [PATCH 14/46] make dspace the default theme --- src/environments/environment.common.ts | 8 ++++++-- src/index.html | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index b8248890fc..4e246f7243 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -255,10 +255,14 @@ export const environment: GlobalConfig = { // // A theme with only a name will match every route // name: 'custom' // }, + // { + // // This theme will use the default bootstrap styling for DSpace components + // name: BASE_THEME_NAME + // }, { - // This theme will use the default bootstrap styling for DSpace components - name: BASE_THEME_NAME + // The default dspace theme + name: 'dspace' }, ], // Whether the UI should rewrite file download URLs to match its domain. Only necessary to enable when running UI and REST API on separate domains diff --git a/src/index.html b/src/index.html index 072938b5ef..cb5d35e4f5 100644 --- a/src/index.html +++ b/src/index.html @@ -7,7 +7,7 @@ DSpace - + From 60d917bb8a55671587b999f7b52fc50b49a6cb48 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:31:11 +0100 Subject: [PATCH 15/46] style home-page-news --- .../home-news/home-news.component.html | 26 +++++++ .../home-news/home-news.component.scss | 69 ++++++++++++++++++ .../home-news/home-news.component.ts | 14 ++++ .../dspace}/assets/images/banner.jpg | Bin .../styles/_theme_css_variable_overrides.scss | 2 +- src/themes/dspace/theme.module.ts | 2 + 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.html create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.scss create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.ts rename src/{ => themes/dspace}/assets/images/banner.jpg (100%) diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.html b/src/themes/dspace/app/+home-page/home-news/home-news.component.html new file mode 100644 index 0000000000..9063961ace --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.html @@ -0,0 +1,26 @@ +
+
+
+
+
+

DSpace 7

+

DSpace is the world leading open source repository platform that enables + organisations to:

+
+
+
    +
  • easily ingest documents, audio, video, datasets and their corresponding Dublin Core + metadata +
  • +
  • open up this content to local and global audiences, thanks to the OAI-PMH interface and + Google Scholar optimizations +
  • +
  • issue permanent urls and trustworthy identifiers, including optional integrations with + handle.net and DataCite DOI +
  • +
+

Join an international community of leading institutions using DSpace.

+
+
+ Photo by @inspiredimages +
diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.scss b/src/themes/dspace/app/+home-page/home-news/home-news.component.scss new file mode 100644 index 0000000000..b5a070e51e --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.scss @@ -0,0 +1,69 @@ +:host { + display: block; + margin-top: calc(var(--ds-content-spacing) * -1); + + div.background-image { + color: white; + background-color: var(--bs-info); + position: relative; + background-position-y: -200px; + background-image: url('/assets/dspace/images/banner.jpg'); + background-size: cover; + @media screen and (max-width: map-get($grid-breakpoints, lg)) { + background-position-y: 0; + } + + .container { + position: relative; + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); + + &:before, &:after { + content: ''; + display: block; + width: var(--ds-banner-background-gradient-width); + height: 100%; + top: 0; + position: absolute; + } + + &:before { + background: linear-gradient(to left, var(--ds-banner-text-background), transparent); + left: calc(-1 * var(--ds-banner-background-gradient-width)); + + } + + &:after { + background: linear-gradient(to right, var(--ds-banner-text-background), transparent); + right: calc(-1 * var(--ds-banner-background-gradient-width)); + } + + background-color: var(--ds-banner-text-background); + } + + + small.credits { + a { + color: inherit; + } + + opacity: 0.3; + position: absolute; + right: var(--bs-spacer); + bottom: 0; + } + } + + .jumbotron { + background-color: transparent; + } + + a { + color: var(--ds-home-news-link-color); + + @include hover { + color: var(--ds-home-news-link-hover-color); + } + } +} + + diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.ts b/src/themes/dspace/app/+home-page/home-news/home-news.component.ts new file mode 100644 index 0000000000..7f06295407 --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { HomeNewsComponent as BaseComponent } from '../../../../../app/+home-page/home-news/home-news.component'; + +@Component({ + selector: 'ds-home-news', + styleUrls: ['./home-news.component.scss'], + templateUrl: './home-news.component.html' +}) + +/** + * Component to render the news section on the home page + */ +export class HomeNewsComponent extends BaseComponent {} + diff --git a/src/assets/images/banner.jpg b/src/themes/dspace/assets/images/banner.jpg similarity index 100% rename from src/assets/images/banner.jpg rename to src/themes/dspace/assets/images/banner.jpg diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index f95ab5c764..75d4fd9362 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -3,7 +3,7 @@ :root { --ds-banner-text-background: rgba(0, 0, 0, 0.35); --ds-banner-background-gradient-width: 300px; - --ds-home-news-link-color: $green; + --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; } diff --git a/src/themes/dspace/theme.module.ts b/src/themes/dspace/theme.module.ts index 2959728156..ed840c2e25 100644 --- a/src/themes/dspace/theme.module.ts +++ b/src/themes/dspace/theme.module.ts @@ -28,6 +28,7 @@ import { StatisticsModule } from '../../app/statistics/statistics.module'; import { StoreModule } from '@ngrx/store'; import { StoreRouterConnectingModule } from '@ngrx/router-store'; import { TranslateModule } from '@ngx-translate/core'; +import { HomeNewsComponent } from './app/+home-page/home-news/home-news.component'; import { HomePageModule } from '../../app/+home-page/home-page.module'; import { AppModule } from '../../app/app.module'; import { ItemPageModule } from '../../app/+item-page/item-page.module'; @@ -42,6 +43,7 @@ import { MyDSpacePageModule } from '../../app/+my-dspace-page/my-dspace-page.mod import { NavbarComponent } from './app/navbar/navbar.component'; const DECLARATIONS = [ + HomeNewsComponent, NavbarComponent ]; From cbee776fa4920b547f3ec6067d9d711a190f75cc Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 24 Mar 2021 14:44:10 +0100 Subject: [PATCH 16/46] fix issues with path separators in windows --- src/styles/_mixins.scss | 1 - webpack/helpers.ts | 4 ++-- webpack/webpack.common.ts | 8 +++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 04347b3131..ae9e6082f5 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -1,4 +1,3 @@ -@import '../../node_modules/bootstrap/scss/functions.scss'; @import '../../node_modules/bootstrap/scss/mixins.scss'; @mixin word-wrap() { diff --git a/webpack/helpers.ts b/webpack/helpers.ts index 532b170bf3..43855f6c72 100644 --- a/webpack/helpers.ts +++ b/webpack/helpers.ts @@ -6,8 +6,8 @@ export const projectRoot = (relativePath) => { export const globalCSSImports = () => { return [ - projectRoot('src/styles/_variables.scss'), - projectRoot('src/styles/_mixins.scss'), + projectRoot(path.join('src', 'styles', '_variables.scss')), + projectRoot(path.join('src', 'styles', '_mixins.scss')), ]; }; diff --git a/webpack/webpack.common.ts b/webpack/webpack.common.ts index 926241e0bb..07e55d89d4 100644 --- a/webpack/webpack.common.ts +++ b/webpack/webpack.common.ts @@ -17,7 +17,9 @@ export const copyWebpackOptions = { to: 'assets', }, { - from: path.join(__dirname, '..', 'src', 'themes', '*', 'assets', '**', '*'), + // replace(/\\/g, '/') because glob patterns need forward slashes, even on windows: + // https://github.com/mrmlnc/fast-glob#how-to-write-patterns-on-windows + from: path.join(__dirname, '..', 'src', 'themes', '*', 'assets', '**', '*').replace(/\\/g, '/'), to: 'assets', noErrorOnMissing: true, transformPath(targetPath, absolutePath) { @@ -77,7 +79,7 @@ export const commonExports = { test: /\.scss$/, exclude: [ /node_modules/, - /(_exposed)?_variables.scss$|\/src\/themes\/[^/]+\/styles\/.+.scss$/ + /(_exposed)?_variables.scss$|[\/|\\]src[\/|\\]themes[\/|\\].+?[\/|\\]styles[\/|\\].+\.scss$/ ], use: [ ...SCSS_LOADERS, @@ -90,7 +92,7 @@ export const commonExports = { ] }, { - test: /(_exposed)?_variables.scss$|\/src\/themes\/[^/]+\/styles\/.+.scss$/, + test: /(_exposed)?_variables.scss$|[\/|\\]src[\/|\\]themes[\/|\\].+?[\/|\\]styles[\/|\\].+\.scss$/, exclude: [/node_modules/], use: [ ...SCSS_LOADERS, From b16a565f7cade7ed0973554a9ecd1ddd7af97bdb Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 6 Apr 2021 12:59:38 +0200 Subject: [PATCH 17/46] fixed dropzone layover issue --- src/app/shared/uploader/uploader.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/uploader/uploader.component.html b/src/app/shared/uploader/uploader.component.html index 36078fbeb4..4f7d507404 100644 --- a/src/app/shared/uploader/uploader.component.html +++ b/src/app/shared/uploader/uploader.component.html @@ -1,5 +1,5 @@
Date: Tue, 6 Apr 2021 13:27:20 +0200 Subject: [PATCH 18/46] Unreverted changes from #1053 --- src/app/shared/mocks/submission.mock.ts | 85 +++++++++++++++++++ src/app/shared/uploader/uploader.component.ts | 25 +----- .../form/submission-form.component.html | 65 +++++++------- .../form/submission-form.component.spec.ts | 9 +- .../form/submission-form.component.ts | 12 ++- .../sections/sections.service.spec.ts | 34 ++++++-- .../submission/sections/sections.service.ts | 25 +++++- 7 files changed, 186 insertions(+), 69 deletions(-) diff --git a/src/app/shared/mocks/submission.mock.ts b/src/app/shared/mocks/submission.mock.ts index 16cc9b6262..1ee097af71 100644 --- a/src/app/shared/mocks/submission.mock.ts +++ b/src/app/shared/mocks/submission.mock.ts @@ -1101,6 +1101,91 @@ export const mockSubmissionState: SubmissionObjectState = Object.assign({}, { } }); +export const mockSubmissionStateWithoutUpload: SubmissionObjectState = Object.assign({}, { + 826: { + collection: mockSubmissionCollectionId, + definition: 'traditional', + selfUrl: mockSubmissionSelfUrl, + activeSection: null, + sections: { + extraction: { + config: '', + mandatory: true, + sectionType: 'utils', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errors: [], + isLoading: false, + isValid: false + } as any, + collection: { + config: '', + mandatory: true, + sectionType: 'collection', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errors: [], + isLoading: false, + isValid: false + } as any, + traditionalpageone: { + header: 'submit.progressbar.describe.stepone', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpageone', + mandatory: true, + sectionType: 'submission-form', + collapsed: false, + enabled: true, + data: {}, + errors: [], + formId: '2_traditionalpageone', + isLoading: false, + isValid: false + } as any, + traditionalpagetwo: { + header: 'submit.progressbar.describe.steptwo', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpagetwo', + mandatory: false, + sectionType: 'submission-form', + collapsed: false, + enabled: false, + data: {}, + errors: [], + isLoading: false, + isValid: false + } as any, + license: { + header: 'submit.progressbar.license', + config: '', + mandatory: true, + sectionType: 'license', + visibility: { + main: null, + other: 'READONLY' + }, + collapsed: false, + enabled: true, + data: {}, + errors: [], + isLoading: false, + isValid: false + } as any + }, + isLoading: false, + savePending: false, + depositPending: false + } +}); + export const mockSectionsState = Object.assign({}, { extraction: { config: '', diff --git a/src/app/shared/uploader/uploader.component.ts b/src/app/shared/uploader/uploader.component.ts index 0e19c38b55..a0dd0e5bba 100644 --- a/src/app/shared/uploader/uploader.component.ts +++ b/src/app/shared/uploader/uploader.component.ts @@ -1,29 +1,16 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - Input, - Output, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, Output, ViewEncapsulation, } from '@angular/core'; import { of as observableOf } from 'rxjs'; import { FileUploader } from 'ng2-file-upload'; import { uniqueId } from 'lodash'; -import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; +import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; import { UploaderOptions } from './uploader-options.model'; import { hasValue, isNotEmpty, isUndefined } from '../empty.util'; import { UploaderService } from './uploader.service'; import { UploaderProperties } from './uploader-properties.model'; import { HttpXsrfTokenExtractor } from '@angular/common/http'; -import { - XSRF_REQUEST_HEADER, - XSRF_RESPONSE_HEADER, - XSRF_COOKIE -} from '../../core/xsrf/xsrf.interceptor'; +import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../core/xsrf/xsrf.interceptor'; import { CookieService } from '../../core/services/cookie.service'; @Component({ @@ -146,12 +133,6 @@ export class UploaderComponent { this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }]; this.onBeforeUpload(); this.isOverDocumentDropZone = observableOf(false); - - // Move page target to the uploader - const config: ScrollToConfigOptions = { - target: this.uploaderId - }; - this.scrollToService.scrollTo(config); }; if (hasValue(this.uploadProperties)) { this.uploader.onBuildItemForm = (item, form) => { diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index 7376b1e10b..33b5d4be12 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -1,35 +1,36 @@
-
- -
-
-
-
- - -
-
- - -
-
+
+
+ +
+
-
- - - - -
- +
+ + +
+
+ + +
+
+ +
+ + + + +
+
diff --git a/src/app/submission/form/submission-form.component.spec.ts b/src/app/submission/form/submission-form.component.spec.ts index d719472adf..dd8e6d0ea3 100644 --- a/src/app/submission/form/submission-form.component.spec.ts +++ b/src/app/submission/form/submission-form.component.spec.ts @@ -24,7 +24,7 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic import { createTestComponent } from '../../shared/testing/utils.test'; import { Item } from '../../core/shared/item.model'; import { TestScheduler } from 'rxjs/testing'; - +import { SectionsService } from '../sections/sections.service'; describe('SubmissionFormComponent Component', () => { @@ -55,6 +55,7 @@ describe('SubmissionFormComponent Component', () => { { provide: AuthService, useClass: AuthServiceStub }, { provide: HALEndpointService, useValue: new HALEndpointServiceStub('workspaceitems') }, { provide: SubmissionService, useValue: submissionServiceStub }, + { provide: SectionsService, useValue: { isSectionTypeAvailable: () => observableOf(true) } }, ChangeDetectorRef, SubmissionFormComponent ], @@ -115,7 +116,7 @@ describe('SubmissionFormComponent Component', () => { expect(compAsAny.submissionSections).toBeUndefined(); expect(compAsAny.subs).toEqual([]); expect(submissionServiceStub.startAutoSave).not.toHaveBeenCalled(); - expect(comp.loading).toBeObservable(cold('(a|)', {a: true})); + expect(comp.loading).toBeObservable(cold('(a|)', { a: true })); done(); }); @@ -140,7 +141,7 @@ describe('SubmissionFormComponent Component', () => { }); scheduler.flush(); - expect(comp.submissionSections).toBeObservable(cold('(a|)', {a: sectionsList})); + expect(comp.submissionSections).toBeObservable(cold('(a|)', { a: sectionsList })); expect(submissionServiceStub.dispatchInit).toHaveBeenCalledWith( collectionId, @@ -201,7 +202,7 @@ describe('SubmissionFormComponent Component', () => { submissionDefinition: { name: 'traditional' } - } as any); + } as any); fixture.detectChanges(); }); scheduler.flush(); diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index dc6f264d9b..8df0ab1658 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -15,6 +15,8 @@ import { SubmissionObjectEntry } from '../objects/submission-objects.reducer'; import { SectionDataObject } from '../sections/models/section-data.model'; import { SubmissionService } from '../submission.service'; import { Item } from '../../core/shared/item.model'; +import { SectionsType } from '../sections/sections-type'; +import { SectionsService } from '../sections/sections.service'; /** * This component represents the submission form. @@ -69,6 +71,11 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { */ public loading: Observable = observableOf(true); + /** + * Emits true when the submission config has bitstream uploading enabled in submission + */ + public uploadEnabled$: Observable; + /** * Observable of the list of submission's sections * @type {Observable} @@ -100,12 +107,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * @param {ChangeDetectorRef} changeDetectorRef * @param {HALEndpointService} halService * @param {SubmissionService} submissionService + * @param {SectionsService} sectionsService */ constructor( private authService: AuthService, private changeDetectorRef: ChangeDetectorRef, private halService: HALEndpointService, - private submissionService: SubmissionService) { + private submissionService: SubmissionService, + private sectionsService: SectionsService) { this.isActive = true; } @@ -129,6 +138,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { return observableOf([]); } })); + this.uploadEnabled$ = this.sectionsService.isSectionTypeAvailable(this.submissionId, SectionsType.Upload); // check if is submission loading this.loading = this.submissionService.getSubmissionObject(this.submissionId).pipe( diff --git a/src/app/submission/sections/sections.service.spec.ts b/src/app/submission/sections/sections.service.spec.ts index 6bdf26a082..0d63beeaea 100644 --- a/src/app/submission/sections/sections.service.spec.ts +++ b/src/app/submission/sections/sections.service.spec.ts @@ -1,4 +1,4 @@ -import { waitForAsync, TestBed } from '@angular/core/testing'; +import { TestBed, waitForAsync } from '@angular/core/testing'; import { cold, getTestScheduler } from 'jasmine-marbles'; import { of as observableOf } from 'rxjs'; @@ -17,7 +17,8 @@ import { SectionsService } from './sections.service'; import { mockSectionsData, mockSectionsErrors, - mockSubmissionState + mockSubmissionState, + mockSubmissionStateWithoutUpload } from '../../shared/mocks/submission.mock'; import { DisableSectionAction, @@ -27,11 +28,7 @@ import { SectionStatusChangeAction, UpdateSectionDataAction } from '../objects/submission-objects.actions'; -import { - FormAddError, - FormClearErrorsAction, - FormRemoveErrorAction -} from '../../shared/form/form.actions'; +import { FormAddError, FormClearErrorsAction, FormRemoveErrorAction } from '../../shared/form/form.actions'; import parseSectionErrors from '../utils/parseSectionErrors'; import { SubmissionScopeType } from '../../core/submission/submission-scope-type'; import { SubmissionSectionError } from '../objects/submission-objects.reducer'; @@ -52,6 +49,7 @@ describe('SectionsService test suite', () => { const sectionErrors: any = parseSectionErrors(mockSectionsErrors); const sectionData: any = mockSectionsData; const submissionState: any = Object.assign({}, mockSubmissionState[submissionId]); + const submissionStateWithoutUpload: any = Object.assign({}, mockSubmissionStateWithoutUpload[submissionId]); const sectionState: any = Object.assign({}, mockSubmissionState['826'].sections[sectionId]); const store: any = jasmine.createSpyObj('store', { @@ -314,6 +312,28 @@ describe('SectionsService test suite', () => { }); }); + describe('isSectionTypeAvailable', () => { + it('should return an observable of true when section is available', () => { + store.select.and.returnValue(observableOf(submissionState)); + + const expected = cold('(b|)', { + b: true + }); + + expect(service.isSectionTypeAvailable(submissionId, SectionsType.Upload)).toBeObservable(expected); + }); + + it('should return an observable of false when section is not available', () => { + store.select.and.returnValue(observableOf(submissionStateWithoutUpload)); + + const expected = cold('(b|)', { + b: false + }); + + expect(service.isSectionAvailable(submissionId, SectionsType.Upload)).toBeObservable(expected); + }); + }); + describe('addSection', () => { it('should dispatch a new EnableSectionAction a move target to new section', () => { diff --git a/src/app/submission/sections/sections.service.ts b/src/app/submission/sections/sections.service.ts index b2bc0e4f98..d8d1491cb7 100644 --- a/src/app/submission/sections/sections.service.ts +++ b/src/app/submission/sections/sections.service.ts @@ -5,7 +5,7 @@ import { distinctUntilChanged, filter, map, take } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; -import { isEqual } from 'lodash'; +import { findKey, isEqual } from 'lodash'; import { SubmissionState } from '../submission.reducers'; import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; @@ -291,14 +291,14 @@ export class SectionsService { } /** - * Check if a given section is a read only available + * Check if a given section id is present in the list of sections * * @param submissionId * The submission id * @param sectionId * The section id * @return Observable - * Emits true whenever a given section should be available + * Emits true whenever a given section id should be available */ public isSectionAvailable(submissionId: string, sectionId: string): Observable { return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( @@ -309,6 +309,25 @@ export class SectionsService { distinctUntilChanged()); } + /** + * Check if a given section type is present in the list of sections + * + * @param submissionId + * The submission id + * @param sectionType + * The section type + * @return Observable + * Emits true whenever a given section type should be available + */ + public isSectionTypeAvailable(submissionId: string, sectionType: SectionsType): Observable { + return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( + filter((submissionState: SubmissionObjectEntry) => isNotUndefined(submissionState)), + map((submissionState: SubmissionObjectEntry) => { + return isNotUndefined(submissionState.sections) && isNotUndefined(findKey(submissionState.sections, {sectionType: sectionType})); + }), + distinctUntilChanged()); + } + /** * Dispatch a new [EnableSectionAction] to add a new section and move page target to it * From 73319533d1b8ced458e754b74714669abe4d44ba Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 6 Apr 2021 16:36:01 +0200 Subject: [PATCH 19/46] Implement feedback --- .../epeople-registry.component.spec.ts | 6 +-- .../epeople-registry.component.ts | 6 +-- .../eperson-form.component.spec.ts | 6 +-- .../eperson-form/eperson-form.component.ts | 2 +- .../members-list.component.spec.ts | 7 +-- .../members-list/members-list.component.ts | 2 +- .../subgroups-list.component.spec.ts | 7 +-- .../subgroup-list/subgroups-list.component.ts | 2 +- .../groups-registry.component.spec.ts | 7 +-- .../groups-registry.component.ts | 2 +- src/app/core/pagination/pagination.service.ts | 10 ++-- .../pagination/pagination.component.spec.ts | 3 +- .../eperson-group-list.component.ts | 4 +- .../search-form/search-form.component.ts | 3 +- .../search-facet-option.component.spec.ts | 4 +- ...earch-facet-range-option.component.spec.ts | 2 +- ...ch-facet-selected-option.component.spec.ts | 4 +- .../shared/testing/pagination-service.stub.ts | 2 +- ...ion-import-external-searchbar.component.ts | 5 +- .../submission-import-external.component.ts | 51 ++++++++++++------- 20 files changed, 69 insertions(+), 66 deletions(-) diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index 29baba4409..bcf7e8f1d9 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -25,10 +25,8 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser import { RouterStub } from '../../shared/testing/router.stub'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { RequestService } from '../../core/data/request.service'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { PaginationService } from '../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('EPeopleRegistryComponent', () => { let component: EPeopleRegistryComponent; diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts index 3ec8efa940..b99304d037 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts @@ -20,7 +20,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../core/data/request.service'; import { PageInfo } from '../../core/shared/page-info.model'; import { NoContent } from '../../core/shared/NoContent.model'; -import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-epeople-registry', @@ -157,14 +157,14 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { const query: string = data.query; const scope: string = data.scope; if (query != null && this.currentSearchQuery !== query) { - this.router.navigate(this.epersonService.getEPeoplePageRouterLink(), { + this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], { queryParamsHandling: 'merge' }); this.currentSearchQuery = query; this.paginationService.resetPage(this.config.id); } if (scope != null && this.currentSearchScope !== scope) { - this.router.navigate(this.epersonService.getEPeoplePageRouterLink(), { + this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], { queryParamsHandling: 'merge' }); this.currentSearchScope = scope; diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 66f633ceef..832f4f6ce5 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -26,10 +26,8 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati import { GroupDataService } from '../../../core/eperson/group-data.service'; import { createPaginatedList } from '../../../shared/testing/utils.test'; import { RequestService } from '../../../core/data/request.service'; -import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index faf3b34454..11c117ef55 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -31,7 +31,7 @@ import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/c import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../../core/data/request.service'; import { NoContent } from '../../../core/shared/NoContent.model'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-eperson-form', diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts index 186cfb713b..0b19b17100 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -26,11 +26,8 @@ import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { RouterMock } from '../../../../shared/mocks/router.mock'; -import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../../../../core/data/request.models'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; describe('MembersListComponent', () => { let component: MembersListComponent; diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index b4032520e0..54d144da51 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -23,8 +23,8 @@ import { } from '../../../../core/shared/operators'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; import {EpersonDtoModel} from '../../../../core/eperson/models/eperson-dto.model'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; /** * Keys to keep track of specific subscriptions diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts index af54a60da6..bee5126e09 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts @@ -35,11 +35,8 @@ import { getMockTranslateService } from '../../../../shared/mocks/translate.serv import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { map } from 'rxjs/operators'; -import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../../../../core/data/request.models'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; describe('SubgroupsListComponent', () => { let component: SubgroupsListComponent; diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts index 1aee7048f7..6d8285f10b 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts @@ -16,7 +16,7 @@ import { import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../../../core/shared/NoContent.model'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; /** * Keys to keep track of specific subscriptions diff --git a/src/app/access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts index b0b954c4e4..10064800e1 100644 --- a/src/app/access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts @@ -28,11 +28,8 @@ import { TranslateLoaderMock } from '../../shared/testing/translate-loader.mock' import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { RouterMock } from '../../shared/mocks/router.mock'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../../core/data/request.models'; -import { PaginationService } from '../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; describe('GroupRegistryComponent', () => { let component: GroupsRegistryComponent; diff --git a/src/app/access-control/group-registry/groups-registry.component.ts b/src/app/access-control/group-registry/groups-registry.component.ts index f7834949e0..b28ac043d9 100644 --- a/src/app/access-control/group-registry/groups-registry.component.ts +++ b/src/app/access-control/group-registry/groups-registry.component.ts @@ -34,7 +34,7 @@ import { hasValue } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../core/shared/NoContent.model'; -import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-groups-registry', diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index b6ae304491..dae6991834 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -18,10 +18,10 @@ import { isNumeric } from 'rxjs/internal-compatibility'; * Service to manage the pagination of different components * The pagination information will be stored in the route based on a paginationID. * The following params are used for the different kind of pagination information: - * - For the page: p.{paginationID} - * - For the page size: rpp.{paginationID} - * - For the sort direction: sd.{paginationID} - * - For the sort field: sf.{paginationID} + * - For the page: {paginationID}.p + * - For the page size: {paginationID}.rpp + * - For the sort direction: {paginationID}.sd + * - For the sort field: {paginationID}.sf */ export class PaginationService { @@ -145,7 +145,6 @@ export class PaginationService { if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams) || isNotEmpty(this.clearParams)) { const queryParams = Object.assign({}, this.clearParams, currentParametersWithIdName, parametersWithIdName, extraParams); - console.log(queryParams, this.clearParams); if (retainScrollPosition) { this.router.navigate(url, { queryParams: queryParams, @@ -159,7 +158,6 @@ export class PaginationService { }); } this.clearParams = {}; - console.log('postcear', this.clearParams); } }); } diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 3c50f66158..cf2d1c13fd 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -127,7 +127,8 @@ describe('Pagination component', () => { // waitForAsync beforeEach beforeEach(waitForAsync(() => { activatedRouteStub = new MockActivatedRoute(); - routerStub = new RouterMock(); hostWindowServiceStub = new HostWindowServiceMock(_initialState.width); + routerStub = new RouterMock(); + hostWindowServiceStub = new HostWindowServiceMock(_initialState.width); currentPagination = new BehaviorSubject(pagination); currentSort = new BehaviorSubject(sort); diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index 8605033b2e..11b5b5e7b3 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -186,7 +186,9 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { this.subs.push(search$.pipe(getFirstCompletedRemoteData()) .subscribe((list: RemoteData>) => { - this.list$.next(list); + if (hasValue(this.list$)) { + this.list$.next(list); + } }) ); }); diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index fd1abde057..2791aee378 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -95,7 +95,8 @@ export class SearchFormComponent { */ updateSearch(data: any) { const queryParams = Object.assign({}, data); - queryParams[`page.${this.searchConfig.paginationID}`] = 1; + const pageParam = this.paginationService.getPageParam(this.searchConfig.paginationID); + queryParams[pageParam] = 1; this.router.navigate(this.getSearchLinkParts(), { queryParams: queryParams, diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts index a266e4e425..7299a39c32 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts @@ -140,7 +140,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'], - ['page.page-id']: 1 + ['page-id.page']: 1 }); }); }); @@ -155,7 +155,7 @@ describe('SearchFacetOptionComponent', () => { (comp as any).updateAddParams(selectedValues); expect(comp.addQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`], - ['page.page-id']: 1 + ['page-id.page']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts index 2c93b0186b..9ed8dee0ea 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts @@ -126,7 +126,7 @@ describe('SearchFacetRangeOptionComponent', () => { expect(comp.changeQueryParams).toEqual({ [mockFilterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: ['50'], [mockFilterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: ['60'], - ['page.page-id']: 1 + ['page-id.page']: 1 }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts index cbb6672916..8f422b41bf 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.spec.ts @@ -165,7 +165,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedValues); expect(comp.removeQueryParams).toEqual({ [mockFilterConfig.paramName]: [value1], - ['page.page-id']: 1 + ['page-id.page']: 1 }); }); }); @@ -181,7 +181,7 @@ describe('SearchFacetSelectedOptionComponent', () => { (comp as any).updateRemoveParams(selectedAuthorityValues); expect(comp.removeQueryParams).toEqual({ [mockAuthorityFilterConfig.paramName]: [`${value1},${operator}`], - ['page.page-id']: 1 + ['page-id.page']: 1 }); }); }); diff --git a/src/app/shared/testing/pagination-service.stub.ts b/src/app/shared/testing/pagination-service.stub.ts index cd24808161..985a5bfc4a 100644 --- a/src/app/shared/testing/pagination-service.stub.ts +++ b/src/app/shared/testing/pagination-service.stub.ts @@ -20,6 +20,6 @@ export class PaginationServiceStub { updateRouteWithUrl = jasmine.createSpy('updateRouteWithUrl'); clearPagination = jasmine.createSpy('clearPagination'); getRouteParameterValue = jasmine.createSpy('getRouteParameterValue').and.returnValue(observableOf('')); - getPageParam = jasmine.createSpy('getPageParam').and.returnValue(`page.${this.pagination.id}`); + getPageParam = jasmine.createSpy('getPageParam').and.returnValue(`${this.pagination.id}.page`); } diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts index 5e26df829c..30f5184d57 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts @@ -18,7 +18,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { PageInfo } from '../../../core/shared/page-info.model'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { FindListOptions } from '../../../core/data/request.models'; -import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { HostWindowService } from '../../../shared/host-window.service'; import { hasValue } from '../../../shared/empty.util'; @@ -124,7 +124,7 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); return observableOf(paginatedListRD); }), - getFirstSucceededRemoteDataPayload() + getFirstSucceededRemoteDataPayload(), ).subscribe((externalSource: PaginatedList) => { externalSource.page.forEach((element) => { this.sourceList.push({ id: element.id, name: element.name }); @@ -166,6 +166,7 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); return observableOf(paginatedListRD); }), + getFirstSucceededRemoteData(), tap(() => this.sourceListLoading = false) ).subscribe((externalSource: RemoteData>) => { externalSource.payload.page.forEach((element) => { diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index fe568533e4..e8370a9be0 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; -import { filter, mergeMap, switchMap, take } from 'rxjs/operators'; +import { filter, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceService } from '../../core/data/external-source.service'; @@ -45,7 +45,10 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { */ public isLoading$: BehaviorSubject = new BehaviorSubject(false); - public reload$: BehaviorSubject<{query: string, source: string}> = new BehaviorSubject<{query: string; source: string}>({query: '', source: ''}); + public reload$: BehaviorSubject<{ query: string, source: string }> = new BehaviorSubject<{ query: string; source: string }>({ + query: '', + source: '' + }); /** * Configuration to use for the import buttons */ @@ -83,6 +86,8 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { */ protected subs: Subscription[] = []; + private retrieveExternalSourcesSub: Subscription; + /** * Initialize the component variables. * @param {SearchConfigurationService} searchConfigService @@ -108,7 +113,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.listId = 'list-submission-external-sources'; this.context = Context.EntitySearchModalWithNameVariants; this.repeatable = false; - this.routeData = { sourceId: '', query: '' }; + this.routeData = {sourceId: '', query: ''}; this.importConfig = { buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label }; @@ -133,10 +138,13 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.router.navigate( [], { - queryParams: { source: event.sourceId, query: event.query }, + queryParams: {source: event.sourceId, query: event.query}, replaceUrl: true } - ).then(() => this.reload$.next({source: event.sourceId, query: event.query})); + ).then(() => { + this.reload$.next({source: event.sourceId, query: event.query}); + this.retrieveExternalSources(); + }); } /** @@ -158,6 +166,10 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { this.subs .filter((sub) => hasValue(sub)) .forEach((sub) => sub.unsubscribe()); + if (hasValue(this.retrieveExternalSourcesSub)) { + this.retrieveExternalSourcesSub.unsubscribe(); + } + } /** @@ -167,22 +179,23 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * @param query The query string to search */ private retrieveExternalSources(): void { - this.reload$.pipe( - switchMap( - (sourceQueryObject: { source: string, query: string }) => { + if (hasValue(this.retrieveExternalSourcesSub)) { + this.retrieveExternalSourcesSub.unsubscribe(); + } + this.retrieveExternalSourcesSub = this.reload$.pipe( + filter((sourceQueryObject: { source: string, query: string }) => isNotEmpty(sourceQueryObject.source) && isNotEmpty(sourceQueryObject.query)), + switchMap((sourceQueryObject: { source: string, query: string }) => { const source = sourceQueryObject.source; const query = sourceQueryObject.query; - if (isNotEmpty(source) && isNotEmpty(query)) { - this.routeData.sourceId = source; - this.routeData.query = query; - this.isLoading$.next(true); - return this.searchConfigService.paginatedSearchOptions.pipe( - filter((searchOptions) => searchOptions.query === query), - mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( - getFinishedRemoteData(), - )), - ); - } + this.routeData.sourceId = source; + this.routeData.query = query; + return this.searchConfigService.paginatedSearchOptions.pipe( + tap((v) => this.isLoading$.next(true)), + filter((searchOptions) => searchOptions.query === query), + mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( + getFinishedRemoteData(), + )), + ); } ), ).subscribe((rdData) => { From 868b7ad37bbcb49183c992194556b27715444c35 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 25 Jan 2021 15:46:21 +0100 Subject: [PATCH 20/46] 76150: Add a bitstream download page --- docs/Configuration.md | 66 -------- .../bitstream-page-routing.module.ts | 8 + .../full-file-section.component.html | 4 +- .../file-section/file-section.component.html | 2 +- .../data/feature-authorization/feature-id.ts | 1 + .../core/metadata/metadata.service.spec.ts | 8 +- src/app/core/metadata/metadata.service.ts | 10 +- .../detail/process-detail.component.html | 2 +- .../bitstream-download-page.component.html | 3 + .../bitstream-download-page.component.spec.ts | 158 ++++++++++++++++++ .../bitstream-download-page.component.ts | 83 +++++++++ .../file-download-link.component.html | 4 +- .../file-download-link.component.spec.ts | 35 ++-- .../file-download-link.component.ts | 26 ++- src/app/shared/shared.module.ts | 10 ++ src/assets/i18n/en.json5 | 4 + 16 files changed, 308 insertions(+), 116 deletions(-) create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.html create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.ts diff --git a/docs/Configuration.md b/docs/Configuration.md index f918622568..f4fff1166c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -64,69 +64,3 @@ In order to start using one of these services, select it from the [Angulartics P The Google Analytics script was added in [`main.browser.ts`](https://github.com/DSpace/dspace-angular/blob/ff04760f4af91ac3e7add5e7424a46cb2439e874/src/main.browser.ts#L33) instead of the `` tag in `index.html` to ensure events get sent when the page is shown in a client's browser, and not when it's rendered on the universal server. Likely you'll want to do the same when adding a new service. -## SEO when hosting REST Api and UI on different servers - -Indexers such as Google Scholar require that files are hosted on the same domain as the page that links them. In DSpace 7, Bitstreams are served from the REST server. So if you use different servers for the REST api and the UI you'll want to ensure that Bitstream downloads are proxied through the UI server. - -In order to achieve this we'll need to do two things: -- **Proxy the Bitstream downloads through the UI server.** You'll need to put a webserver such as httpd or nginx in front of the UI server in order to achieve this. [Below](#apache-http-server-config) you'll find a section explaining how to do it in httpd. -- **Update the URLs for Bitstream downloads to match the UI server.** This can be done using a setting in the UI environment file. - -### UI config -If you set the property `rewriteDownloadUrls` to `true` in your `environment.prod.ts` file, the [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) of any download URL will be replaced by the origin of the UI. This will also happen for the `citation_pdf_url` `` tag on Item pages. - -The app will determine the UI origin currently in use, so the external UI URL doesn't need to be configured anywhere and rewrites will still work if you host the UI from multiple domains. - -### Apache HTTP Server config - -#### Basics -In order to be able to host bitstreams from the UI Server you'll need to enable mod_proxy and add the following to the httpd config of your UI server: - -``` -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "http://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "http://rest.api/server/api/core/bitstreams/$1/content" -``` - -Replace http://rest.api in with the correct origin for your REST server. - -The `ProxyPassMatch` line forwards all requests matching the regular expression for a bitstream download URL to the corresponding path on the REST server - -The `ProxyPassReverse` ensures that if the REST server were to return redirect response, httpd would also swap out its hostname for the hostname of the UI before forwarding the response to the client. - -#### Using HTTPS -If your REST server uses https, you'll need to enable mod_ssl and ensure `SSLProxyEngine on` is part of your UI server's httpd config as well - -If the UI hostname doesn't match the CN in the SSL certificate of the REST server (which is likely if they're on different domains), you'll also need to add the following lines - -``` -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -``` -These are two names for [the same directive](https://httpd.apache.org/docs/trunk/mod/mod_ssl.html#sslproxycheckpeername) that have been used for various versions of httpd, old versions need the former, then some in-between versions need both, and newer versions only need the latter. Keeping them both doesn't harm anything. - -So the entire config becomes: - -``` -SSLProxyEngine on -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -``` - -If you don't want httpd to verify the certificate of the REST server, you can also turn all checks off with the following config: - -``` -SSLProxyEngine on -SSLProxyVerify none -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -SSLProxyCheckPeerExpire off -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -``` - - - - - diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index 14d688064c..bbbd65f279 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; +import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component'; const EDIT_BITSTREAM_PATH = ':id/edit'; @@ -12,6 +13,13 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; @NgModule({ imports: [ RouterModule.forChild([ + { + path:':id/download', + component: BitstreamDownloadPageComponent, + resolve: { + bitstream: BitstreamPageResolver + }, + }, { path: EDIT_BITSTREAM_PATH, component: EditBitstreamPageComponent, diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html index 00218b66d1..a054d58bf5 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html @@ -34,7 +34,7 @@
- + {{"item.page.filesection.download" | translate}}
@@ -76,7 +76,7 @@
- + {{"item.page.filesection.download" | translate}}
diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.html b/src/app/+item-page/simple/field-components/file-section/file-section.component.html index 1fdee6dc4d..0fa5daa012 100644 --- a/src/app/+item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.html @@ -1,7 +1,7 @@
- + {{file?.name}} ({{(file?.sizeBytes) | dsFileSize }}) diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index e3473a895e..dbdd794665 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -12,4 +12,5 @@ export enum FeatureID { CanManageGroups = 'canManageGroups', IsCollectionAdmin = 'isCollectionAdmin', IsCommunityAdmin = 'isCommunityAdmin', + CanDownload = 'canDownload', } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 25165aba79..043bc3cc05 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -181,11 +181,7 @@ describe('MetadataService', () => { Meta, Title, // tslint:disable-next-line:no-empty - { provide: ItemDataService, useValue: { findById: () => { } } }, - { - provide: HardRedirectService, - useValue: { rewriteDownloadURL: (a) => a, getRequestOrigin: () => environment.ui.baseUrl } - }, + { provide: ItemDataService, useValue: { findById: () => {} } }, BrowseService, MetadataService ], @@ -225,7 +221,7 @@ describe('MetadataService', () => { tick(); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); - expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual(new URLCombiner(environment.ui.baseUrl, router.url).toString()); + expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([environment.ui.baseUrl, router.url].join('')); expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); })); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index dac0a787fb..eed4228683 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; @@ -20,8 +20,7 @@ import { Bitstream } from '../shared/bitstream.model'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; -import { HardRedirectService } from '../services/hard-redirect.service'; -import { URLCombiner } from '../url-combiner/url-combiner'; +import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; @Injectable() @@ -262,7 +261,7 @@ export class MetadataService { */ private setCitationAbstractUrlTag(): void { if (this.currentObject.value instanceof Item) { - const value = new URLCombiner(this.redirectService.getRequestOrigin(), this.router.url).toString(); + const value = [environment.ui.baseUrl, this.router.url].join(''); this.addMetaTag('citation_abstract_html_url', value); } } @@ -287,8 +286,7 @@ export class MetadataService { getFirstSucceededRemoteDataPayload() ).subscribe((format: BitstreamFormat) => { if (format.mimetype === 'application/pdf') { - const rewrittenURL= this.redirectService.rewriteDownloadURL(bitstream._links.content.href); - this.addMetaTag('citation_pdf_url', rewrittenURL); + this.addMetaTag('citation_pdf_url', bitstream._links.content.href); } }); } diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index d59f93254e..ef099d4f9f 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -15,7 +15,7 @@
- + {{getFileName(file)}} ({{(file?.sizeBytes) | dsFileSize }}) diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.html b/src/app/shared/bitstream-download-page/bitstream-download-page.component.html new file mode 100644 index 0000000000..183449fc5e --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.html @@ -0,0 +1,3 @@ +
+

{{'bitstream.download.page' | translate:{bitstream: (bitstream$ | async)?.name} }}

+
diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts new file mode 100644 index 0000000000..ca5a079faa --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -0,0 +1,158 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AuthService } from '../../core/auth/auth.service'; +import { FileService } from '../../core/shared/file.service'; +import { of as observableOf } from 'rxjs'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page.component'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { ActivatedRoute, Router } from '@angular/router'; +import { getForbiddenRoute } from '../../app-routing-paths'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; + +describe('BitstreamDownloadPageComponent', () => { + let component: BitstreamDownloadPageComponent; + let fixture: ComponentFixture; + + let authService: AuthService; + let fileService: FileService; + let authorizationService: AuthorizationDataService; + let hardRedirectService: HardRedirectService; + let activatedRoute; + let router; + + let bitstream: Bitstream; + + function init() { + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + setRedirectUrl: {} + }); + authorizationService = jasmine.createSpyObj('authorizationSerivice', { + isAuthorized: observableOf(true) + }); + + fileService = jasmine.createSpyObj('fileService', { + downloadFile: observableOf('content-url-with-headers') + }); + + hardRedirectService = jasmine.createSpyObj('fileService', { + redirect: {} + }); + bitstream = Object.assign(new Bitstream(), { + uuid: 'bitstreamUuid', + _links: { + content: {href: 'bitstream-content-link'}, + self: {href: 'bitstream-self-link'}, + } + }); + + activatedRoute = { + data: observableOf({ + bitstream: createSuccessfulRemoteDataObject( + bitstream + ) + }) + }; + + router = jasmine.createSpyObj('router', ['navigateByUrl']); + } + + function initTestbed() { + TestBed.configureTestingModule({ + imports: [CommonModule, TranslateModule.forRoot()], + declarations: [BitstreamDownloadPageComponent], + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: Router, useValue: router}, + {provide: AuthorizationDataService, useValue: authorizationService}, + {provide: AuthService, useValue: authService}, + {provide: FileService, useValue: fileService}, + {provide: HardRedirectService, useValue: hardRedirectService}, + ] + }) + .compileComponents(); + } + + describe('init', () => { + beforeEach(async(() => { + init(); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should init the comp', () => { + expect(component).toBeTruthy(); + }); + }); + + describe('bitstream retrieval', () => { + describe('when the user is authorized and not logged in', () => { + beforeEach(async(() => { + init(); + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should redirect to the content link', () => { + expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link'); + }); + }); + describe('when the user is authorized and logged in', () => { + beforeEach(async(() => { + init(); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should redirect to an updated content link', () => { + expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers'); + }); + }); + describe('when the user is not authorized and logged in', () => { + beforeEach(async(() => { + init(); + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should navigate to the forbidden route', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), {skipLocationChange: true}); + }); + }); + describe('when the user is not authorized and not logged in', () => { + beforeEach(async(() => { + init(); + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should navigate to the login page', () => { + expect(authService.setRedirectUrl).toHaveBeenCalled(); + expect(router.navigateByUrl).toHaveBeenCalledWith('login'); + }); + }); + }); +}); diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts new file mode 100644 index 0000000000..ccaec76751 --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -0,0 +1,83 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { hasValue, isNotEmpty } from '../empty.util'; +import { getRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { AuthService } from '../../core/auth/auth.service'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { FileService } from '../../core/shared/file.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { getForbiddenRoute } from '../../app-routing-paths'; +import { RemoteData } from '../../core/data/remote-data'; +import { tap } from 'rxjs/internal/operators/tap'; + +@Component({ + selector: 'ds-bitstream-download-page', + templateUrl: './bitstream-download-page.component.html' +}) +/** + * Page component for downloading a bitstream + */ +export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { + + bitstream$: Observable; + bitstreamRD$: Observable>; + + subs: Subscription[] = []; + + constructor( + private route: ActivatedRoute, + protected router: Router, + private authorizationService: AuthorizationDataService, + private auth: AuthService, + private fileService: FileService, + private hardRedirectService: HardRedirectService, + ) { + + } + + ngOnInit(): void { + + this.bitstreamRD$ = this.route.data.pipe( + map((data) => data.bitstream)); + + this.bitstream$ = this.bitstreamRD$.pipe( + tap((v) => console.log('dfdf', v)), + redirectOn4xx(this.router, this.auth), + getRemoteDataPayload() + ); + + this.subs.push(this.bitstream$.subscribe((bitstream) => { + const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); + const isLoggedIn$ = this.auth.isAuthenticated(); + + this.subs.push(observableCombineLatest(isAuthorized$, isLoggedIn$) + .subscribe(([isAuthorized, isLoggedIn]) => { + if (isAuthorized && isLoggedIn) { + const fileLink$ = this.fileService.downloadFile(bitstream._links.content.href); + this.subs.push(fileLink$.subscribe((fileLink) => { + this.hardRedirectService.redirect(fileLink); + })); + } else if (isAuthorized && !isLoggedIn) { + this.hardRedirectService.redirect(bitstream._links.content.href); + } else if (!isAuthorized && isLoggedIn) { + this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); + } else if (!isAuthorized && !isLoggedIn) { + this.auth.setRedirectUrl(this.router.url); + this.router.navigateByUrl('login'); + } + })); + })); + + } + + ngOnDestroy(): void { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } +} diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html index 06624c8b40..f1843da5c6 100644 --- a/src/app/shared/file-download-link/file-download-link.component.html +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -1,5 +1,5 @@ - - + + diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 4f3c17402f..53f39b104e 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -3,7 +3,10 @@ import { FileDownloadLinkComponent } from './file-download-link.component'; import { AuthService } from '../../core/auth/auth.service'; import { FileService } from '../../core/shared/file.service'; import { of as observableOf } from 'rxjs'; -import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { By } from '@angular/platform-browser'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; +import { getBitstreamModuleRoute } from '../../app-routing-paths'; describe('FileDownloadLinkComponent', () => { let component: FileDownloadLinkComponent; @@ -11,14 +14,16 @@ describe('FileDownloadLinkComponent', () => { let authService: AuthService; let fileService: FileService; - let href: string; + let bitstream: Bitstream; function init() { authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true) }); fileService = jasmine.createSpyObj('fileService', ['downloadFile']); - href = 'test-download-file-link'; + bitstream = Object.assign(new Bitstream(), { + uuid: 'bitstreamUuid', + }); } beforeEach(waitForAsync(() => { @@ -28,7 +33,6 @@ describe('FileDownloadLinkComponent', () => { providers: [ { provide: AuthService, useValue: authService }, { provide: FileService, useValue: fileService }, - { provide: HardRedirectService, useValue: { rewriteDownloadURL: (a) => a } }, ] }) .compileComponents(); @@ -37,23 +41,22 @@ describe('FileDownloadLinkComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(FileDownloadLinkComponent); component = fixture.componentInstance; - component.href = href; + component.bitstream = bitstream; fixture.detectChanges(); }); - describe('downloadFile', () => { - let result; + describe('init', () => { - beforeEach(() => { - result = component.downloadFile(); - }); + describe('getBitstreamPath', () => { + it('should set the bitstreamPath based on the input bitstream', () => { + expect(component.bitstreamPath).toEqual(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); + }); + }) - it('should call fileService.downloadFile with the provided href', () => { - expect(fileService.downloadFile).toHaveBeenCalledWith(href); - }); + it('should init the component', () => { + const link = fixture.debugElement.query(By.css('a')).nativeElement; + expect(link.href).toContain(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); + }) - it('should return false', () => { - expect(result).toEqual(false); - }); }); }); diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index f9861cd997..8a1fbd6e0e 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -2,7 +2,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { FileService } from '../../core/shared/file.service'; import { Observable } from 'rxjs'; import { AuthService } from '../../core/auth/auth.service'; -import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { getBitstreamModuleRoute } from '../../app-routing-paths'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; @Component({ selector: 'ds-file-download-link', @@ -15,15 +17,12 @@ import { HardRedirectService } from '../../core/services/hard-redirect.service'; * ensuring the user is authorized to download the file. */ export class FileDownloadLinkComponent implements OnInit { - /** - * Href to link to - */ - @Input() href: string; /** - * Optional file name for the download + * Optional bitstream instead of href and file name */ - @Input() download: string; + @Input() bitstream: Bitstream; + bitstreamPath: string; /** * Whether or not the current user is authenticated @@ -32,20 +31,15 @@ export class FileDownloadLinkComponent implements OnInit { constructor(private fileService: FileService, private authService: AuthService, - private redirectService: HardRedirectService) { + ) { } ngOnInit() { this.isAuthenticated$ = this.authService.isAuthenticated(); - this.href = this.redirectService.rewriteDownloadURL(this.href); + this.bitstreamPath = this.getBitstreamPath(); } - /** - * Start a download of the file - * Return false to ensure the original href is displayed when the user hovers over the link - */ - downloadFile(): boolean { - this.fileService.downloadFile(this.href); - return false; + getBitstreamPath() { + return new URLCombiner(getBitstreamModuleRoute(), this.bitstream.uuid, 'download').toString(); } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index e9f9d69918..4de0f2901e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -223,6 +223,7 @@ import { SearchObjects } from './search/search-objects.model'; import { SearchResult } from './search/search-result.model'; import { FacetConfigResponse } from './search/facet-config-response.model'; import { FacetValues } from './search/facet-values.model'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; import { GenericItemPageFieldComponent } from '../+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component'; import { RelatedItemsComponent } from '../+item-page/simple/related-items/related-items-component'; @@ -432,6 +433,7 @@ const COMPONENTS = [ EpersonSearchBoxComponent, GroupSearchBoxComponent, FileDownloadLinkComponent, + BitstreamDownloadPageComponent, CollectionDropdownComponent, ExportMetadataSelectorComponent, ConfirmationModalComponent, @@ -510,6 +512,14 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, ClaimedTaskActionsEditMetadataComponent, + CollectionDropdownComponent, + FileDownloadLinkComponent, + BitstreamDownloadPageComponent, + CurationFormComponent, + ExportMetadataSelectorComponent, + ConfirmationModalComponent, + VocabularyTreeviewComponent, + SidebarSearchListElementComponent, PublicationSidebarSearchListElementComponent, CollectionSidebarSearchListElementComponent, CommunitySidebarSearchListElementComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2924a3a47e..1b9e252922 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -526,6 +526,10 @@ + "bitstream.download.page": "Now downloading {{bitstream}}..." , + + + "bitstream.edit.bitstream": "Bitstream: ", "bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".", From 3137f3e6c11001b39acca9d0531718ca6b7fd257 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 11:05:13 +0100 Subject: [PATCH 21/46] 76150: Implement feedback --- src/app/core/shared/file.service.ts | 14 ++-- .../bitstream-download-page.component.spec.ts | 2 +- .../bitstream-download-page.component.ts | 71 ++++++++++--------- .../item-detail-preview.component.ts | 2 +- .../section-upload-file.component.spec.ts | 4 +- .../file/section-upload-file.component.ts | 2 +- 6 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/app/core/shared/file.service.ts b/src/app/core/shared/file.service.ts index fcbd86161a..98c468e9a3 100644 --- a/src/app/core/shared/file.service.ts +++ b/src/app/core/shared/file.service.ts @@ -1,10 +1,11 @@ import { Inject, Injectable } from '@angular/core'; import { RawRestResponse } from '../dspace-rest/raw-rest-response.model'; import { AuthService } from '../auth/auth.service'; -import { take } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { URLCombiner } from '../url-combiner/url-combiner'; import { hasValue } from '../../shared/empty.util'; +import { Observable } from 'rxjs/internal/Observable'; /** * Provides utility methods to save files on the client-side. @@ -17,17 +18,16 @@ export class FileService { ) { } /** - * Combines an URL with a short-lived token and sets the current URL to the newly created one + * Combines an URL with a short-lived token and sets the current URL to the newly created one and returns it * * @param url * file url */ - downloadFile(url: string) { - this.authService.getShortlivedToken().pipe(take(1)).subscribe((token) => { - this._window.nativeWindow.location.href = hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url; - }); + retrieveFileDownloadLink(url: string): Observable { + return this.authService.getShortlivedToken().pipe(take(1), map((token) => + hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url + )); } - /** * Derives file name from the http response * by looking inside content-disposition diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts index ca5a079faa..2803a7f789 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -35,7 +35,7 @@ describe('BitstreamDownloadPageComponent', () => { }); fileService = jasmine.createSpyObj('fileService', { - downloadFile: observableOf('content-url-with-headers') + retrieveFileDownloadLink: observableOf('content-url-with-headers') }); hardRedirectService = jasmine.createSpyObj('fileService', { diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts index ccaec76751..5b82928a64 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -1,19 +1,17 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { map } from 'rxjs/operators'; +import { Component, OnInit } from '@angular/core'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs/internal/Subscription'; import { hasValue, isNotEmpty } from '../empty.util'; import { getRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators'; import { Bitstream } from '../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { AuthService } from '../../core/auth/auth.service'; -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { FileService } from '../../core/shared/file.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { getForbiddenRoute } from '../../app-routing-paths'; import { RemoteData } from '../../core/data/remote-data'; -import { tap } from 'rxjs/internal/operators/tap'; @Component({ selector: 'ds-bitstream-download-page', @@ -22,12 +20,11 @@ import { tap } from 'rxjs/internal/operators/tap'; /** * Page component for downloading a bitstream */ -export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { +export class BitstreamDownloadPageComponent implements OnInit { bitstream$: Observable; bitstreamRD$: Observable>; - subs: Subscription[] = []; constructor( private route: ActivatedRoute, @@ -46,38 +43,42 @@ export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { map((data) => data.bitstream)); this.bitstream$ = this.bitstreamRD$.pipe( - tap((v) => console.log('dfdf', v)), redirectOn4xx(this.router, this.auth), getRemoteDataPayload() ); - this.subs.push(this.bitstream$.subscribe((bitstream) => { - const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); - const isLoggedIn$ = this.auth.isAuthenticated(); - - this.subs.push(observableCombineLatest(isAuthorized$, isLoggedIn$) - .subscribe(([isAuthorized, isLoggedIn]) => { - if (isAuthorized && isLoggedIn) { - const fileLink$ = this.fileService.downloadFile(bitstream._links.content.href); - this.subs.push(fileLink$.subscribe((fileLink) => { - this.hardRedirectService.redirect(fileLink); + this.bitstream$.pipe( + switchMap((bitstream: Bitstream) => { + const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); + const isLoggedIn$ = this.auth.isAuthenticated(); + return observableCombineLatest([isAuthorized$, isLoggedIn$, observableOf(bitstream)]); + }), + filter(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => hasValue(isAuthorized) && hasValue(isLoggedIn)), + take(1), + switchMap(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => { + if (isAuthorized && isLoggedIn) { + return this.fileService.retrieveFileDownloadLink(bitstream._links.content.href).pipe( + filter((fileLink) => hasValue(fileLink)), + take(1), + map((fileLink) => { + return [isAuthorized, isLoggedIn, bitstream, fileLink]; + // return [isAuthorized, isLoggedIn, bitstream]; })); - } else if (isAuthorized && !isLoggedIn) { - this.hardRedirectService.redirect(bitstream._links.content.href); - } else if (!isAuthorized && isLoggedIn) { - this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); - } else if (!isAuthorized && !isLoggedIn) { - this.auth.setRedirectUrl(this.router.url); - this.router.navigateByUrl('login'); - } - })); - })); - - } - - ngOnDestroy(): void { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); + } else { + return [[isAuthorized, isLoggedIn, bitstream, '']]; + } + }) + ).subscribe(([isAuthorized, isLoggedIn, bitstream, fileLink]: [boolean, boolean, Bitstream, string]) => { + if (isAuthorized && isLoggedIn && isNotEmpty(fileLink)) { + this.hardRedirectService.redirect(fileLink); + } else if (isAuthorized && !isLoggedIn) { + this.hardRedirectService.redirect(bitstream._links.content.href); + } else if (!isAuthorized && isLoggedIn) { + this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); + } else if (!isAuthorized && !isLoggedIn) { + this.auth.setRedirectUrl(this.router.url); + this.router.navigateByUrl('login'); + } + }); } } diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts index b7e67ff2ec..a4dc0a1d3d 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts @@ -82,7 +82,7 @@ export class ItemDetailPreviewComponent { first()) .subscribe((url) => { const fileUrl = `${url}/${uuid}/content`; - this.fileService.downloadFile(fileUrl); + this.fileService.retrieveFileDownloadLink(fileUrl); }); } diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 730a456faa..ac91e5eb3c 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -41,7 +41,7 @@ import { FormBuilderService } from '../../../../shared/form/builder/form-builder function getMockFileService(): FileService { return jasmine.createSpyObj('FileService', { - downloadFile: jasmine.createSpy('downloadFile'), + retrieveFileDownloadLink: jasmine.createSpy('retrieveFileDownloadLink'), getFileNameFromResponseContentDisposition: jasmine.createSpy('getFileNameFromResponseContentDisposition') }); } @@ -232,7 +232,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { tick(); - expect(fileService.downloadFile).toHaveBeenCalled(); + expect(fileService.retrieveFileDownloadLink).toHaveBeenCalled(); })); it('should save Bitstream File data properly when form is valid', fakeAsync(() => { diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts index 80945bc1fd..5a97140a70 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -224,7 +224,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { first()) .subscribe((url) => { const fileUrl = `${url}/${this.fileData.uuid}/content`; - this.fileService.downloadFile(fileUrl); + this.fileService.retrieveFileDownloadLink(fileUrl); }); } From a1abface1335762f4a23319837162595155402f2 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 11:32:56 +0100 Subject: [PATCH 22/46] Fix lint issues --- .../file-download-link/file-download-link.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 53f39b104e..6f7f50e585 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -51,12 +51,12 @@ describe('FileDownloadLinkComponent', () => { it('should set the bitstreamPath based on the input bitstream', () => { expect(component.bitstreamPath).toEqual(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); }); - }) + }); it('should init the component', () => { const link = fixture.debugElement.query(By.css('a')).nativeElement; expect(link.href).toContain(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); - }) + }); }); }); From 69c29407a29b7870edfd6db4cb6221a0227321ef Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 14:37:14 +0100 Subject: [PATCH 23/46] Remove comment --- .../bitstream-download-page/bitstream-download-page.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts index 5b82928a64..a09d7e8b3e 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -62,7 +62,6 @@ export class BitstreamDownloadPageComponent implements OnInit { take(1), map((fileLink) => { return [isAuthorized, isLoggedIn, bitstream, fileLink]; - // return [isAuthorized, isLoggedIn, bitstream]; })); } else { return [[isAuthorized, isLoggedIn, bitstream, '']]; From 99a96451b121ab6ff500ff7fc70976e83e7dc3b3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 12 Feb 2021 10:01:08 +0100 Subject: [PATCH 24/46] Update metadataservice citation tag to refer to new download page --- src/app/app-routing-paths.ts | 5 +++++ .../core/metadata/metadata.service.spec.ts | 2 +- src/app/core/metadata/metadata.service.ts | 6 ++++-- .../file-download-link.component.ts | 19 ++----------------- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 08f7b9585f..ecd9dc51d4 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -6,6 +6,7 @@ import { getCommunityPageRoute } from './+community-page/community-page-routing- import { getCollectionPageRoute } from './+collection-page/collection-page-routing-paths'; import { getItemPageRoute } from './+item-page/item-page-routing-paths'; import { hasValue } from './shared/empty.util'; +import { URLCombiner } from './core/url-combiner/url-combiner'; export const BITSTREAM_MODULE_PATH = 'bitstreams'; @@ -13,6 +14,10 @@ export function getBitstreamModuleRoute() { return `/${BITSTREAM_MODULE_PATH}`; } +export function getBitstreamDownloadRoute(bitstream) : string{ + return new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(); +} + export const ADMIN_MODULE_PATH = 'admin'; export function getAdminModuleRoute() { diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 043bc3cc05..18421dd489 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -222,7 +222,7 @@ describe('MetadataService', () => { expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([environment.ui.baseUrl, router.url].join('')); - expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); + expect(tagStore.get('citation_pdf_url')[0].content).toEqual('/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/download'); })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index eed4228683..5bbb90ac80 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -22,6 +22,8 @@ import { Item } from '../shared/item.model'; import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; +import { URLCombiner } from '../url-combiner/url-combiner'; +import { getBitstreamDownloadRoute, getBitstreamModuleRoute } from '../../app-routing-paths'; @Injectable() export class MetadataService { @@ -40,7 +42,6 @@ export class MetadataService { private dsoNameService: DSONameService, private bitstreamDataService: BitstreamDataService, private bitstreamFormatDataService: BitstreamFormatDataService, - private redirectService: HardRedirectService, private rootService: RootDataService ) { // TODO: determine what open graph meta tags are needed and whether @@ -286,7 +287,8 @@ export class MetadataService { getFirstSucceededRemoteDataPayload() ).subscribe((format: BitstreamFormat) => { if (format.mimetype === 'application/pdf') { - this.addMetaTag('citation_pdf_url', bitstream._links.content.href); + const bitstreamLink = getBitstreamDownloadRoute(bitstream); + this.addMetaTag('citation_pdf_url', bitstreamLink); } }); } diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index 8a1fbd6e0e..4423b6f5b7 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -1,10 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FileService } from '../../core/shared/file.service'; -import { Observable } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; import { Bitstream } from '../../core/shared/bitstream.model'; -import { getBitstreamModuleRoute } from '../../app-routing-paths'; -import { URLCombiner } from '../../core/url-combiner/url-combiner'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; @Component({ selector: 'ds-file-download-link', @@ -24,22 +20,11 @@ export class FileDownloadLinkComponent implements OnInit { @Input() bitstream: Bitstream; bitstreamPath: string; - /** - * Whether or not the current user is authenticated - */ - isAuthenticated$: Observable; - - constructor(private fileService: FileService, - private authService: AuthService, - ) { - } - ngOnInit() { - this.isAuthenticated$ = this.authService.isAuthenticated(); this.bitstreamPath = this.getBitstreamPath(); } getBitstreamPath() { - return new URLCombiner(getBitstreamModuleRoute(), this.bitstream.uuid, 'download').toString(); + return getBitstreamDownloadRoute(this.bitstream); } } From 72ca74bdf343baebdf20099f4b5c1f8e85069a8c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 12 Feb 2021 10:31:17 +0100 Subject: [PATCH 25/46] Fix checkstyle issue --- src/app/app-routing-paths.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index ecd9dc51d4..7dfdbd2c49 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -14,7 +14,7 @@ export function getBitstreamModuleRoute() { return `/${BITSTREAM_MODULE_PATH}`; } -export function getBitstreamDownloadRoute(bitstream) : string{ +export function getBitstreamDownloadRoute(bitstream): string { return new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(); } From ff4bd59de0d280121420d5fd4721891ac30f7e6f Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:45:06 +0200 Subject: [PATCH 26/46] use GET for shortlivedtoken requests on the server, POST on the client --- .../core/auth/auth-request.service.spec.ts | 74 +++++++++++++++++++ src/app/core/auth/auth-request.service.ts | 31 +++++--- .../auth/browser-auth-request.service.spec.ts | 29 ++++++++ .../core/auth/browser-auth-request.service.ts | 34 +++++++++ .../auth/server-auth-request.service.spec.ts | 34 +++++++++ .../core/auth/server-auth-request.service.ts | 36 +++++++++ src/app/core/core.module.ts | 2 - src/modules/app/browser-app.module.ts | 6 ++ src/modules/app/server-app.module.ts | 6 ++ 9 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 src/app/core/auth/auth-request.service.spec.ts create mode 100644 src/app/core/auth/browser-auth-request.service.spec.ts create mode 100644 src/app/core/auth/browser-auth-request.service.ts create mode 100644 src/app/core/auth/server-auth-request.service.spec.ts create mode 100644 src/app/core/auth/server-auth-request.service.ts diff --git a/src/app/core/auth/auth-request.service.spec.ts b/src/app/core/auth/auth-request.service.spec.ts new file mode 100644 index 0000000000..707daf9e30 --- /dev/null +++ b/src/app/core/auth/auth-request.service.spec.ts @@ -0,0 +1,74 @@ +import { AuthRequestService } from './auth-request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { PostRequest } from '../data/request.models'; +import { TestScheduler } from 'rxjs/testing'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { ShortLivedToken } from './models/short-lived-token.model'; +import { RemoteData } from '../data/remote-data'; + +describe(`AuthRequestService`, () => { + let halService: HALEndpointService; + let endpointURL: string; + let shortLivedToken: ShortLivedToken; + let shortLivedTokenRD: RemoteData; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let service: AuthRequestService; + let testScheduler; + + class TestAuthRequestService extends AuthRequestService { + constructor( + hes: HALEndpointService, + rs: RequestService, + rdbs: RemoteDataBuildService + ) { + super(hes, rs, rdbs); + } + + protected createShortLivedTokenRequest(href: string): PostRequest { + return new PostRequest(this.requestService.generateRequestId(), href); + } + } + + const init = (cold: typeof TestScheduler.prototype.createColdObservable) => { + endpointURL = 'https://rest.api/auth'; + shortLivedToken = Object.assign(new ShortLivedToken(), { + value: 'some-token' + }); + shortLivedTokenRD = createSuccessfulRemoteDataObject(shortLivedToken); + + halService = jasmine.createSpyObj('halService', { + 'getEndpoint': cold('a', { a: endpointURL }) + }); + requestService = jasmine.createSpyObj('requestService', { + 'send': null + }); + rdbService = jasmine.createSpyObj('rdbService', { + 'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD }) + }); + + service = new TestAuthRequestService(halService, requestService, rdbService); + }; + + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + }); + + describe(`getShortlivedToken`, () => { + it(`should call createShortLivedTokenRequest with the url for the endpoint`, () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + init(cold); + spyOn(service as any, 'createShortLivedTokenRequest'); + // expectObservable is needed to let testScheduler know to take it in to account, but since + // we're not testing the outcome in this test, a .toBe(…) isn't necessary + expectObservable(service.getShortlivedToken()); + flush(); + expect((service as any).createShortLivedTokenRequest).toHaveBeenCalledWith(`${endpointURL}/shortlivedtokens`); + }); + }); + }); +}); diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 4315ddfea8..00a94822d3 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,14 +1,9 @@ import { Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; import { isNotEmpty } from '../../shared/empty.util'; -import { - GetRequest, - PostRequest, - RestRequest, -} from '../data/request.models'; +import { GetRequest, PostRequest, RestRequest, } from '../data/request.models'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -17,8 +12,10 @@ import { AuthStatus } from './models/auth-status.model'; import { ShortLivedToken } from './models/short-lived-token.model'; import { URLCombiner } from '../url-combiner/url-combiner'; -@Injectable() -export class AuthRequestService { +/** + * Abstract service to send authentication requests + */ +export abstract class AuthRequestService { protected linkName = 'authn'; protected browseEndpoint = ''; protected shortlivedtokensEndpoint = 'shortlivedtokens'; @@ -62,16 +59,26 @@ export class AuthRequestService { } /** - * Send a POST request to retrieve a short-lived token which provides download access of restricted files + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected abstract createShortLivedTokenRequest(href: string): GetRequest | PostRequest; + + /** + * Send a request to retrieve a short-lived token which provides download access of restricted files */ public getShortlivedToken(): Observable { return this.halService.getEndpoint(this.linkName).pipe( filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()), - map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), - tap((request: PostRequest) => this.requestService.send(request)), - switchMap((request: PostRequest) => this.rdbService.buildFromRequestUUID(request.uuid)), + map((endpointURL: string) => this.createShortLivedTokenRequest(endpointURL)), + tap((request: RestRequest) => this.requestService.send(request)), + switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)), getFirstCompletedRemoteData(), map((response: RemoteData) => { if (response.hasSucceeded) { diff --git a/src/app/core/auth/browser-auth-request.service.spec.ts b/src/app/core/auth/browser-auth-request.service.spec.ts new file mode 100644 index 0000000000..18d27340af --- /dev/null +++ b/src/app/core/auth/browser-auth-request.service.spec.ts @@ -0,0 +1,29 @@ +import { AuthRequestService } from './auth-request.service'; +import { RequestService } from '../data/request.service'; +import { BrowserAuthRequestService } from './browser-auth-request.service'; + +describe(`BrowserAuthRequestService`, () => { + let href: string; + let requestService: RequestService; + let service: AuthRequestService; + + beforeEach(() => { + href = 'https://rest.api/auth/shortlivedtokens'; + requestService = jasmine.createSpyObj('requestService', { + 'generateRequestId': '8bb0582d-5013-4337-af9c-763beb25aae2' + }); + service = new BrowserAuthRequestService(null, requestService, null); + }); + + describe(`createShortLivedTokenRequest`, () => { + it(`should return a PostRequest`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.constructor.name).toBe('PostRequest'); + }); + + it(`should return a request with the given href`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.href).toBe(href) ; + }); + }); +}); diff --git a/src/app/core/auth/browser-auth-request.service.ts b/src/app/core/auth/browser-auth-request.service.ts new file mode 100644 index 0000000000..85d5f54340 --- /dev/null +++ b/src/app/core/auth/browser-auth-request.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { AuthRequestService } from './auth-request.service'; +import { PostRequest } from '../data/request.models'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +/** + * Client side version of the service to send authentication requests + */ +@Injectable() +export class BrowserAuthRequestService extends AuthRequestService { + + constructor( + halService: HALEndpointService, + requestService: RequestService, + rdbService: RemoteDataBuildService + ) { + super(halService, requestService, rdbService); + } + + /** + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected createShortLivedTokenRequest(href: string): PostRequest { + return new PostRequest(this.requestService.generateRequestId(), href); + } + +} diff --git a/src/app/core/auth/server-auth-request.service.spec.ts b/src/app/core/auth/server-auth-request.service.spec.ts new file mode 100644 index 0000000000..69053fbb3a --- /dev/null +++ b/src/app/core/auth/server-auth-request.service.spec.ts @@ -0,0 +1,34 @@ +import { AuthRequestService } from './auth-request.service'; +import { RequestService } from '../data/request.service'; +import { ServerAuthRequestService } from './server-auth-request.service'; + +describe(`ServerAuthRequestService`, () => { + let href: string; + let requestService: RequestService; + let service: AuthRequestService; + + beforeEach(() => { + href = 'https://rest.api/auth/shortlivedtokens'; + requestService = jasmine.createSpyObj('requestService', { + 'generateRequestId': '8bb0582d-5013-4337-af9c-763beb25aae2' + }); + service = new ServerAuthRequestService(null, requestService, null); + }); + + describe(`createShortLivedTokenRequest`, () => { + it(`should return a GetRequest`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.constructor.name).toBe('GetRequest'); + }); + + it(`should return a request with the given href`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.href).toBe(href) ; + }); + + it(`should have a responseMsToLive of 2 seconds`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.responseMsToLive).toBe(2 * 1000) ; + }); + }); +}); diff --git a/src/app/core/auth/server-auth-request.service.ts b/src/app/core/auth/server-auth-request.service.ts new file mode 100644 index 0000000000..751389f71d --- /dev/null +++ b/src/app/core/auth/server-auth-request.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { AuthRequestService } from './auth-request.service'; +import { GetRequest } from '../data/request.models'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +/** + * Server side version of the service to send authentication requests + */ +@Injectable() +export class ServerAuthRequestService extends AuthRequestService { + + constructor( + halService: HALEndpointService, + requestService: RequestService, + rdbService: RemoteDataBuildService + ) { + super(halService, requestService, rdbService); + } + + /** + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected createShortLivedTokenRequest(href: string): GetRequest { + return Object.assign(new GetRequest(this.requestService.generateRequestId(), href), { + responseMsToLive: 2 * 1000 // A short lived token is only valid for 2 seconds. + }); + } + +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f73bfd0bdf..619a7dbadc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -31,7 +31,6 @@ import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service'; -import { AuthRequestService } from './auth/auth-request.service'; import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthStatus } from './auth/models/auth-status.model'; import { BrowseService } from './browse/browse.service'; @@ -188,7 +187,6 @@ const EXPORTS = []; const PROVIDERS = [ ApiService, AuthenticatedGuard, - AuthRequestService, CommunityDataService, CollectionDataService, SiteDataService, diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 4b6c5c813e..070d530dfe 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -31,6 +31,8 @@ import { import { LocaleService } from '../../app/core/locale/locale.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { RouterModule, NoPreloading } from '@angular/router'; +import { AuthRequestService } from '../../app/core/auth/auth-request.service'; +import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; export const REQ_KEY = makeStateKey('req'); @@ -104,6 +106,10 @@ export function getRequest(transferState: TransferState): any { provide: GoogleAnalyticsService, useClass: GoogleAnalyticsService, }, + { + provide: AuthRequestService, + useClass: BrowserAuthRequestService, + }, { provide: LocationToken, useFactory: locationProvider, diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 906d4c5f35..dad3a60d5c 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -31,6 +31,8 @@ import { ServerHardRedirectService } from '../../app/core/services/server-hard-r import { Angulartics2 } from 'angulartics2'; import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock'; import { RouterModule } from '@angular/router'; +import { AuthRequestService } from '../../app/core/auth/auth-request.service'; +import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service'; export function createTranslateLoader() { return new TranslateJson5UniversalLoader('dist/server/assets/i18n/', '.json5'); @@ -82,6 +84,10 @@ export function createTranslateLoader() { provide: SubmissionService, useClass: ServerSubmissionService }, + { + provide: AuthRequestService, + useClass: ServerAuthRequestService, + }, { provide: LocaleService, useClass: ServerLocaleService From 3c93777e845d27860e6d648c6bd078d17f188821 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:45:35 +0200 Subject: [PATCH 27/46] don't use CSR fallback when the headers are changed after the response is sent --- server.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.ts b/server.ts index ada6c9f040..73b88cd0c6 100644 --- a/server.ts +++ b/server.ts @@ -160,6 +160,11 @@ function ngApp(req, res) { }, (err, data) => { if (hasNoValue(err) && hasValue(data)) { res.send(data); + } else if (hasValue(err) && err.code === 'ERR_HTTP_HEADERS_SENT') { + // When this error occurs we can't fall back to CSR because the response has already been + // sent. These errors occur for various reasons in universal, not all of which are in our + // control to solve. + console.warn('Warning [ERR_HTTP_HEADERS_SENT]: Tried to set headers after they were sent to the client'); } else { console.warn('Error in SSR, serving for direct CSR.'); if (hasValue(err)) { From 425f530f087eba079e12223109b23d9d1a55187d Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:58:37 +0200 Subject: [PATCH 28/46] fix LGTM warnings --- src/app/core/metadata/metadata.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 5bbb90ac80..807f7a42ab 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; @@ -19,11 +19,13 @@ import { BitstreamFormat } from '../shared/bitstream-format.model'; import { Bitstream } from '../shared/bitstream.model'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; -import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; +import { + getFirstSucceededRemoteDataPayload, + getFirstSucceededRemoteListPayload +} from '../shared/operators'; import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; -import { URLCombiner } from '../url-combiner/url-combiner'; -import { getBitstreamDownloadRoute, getBitstreamModuleRoute } from '../../app-routing-paths'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; @Injectable() export class MetadataService { From b8cb8f90b0d15bf82924a8eff52d83781b5f34b1 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:26:00 +0200 Subject: [PATCH 29/46] increase contrast to improve accessibility --- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 86d4092a53..2d3b63dc82 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -8,7 +8,8 @@ $font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; $gray-100: #e8ebf3 !default; $gray-400: #ced4da !default; -$gray-800: #444444 !default; // #444 +$gray-600: #959595 !default; +$gray-800: #444444 !default; $navbar-dark-color: #FFFFFF; @@ -16,7 +17,7 @@ $navbar-dark-color: #FFFFFF; $blue: #43515f !default; //$green: #92C642 !default; $green: #92C642 !default; -$cyan: #2e80a3 !default; +$cyan: #207698 !default; $yellow: #ec9433 !default; $red: #CF4444 !default; $dark: #43515f !default; From 6b7da66c81bc72690b576b74e16eabafa7fd69a2 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:26:17 +0200 Subject: [PATCH 30/46] update link href --- .../dspace/app/+home-page/home-news/home-news.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.html b/src/themes/dspace/app/+home-page/home-news/home-news.component.html index 9063961ace..f58c307705 100644 --- a/src/themes/dspace/app/+home-page/home-news/home-news.component.html +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.html @@ -19,7 +19,7 @@ handle.net and DataCite DOI -

Join an international community of leading institutions using DSpace.

+

Join an international community of leading institutions using DSpace.

Photo by @inspiredimages From 7cbce550e214c296a7dcd44902e554d49a7c72ff Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:28:19 +0200 Subject: [PATCH 31/46] remove commented out line --- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 2d3b63dc82..5a6420834d 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -15,7 +15,6 @@ $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ $blue: #43515f !default; -//$green: #92C642 !default; $green: #92C642 !default; $cyan: #207698 !default; $yellow: #ec9433 !default; From 9bf2465ee8b47019a0ff9388ea17dce742c124cc Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 8 Apr 2021 14:18:13 +0200 Subject: [PATCH 32/46] change the font and overlay color on the home-page background image to improve readability --- src/themes/dspace/styles/_theme_css_variable_overrides.scss | 2 +- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index 75d4fd9362..2a61babdb7 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -1,7 +1,7 @@ // Override or add CSS variables for your theme here :root { - --ds-banner-text-background: rgba(0, 0, 0, 0.35); + --ds-banner-text-background: rgba(0, 0, 0, 0.45); --ds-banner-background-gradient-width: 300px; --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 5a6420834d..70aa0b1850 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -3,9 +3,9 @@ // still uses Sass variables internally. So if you want to override bootstrap (or other sass // variables) you can do so here. Their CSS counterparts will include the changes you make here -@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,200i,300,300i,400,400i,600,600i,700,700i,900,900i&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese'); +@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200;0,300;0,400;0,600;0,700;0,800;1,200;1,300;1,400;1,600;1,700;1,800&display=swap'); -$font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +$font-family-sans-serif: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; $gray-100: #e8ebf3 !default; $gray-400: #ced4da !default; $gray-600: #959595 !default; From 4e21bdaf1fc4d4b19ef9e6fa4e24328ab87dab22 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 9 Apr 2021 10:16:56 +0200 Subject: [PATCH 33/46] [CST-3782] repeatable with lookup fixes --- .../ds-dynamic-form-control-container.component.html | 2 ++ .../ds-dynamic-form-control-container.component.ts | 8 +++++--- .../existing-metadata-list-element.component.html | 1 + .../existing-metadata-list-element.component.spec.ts | 3 +++ .../existing-metadata-list-element.component.ts | 6 +++++- .../existing-relation-list-element.component.html | 1 + .../existing-relation-list-element.component.spec.ts | 3 +++ .../existing-relation-list-element.component.ts | 4 ++++ 8 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index bfa9c214e9..7c0c08382a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -54,6 +54,7 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" + [canRemove]="canRemove()" (remove)="onRemove()" > @@ -66,6 +67,7 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" + [canRemove]="canRemove()" > 1; + } + /** * Initialize this.item$ based on this.model.submissionId */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 07ea131a00..5275cef68a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -9,6 +9,7 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts index 3a5623cfdd..2a38a07759 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear import { of as observableOf } from 'rxjs'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../testing/translate-loader.mock'; +import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionServiceStub } from '../../../../testing/submission-service.stub'; describe('ExistingMetadataListElementComponent', () => { let component: ExistingMetadataListElementComponent; @@ -79,6 +81,7 @@ describe('ExistingMetadataListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, + { provide: SubmissionService, useClass: SubmissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index d0d4ad435b..927d5e04d2 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -19,6 +19,7 @@ import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-v import { RelationshipOptions } from '../../models/relationship-options.model'; import { DynamicConcatModel } from '../models/ds-dynamic-concat.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; +import { SubmissionService } from '../../../../../submission/submission.service'; // tslint:disable:max-classes-per-file /** @@ -145,6 +146,7 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; + @Input() canRemove = true; metadataRepresentation$: BehaviorSubject = new BehaviorSubject(undefined); relatedItem: Item; @Output() remove: EventEmitter = new EventEmitter(); @@ -155,7 +157,8 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, constructor( private selectableListService: SelectableListService, - private store: Store + private store: Store, + private submissionService: SubmissionService ) { } @@ -194,6 +197,7 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, * Removes the selected relationship from the list */ removeSelection() { + this.submissionService.dispatchSave(this.submissionId); this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); this.remove.emit(); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html index 15087d2553..ed29fd14e3 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html @@ -8,6 +8,7 @@
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts index fddb960515..d733f503bb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts @@ -12,6 +12,8 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear import { of as observableOf } from 'rxjs'; import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component'; import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; +import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionServiceStub } from '../../../../testing/submission-service.stub'; describe('ExistingRelationListElementComponent', () => { let component: ExistingRelationListElementComponent; @@ -67,6 +69,7 @@ describe('ExistingRelationListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, + { provide: SubmissionService, useClass: SubmissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts index 8a4a7120e8..6d7e846bcb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts @@ -12,6 +12,7 @@ import { RelationshipOptions } from '../../models/relationship-options.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component'; +import { SubmissionService } from '../../../../../submission/submission.service'; // tslint:disable:max-classes-per-file /** @@ -61,6 +62,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; + @Input() canRemove = true; relatedItem$: BehaviorSubject = new BehaviorSubject(undefined); viewType = ViewMode.ListElement; @Output() remove: EventEmitter = new EventEmitter(); @@ -72,6 +74,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, constructor( private selectableListService: SelectableListService, + private submissionService: SubmissionService, private store: Store ) { } @@ -102,6 +105,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, * Removes the selected relationship from the list */ removeSelection() { + this.submissionService.dispatchSave(this.submissionId); this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem$.getValue() })); this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem$.getValue(), this.relationshipOptions.relationshipType, this.submissionId)); } From b537f70d7bc8186e78b206c22e0d0935131524ad Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Mon, 12 Apr 2021 10:04:34 +0200 Subject: [PATCH 34/46] [CST-3782] possibility to remove the only relationship --- .../ds-dynamic-form-control-container.component.html | 2 -- .../ds-dynamic-form-control-container.component.ts | 7 +++---- .../existing-metadata-list-element.component.html | 1 - .../existing-metadata-list-element.component.ts | 1 - .../existing-relation-list-element.component.html | 1 - .../existing-relation-list-element.component.ts | 1 - 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index 7c0c08382a..bfa9c214e9 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -54,7 +54,6 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" - [canRemove]="canRemove()" (remove)="onRemove()" > @@ -67,7 +66,6 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" - [canRemove]="canRemove()" > 1; - } - /** * Initialize this.item$ based on this.model.submissionId */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 5275cef68a..07ea131a00 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -9,7 +9,6 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index 927d5e04d2..29097d1337 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -146,7 +146,6 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; - @Input() canRemove = true; metadataRepresentation$: BehaviorSubject = new BehaviorSubject(undefined); relatedItem: Item; @Output() remove: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html index ed29fd14e3..15087d2553 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html @@ -8,7 +8,6 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts index 6d7e846bcb..8e55f32ee6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts @@ -62,7 +62,6 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; - @Input() canRemove = true; relatedItem$: BehaviorSubject = new BehaviorSubject(undefined); viewType = ViewMode.ListElement; @Output() remove: EventEmitter = new EventEmitter(); From 31e4aa788ee484815c3588a7077e56d11af1068a Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 13 Apr 2021 09:51:05 +0200 Subject: [PATCH 35/46] [CST-3782] lookup click doesn't clear the plain value --- ...ynamic-form-control-container.component.ts | 19 +++++++++++++++++++ ...ng-metadata-list-element.component.spec.ts | 5 ++++- ...xisting-metadata-list-element.component.ts | 13 +++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index a2ed538ce6..b8024346f5 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -37,6 +37,7 @@ import { DynamicFormControl, DynamicFormControlContainerComponent, DynamicFormControlEvent, + DynamicFormControlEventType, DynamicFormControlModel, DynamicFormLayout, DynamicFormLayoutService, @@ -394,6 +395,24 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo size: 'lg' }); + if (hasValue(this.model.value)) { + this.focus.emit({ + $event: new Event('focus'), + context: this.context, + control: this.control, + model: this.model, + type: DynamicFormControlEventType.Focus + } as DynamicFormControlEvent); + + this.change.emit({ + $event: new Event('change'), + context: this.context, + control: this.control, + model: this.model, + type: DynamicFormControlEventType.Change + } as DynamicFormControlEvent); + } + this.submissionService.dispatchSave(this.model.submissionId); const modalComp = this.modalRef.componentInstance; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts index 2a38a07759..b5af2a8843 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts @@ -38,6 +38,7 @@ describe('ExistingMetadataListElementComponent', () => { let relatedSearchResult; let submissionId; let relationshipService; + let submissionServiceStub; function init() { uuid1 = '91ce578d-2e63-4093-8c73-3faafd716000'; @@ -64,6 +65,8 @@ describe('ExistingMetadataListElementComponent', () => { relationship = Object.assign(new Relationship(), { leftItem: leftItemRD$, rightItem: rightItemRD$ }); submissionId = '1234'; reoRel = new ReorderableRelationship(relationship, true, {} as any, {} as any, submissionId); + submissionServiceStub = new SubmissionServiceStub(); + submissionServiceStub.getSubmissionObject.and.returnValue(observableOf({})); } beforeEach(waitForAsync(() => { @@ -81,7 +84,7 @@ describe('ExistingMetadataListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, - { provide: SubmissionService, useClass: SubmissionServiceStub }, + { provide: SubmissionService, useValue: submissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index 29097d1337..899400eba0 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -3,7 +3,7 @@ import { FormControl } from '@angular/forms'; import { DynamicFormArrayGroupModel } from '@ng-dynamic-forms/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { filter } from 'rxjs/operators'; +import { filter, take } from 'rxjs/operators'; import { AppState } from '../../../../../app.reducer'; import { RelationshipService } from '../../../../../core/data/relationship.service'; import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model'; @@ -20,6 +20,7 @@ import { RelationshipOptions } from '../../models/relationship-options.model'; import { DynamicConcatModel } from '../models/ds-dynamic-concat.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionObjectEntry } from '../../../../../submission/objects/submission-objects.reducer'; // tslint:disable:max-classes-per-file /** @@ -197,9 +198,13 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, */ removeSelection() { this.submissionService.dispatchSave(this.submissionId); - this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); - this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); - this.remove.emit(); + this.submissionService.getSubmissionObject(this.submissionId).pipe( + filter((state: SubmissionObjectEntry) => !state.savePending && !state.isLoading), + take(1)).subscribe(() => { + this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); + this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); + this.remove.emit(); + }); } /** From 59560b993cc5c0e04fe133f4daf82e9cb9730165 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 14 Apr 2021 10:45:43 +0200 Subject: [PATCH 36/46] 78407: Allow html in submission license section --- .../submission/sections/license/section-license.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/sections/license/section-license.component.html b/src/app/submission/sections/license/section-license.component.html index b8d0f601d2..5ee8a2db6b 100644 --- a/src/app/submission/sections/license/section-license.component.html +++ b/src/app/submission/sections/license/section-license.component.html @@ -1,4 +1,4 @@ -{{ licenseText$ | async }} +

Date: Wed, 14 Apr 2021 16:42:11 +0200 Subject: [PATCH 37/46] use the chromedriver npm package to download the webdriver version compatible with the installed chrome version --- .github/workflows/build.yml | 7 ++++++- e2e/protractor-ci.conf.js | 4 ++++ package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b5b3f9d8c..d2e8b9fe5e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,9 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-yarn- + - name: Install the latest chromedriver compatible with the installed chrome version + run: yarn global add chromedriver --detect_chromedriver_version + - name: Install Yarn dependencies run: yarn install --frozen-lockfile @@ -94,7 +97,9 @@ jobs: run: curl http://localhost:8080/server/api - name: Run e2e tests (integration tests) - run: yarn run e2e:ci + run: | + chromedriver --url-base='/wd/hub' --port=4444 & + yarn run e2e:ci - name: Shutdown Docker containers run: docker-compose -f ./docker/docker-compose-ci.yml down diff --git a/e2e/protractor-ci.conf.js b/e2e/protractor-ci.conf.js index 63173e44e3..0cfc1f9eaf 100644 --- a/e2e/protractor-ci.conf.js +++ b/e2e/protractor-ci.conf.js @@ -7,4 +7,8 @@ config.capabilities = { } }; +// don't use protractor's webdriver, as it may be incompatible with the installed chrome version +config.directConnect = false; +config.seleniumAddress = 'http://localhost:4444/wd/hub'; + exports.config = config; diff --git a/package.json b/package.json index 4008bb0ac3..80af52e264 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", - "e2e:ci": "ng e2e --protractor-config=./e2e/protractor-ci.conf.js", + "e2e:ci": "ng e2e --webdriver-update=false --protractor-config=./e2e/protractor-ci.conf.js", "compile:server": "webpack --config webpack.server.config.js --progress --color", "serve:ssr": "node dist/server", "clean:coverage": "rimraf coverage", From ef8e66546b31dcc55321b27113b86604f245cadd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 13 Apr 2021 16:40:39 -0500 Subject: [PATCH 38/46] Minor updates to default and dspace theme for Testathon. Also updating DuraSpace to LYRASIS in several places. --- .../+home-page/home-news/home-news.component.html | 6 ++++-- src/app/footer/footer.component.html | 2 +- .../lang-switch/lang-switch.component.spec.ts | 2 +- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 4 ++-- src/assets/i18n/en.json5 | 2 +- src/assets/i18n/es.json5 | 4 ++-- src/assets/i18n/fi.json5 | 4 ++-- src/assets/i18n/fr.json5 | 4 ++-- src/assets/i18n/hu.json5 | 4 ++-- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/lv.json5 | 4 ++-- src/assets/i18n/nl.json5 | 4 ++-- src/assets/i18n/pl.json5 | 4 ++-- src/assets/i18n/pt-BR.json5 | 4 ++-- src/assets/i18n/pt-PT.json5 | 4 ++-- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 4 ++-- .../+home-page/home-news/home-news.component.html | 13 ++++++++++++- 20 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/app/+home-page/home-news/home-news.component.html b/src/app/+home-page/home-news/home-news.component.html index 812c38f798..6bee3cd76f 100644 --- a/src/app/+home-page/home-news/home-news.component.html +++ b/src/app/+home-page/home-news/home-news.component.html @@ -2,7 +2,7 @@
-

Welcome to the DSpace 7 Preview

+

DSpace 7

DSpace is the world leading open source repository platform that enables organisations to:

@@ -13,6 +13,8 @@
  • issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI
  • -

    Join an international community of leading institutions using DSpace.

    +

    Join an international community of leading institutions using DSpace. +

    diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index 3756bce188..bc407c2a97 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -56,7 +56,7 @@

    {{ 'footer.link.dspace' | translate}} {{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }} - {{ 'footer.link.duraspace' | translate}} + {{ 'footer.link.lyrasis' | translate}}

    Join an international community of leading institutions using DSpace.

    +

    Participate in the official community Testathon + from April 19th through May 7th. The test user accounts below have their password set to the name of + this + software in lowercase.

    +
      +
    • Demo Site Administrator = dspacedemo+admin@gmail.com
    • +
    • Demo Community Administrator = dspacedemo+commadmin@gmail.com
    • +
    • Demo Collection Administrator = dspacedemo+colladmin@gmail.com
    • +
    • Demo Submitter = dspacedemo+submit@gmail.com
    • +
    Photo by @inspiredimages From a4a747e922b23c23b46aa931c202ac18b7b44ce8 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 13 Apr 2021 12:28:27 +0200 Subject: [PATCH 39/46] fix issue where the submission form wouldn't update after a relationship was added --- src/app/core/shared/operators.ts | 64 +++++++-------- .../edit/submission-edit.component.ts | 82 +++++++++++++------ .../submit/submission-submit.component.ts | 40 ++++++++- 3 files changed, 124 insertions(+), 62 deletions(-) diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index dd610b6ca7..3128538ea9 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -45,32 +45,32 @@ export const sendRequest = (requestService: RequestService) => (source: Observable): Observable => source.pipe(tap((request: RestRequest) => requestService.send(request))); -export const getRemoteDataPayload = () => - (source: Observable>): Observable => +export const getRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe(map((remoteData: RemoteData) => remoteData.payload)); -export const getPaginatedListPayload = () => - (source: Observable>): Observable => +export const getPaginatedListPayload = () => + (source: Observable>): Observable => source.pipe(map((list: PaginatedList) => list.page)); -export const getAllCompletedRemoteData = () => - (source: Observable>): Observable> => +export const getAllCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => hasValue(rd) && rd.hasCompleted)); -export const getFirstCompletedRemoteData = () => - (source: Observable>): Observable> => +export const getFirstCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(getAllCompletedRemoteData(), take(1)); -export const takeUntilCompletedRemoteData = () => - (source: Observable>): Observable> => +export const takeUntilCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(takeWhile((rd: RemoteData) => hasNoValue(rd) || rd.isLoading, true)); -export const getFirstSucceededRemoteData = () => - (source: Observable>): Observable> => +export const getFirstSucceededRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => rd.hasSucceeded), take(1)); -export const getFirstSucceededRemoteWithNotEmptyData = () => - (source: Observable>): Observable> => +export const getFirstSucceededRemoteWithNotEmptyData = () => + (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => rd.hasSucceeded && isNotEmpty(rd.payload))); /** @@ -83,8 +83,8 @@ export const getFirstSucceededRemoteWithNotEmptyData = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteDataPayload = () => - (source: Observable>): Observable => +export const getFirstSucceededRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe( getFirstSucceededRemoteData(), getRemoteDataPayload() @@ -100,8 +100,8 @@ export const getFirstSucceededRemoteDataPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => - (source: Observable>): Observable => +export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => + (source: Observable>): Observable => source.pipe( getFirstSucceededRemoteWithNotEmptyData(), getRemoteDataPayload() @@ -117,8 +117,8 @@ export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getAllSucceededRemoteDataPayload = () => - (source: Observable>): Observable => +export const getAllSucceededRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe( getAllSucceededRemoteData(), getRemoteDataPayload() @@ -138,8 +138,8 @@ export const getAllSucceededRemoteDataPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteListPayload = () => - (source: Observable>>): Observable => +export const getFirstSucceededRemoteListPayload = () => + (source: Observable>>): Observable => source.pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), @@ -160,8 +160,8 @@ export const getFirstSucceededRemoteListPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getAllSucceededRemoteListPayload = () => - (source: Observable>>): Observable => +export const getAllSucceededRemoteListPayload = () => + (source: Observable>>): Observable => source.pipe( getAllSucceededRemoteData(), getRemoteDataPayload(), @@ -174,8 +174,8 @@ export const getAllSucceededRemoteListPayload = () => * @param router The router used to navigate to a new page * @param authService Service to check if the user is authenticated */ -export const redirectOn4xx = (router: Router, authService: AuthService) => - (source: Observable>): Observable> => +export const redirectOn4xx = (router: Router, authService: AuthService) => + (source: Observable>): Observable> => observableCombineLatest(source, authService.isAuthenticated()).pipe( map(([rd, isAuthenticated]: [RemoteData, boolean]) => { if (rd.hasFailed) { @@ -229,16 +229,16 @@ export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: s return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams }); })); -export const getFinishedRemoteData = () => - (source: Observable>): Observable> => +export const getFinishedRemoteData = () => + (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => !rd.isLoading)); -export const getAllSucceededRemoteData = () => - (source: Observable>): Observable> => +export const getAllSucceededRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => rd.hasSucceeded)); -export const toDSpaceObjectListRD = () => - (source: Observable>>>): Observable>> => +export const toDSpaceObjectListRD = () => + (source: Observable>>>): Observable>> => source.pipe( filter((rd: RemoteData>>) => rd.hasSucceeded), map((rd: RemoteData>>) => { diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 7908a052b7..34fdcba104 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -2,11 +2,11 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Subscription } from 'rxjs'; -import { filter, switchMap } from 'rxjs/operators'; +import { filter, switchMap, debounceTime } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; -import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { SubmissionService } from '../submission.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -14,6 +14,9 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { getAllSucceededRemoteData } from '../../core/shared/operators'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; /** * This component allows to edit an existing workspaceitem/workflowitem. @@ -60,6 +63,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * @type {Array} */ private subs: Subscription[] = []; + + /** + * BehaviorSubject containing the self link to the item for this submission + * @private + */ + private itemLink$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * The item for this submission. + */ public item: Item; /** @@ -69,6 +82,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * @param {NotificationsService} notificationsService * @param {ActivatedRoute} route * @param {Router} router + * @param {ItemDataService} itemDataService * @param {SubmissionService} submissionService * @param {TranslateService} translate */ @@ -76,6 +90,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private route: ActivatedRoute, private router: Router, + private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService) { } @@ -84,32 +99,47 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * Retrieve workspaceitem/workflowitem from server and initialize all instance variables */ ngOnInit() { - this.subs.push(this.route.paramMap.pipe( - switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), - // NOTE new submission is retrieved on the browser side only, so get null on server side rendering - filter((submissionObjectRD: RemoteData) => isNotNull(submissionObjectRD)) - ).subscribe((submissionObjectRD: RemoteData) => { - if (submissionObjectRD.hasSucceeded) { - if (isEmpty(submissionObjectRD.payload)) { - this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit')); - this.router.navigate(['/mydspace']); + this.subs.push( + this.route.paramMap.pipe( + switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), + // NOTE new submission is retrieved on the browser side only, so get null on server side rendering + filter((submissionObjectRD: RemoteData) => isNotNull(submissionObjectRD)) + ).subscribe((submissionObjectRD: RemoteData) => { + if (submissionObjectRD.hasSucceeded) { + if (isEmpty(submissionObjectRD.payload)) { + this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit')); + this.router.navigate(['/mydspace']); + } else { + this.submissionId = submissionObjectRD.payload.id.toString(); + this.collectionId = (submissionObjectRD.payload.collection as Collection).id; + this.selfUrl = submissionObjectRD.payload._links.self.href; + this.sections = submissionObjectRD.payload.sections; + this.itemLink$.next(submissionObjectRD.payload._links.item.href); + this.item = submissionObjectRD.payload.item; + this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel); + } } else { - this.submissionId = submissionObjectRD.payload.id.toString(); - this.collectionId = (submissionObjectRD.payload.collection as Collection).id; - this.selfUrl = submissionObjectRD.payload._links.self.href; - this.sections = submissionObjectRD.payload.sections; - this.item = submissionObjectRD.payload.item as Item; - this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel); - this.changeDetectorRef.detectChanges(); + if (submissionObjectRD.statusCode === 404) { + // redirect to not found page + this.router.navigate(['/404'], { skipLocationChange: true }); + } + // TODO handle generic error } - } else { - if (submissionObjectRD.statusCode === 404) { - // redirect to not found page - this.router.navigate(['/404'], { skipLocationChange: true }); - } - // TODO handle generic error - } - })); + }), + this.itemLink$.pipe( + isNotEmptyOperator(), + switchMap((itemLink: string) => + this.itemDataService.findByHref(itemLink) + ), + getAllSucceededRemoteData(), + // Multiple sources can update the item in quick succession. + // We only want to rerender the form if the item is unchanged for some time + debounceTime(300), + ).subscribe((itemRd: RemoteData) => { + this.item = itemRd.payload; + this.changeDetectorRef.detectChanges(); + }), + ); } /** diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index 003f5280a8..0c2172368a 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; -import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -12,6 +12,11 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { Item } from '../../core/shared/item.model'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { switchMap, debounceTime } from 'rxjs/operators'; +import { getAllSucceededRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { ItemDataService } from '../../core/data/item-data.service'; /** * This component allows to submit a new workspaceitem. @@ -28,6 +33,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { * @type {string} */ public collectionId: string; + + /** + * BehaviorSubject containing the self link to the item for this submission + * @private + */ + private itemLink$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * The item for this submission. + */ public item: Item; /** @@ -71,6 +86,7 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { * * @param {ChangeDetectorRef} changeDetectorRef * @param {NotificationsService} notificationsService + * @param {ItemDataService} itemDataService * @param {SubmissionService} submissionService * @param {Router} router * @param {TranslateService} translate @@ -80,13 +96,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { constructor(private changeDetectorRef: ChangeDetectorRef, private notificationsService: NotificationsService, private router: Router, + private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService, private viewContainerRef: ViewContainerRef, private route: ActivatedRoute) { this.route .queryParams - .subscribe((params) => { this.collectionParam = (params.collection); }); + .subscribe((params) => { + this.collectionParam = (params.collection); + }); } /** @@ -108,11 +127,24 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { this.selfUrl = submissionObject._links.self.href; this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel); this.submissionId = submissionObject.id; + this.itemLink$.next(submissionObject._links.item.href); this.item = submissionObject.item as Item; - this.changeDetectorRef.detectChanges(); } } - }) + }), + this.itemLink$.pipe( + isNotEmptyOperator(), + switchMap((itemLink: string) => + this.itemDataService.findByHref(itemLink) + ), + getAllSucceededRemoteData(), + // Multiple sources can update the item in quick succession. + // We only want to rerender the form if the item is unchanged for some time + debounceTime(300), + ).subscribe((itemRd: RemoteData) => { + this.item = itemRd.payload; + this.changeDetectorRef.detectChanges(); + }) ); } From 56a54ae1c99b787ca3e9f1c4168a4ce90db2f365 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 13 Apr 2021 13:54:16 +0200 Subject: [PATCH 40/46] don't hide the lookup button for virtual relationship fields --- .../ds-dynamic-form-control-container.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index bfa9c214e9..085ccb6248 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -35,7 +35,7 @@
    -
    +