diff --git a/cypress/e2e/browse-by-author.cy.ts b/cypress/e2e/browse-by-author.cy.ts index 07c20ad7c9..32c470231d 100644 --- a/cypress/e2e/browse-by-author.cy.ts +++ b/cypress/e2e/browse-by-author.cy.ts @@ -5,9 +5,9 @@ describe('Browse By Author', () => { cy.visit('/browse/author'); // Wait for to be visible - cy.get('ds-browse-by-metadata-page').should('be.visible'); + cy.get('ds-browse-by-metadata').should('be.visible'); // Analyze for accessibility - testA11y('ds-browse-by-metadata-page'); + testA11y('ds-browse-by-metadata'); }); }); diff --git a/cypress/e2e/browse-by-dateissued.cy.ts b/cypress/e2e/browse-by-dateissued.cy.ts index 4d22420227..7966f1c82e 100644 --- a/cypress/e2e/browse-by-dateissued.cy.ts +++ b/cypress/e2e/browse-by-dateissued.cy.ts @@ -5,9 +5,9 @@ describe('Browse By Date Issued', () => { cy.visit('/browse/dateissued'); // Wait for to be visible - cy.get('ds-browse-by-date-page').should('be.visible'); + cy.get('ds-browse-by-date').should('be.visible'); // Analyze for accessibility - testA11y('ds-browse-by-date-page'); + testA11y('ds-browse-by-date'); }); }); diff --git a/cypress/e2e/browse-by-subject.cy.ts b/cypress/e2e/browse-by-subject.cy.ts index 89b791f03c..57ca88d103 100644 --- a/cypress/e2e/browse-by-subject.cy.ts +++ b/cypress/e2e/browse-by-subject.cy.ts @@ -5,9 +5,9 @@ describe('Browse By Subject', () => { cy.visit('/browse/subject'); // Wait for to be visible - cy.get('ds-browse-by-metadata-page').should('be.visible'); + cy.get('ds-browse-by-metadata').should('be.visible'); // Analyze for accessibility - testA11y('ds-browse-by-metadata-page'); + testA11y('ds-browse-by-metadata'); }); }); diff --git a/cypress/e2e/browse-by-title.cy.ts b/cypress/e2e/browse-by-title.cy.ts index e4e027586a..09195c30df 100644 --- a/cypress/e2e/browse-by-title.cy.ts +++ b/cypress/e2e/browse-by-title.cy.ts @@ -5,9 +5,9 @@ describe('Browse By Title', () => { cy.visit('/browse/title'); // Wait for to be visible - cy.get('ds-browse-by-title-page').should('be.visible'); + cy.get('ds-browse-by-title').should('be.visible'); // Analyze for accessibility - testA11y('ds-browse-by-title-page'); + testA11y('ds-browse-by-title'); }); }); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index deb68f1ea9..1a0dc423b9 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { RouterModule, NoPreloading } from '@angular/router'; +import { NoPreloading, RouterModule } from '@angular/router'; import { AuthBlockingGuard } from './core/auth/auth-blocking.guard'; import { AuthenticatedGuard } from './core/auth/authenticated.guard'; @@ -40,6 +40,7 @@ import { import { ServerCheckGuard } from './core/server-check/server-check.guard'; import { MenuResolver } from './menu.resolver'; import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; +import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard'; @NgModule({ imports: [ @@ -94,7 +95,10 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone path: FORGOT_PASSWORD_PATH, loadChildren: () => import('./forgot-password/forgot-password.module') .then((m) => m.ForgotPasswordModule), - canActivate: [EndUserAgreementCurrentUserGuard] + canActivate: [ + ForgotPasswordCheckGuard, + EndUserAgreementCurrentUserGuard + ] }, { path: COMMUNITY_MODULE_PATH, 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/browse-by-date.component.spec.ts similarity index 88% rename from src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts rename to src/app/browse-by/browse-by-date/browse-by-date.component.spec.ts index e41d3a45b2..c5ef9020f1 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/browse-by-date.component.spec.ts @@ -1,4 +1,4 @@ -import { BrowseByDatePageComponent } from './browse-by-date-page.component'; +import { BrowseByDateComponent } from './browse-by-date.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; @@ -15,7 +15,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { Community } from '../../core/shared/community.model'; import { Item } from '../../core/shared/item.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; -import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec'; +import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec'; import { VarDirective } from '../../shared/utils/var.directive'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { PaginationService } from '../../core/pagination/pagination.service'; @@ -23,10 +23,11 @@ import { PaginationServiceStub } from '../../shared/testing/pagination-service.s import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; import { SortDirection } from '../../core/cache/models/sort-options.model'; +import { cold } from 'jasmine-marbles'; -describe('BrowseByDatePageComponent', () => { - let comp: BrowseByDatePageComponent; - let fixture: ComponentFixture; +describe('BrowseByDateComponent', () => { + let comp: BrowseByDateComponent; + let fixture: ComponentFixture; let route: ActivatedRoute; let paginationService; @@ -86,7 +87,7 @@ describe('BrowseByDatePageComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BrowseByDatePageComponent, EnumKeysPipe, VarDirective], + declarations: [BrowseByDateComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, @@ -101,7 +102,7 @@ describe('BrowseByDatePageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(BrowseByDatePageComponent); + fixture = TestBed.createComponent(BrowseByDateComponent); const browseService = fixture.debugElement.injector.get(BrowseService); spyOn(browseService, 'getFirstItemFor') // ok to expect the default browse as first param since we just need the mock items obtained via sort direction. @@ -112,9 +113,13 @@ describe('BrowseByDatePageComponent', () => { fixture.detectChanges(); }); - it('should initialize the list of items', () => { + it('should initialize the list of items', (done: DoneFn) => { + expect(comp.loading$).toBeObservable(cold('(a|)', { + a: false, + })); comp.items$.subscribe((result) => { expect(result.payload.page).toEqual([firstItem]); + done(); }); }); 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/browse-by-date.component.ts similarity index 72% rename from src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts rename to src/app/browse-by/browse-by-date/browse-by-date.component.ts index d0a75691a2..3485519929 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date/browse-by-date.component.ts @@ -1,10 +1,6 @@ import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; -import { - BrowseByMetadataPageComponent, - browseParamsToOptions, - getBrowseSearchOptions -} from '../browse-by-metadata-page/browse-by-metadata-page.component'; -import { combineLatest as observableCombineLatest } from 'rxjs'; +import { BrowseByMetadataComponent, browseParamsToOptions, getBrowseSearchOptions } from '../browse-by-metadata/browse-by-metadata.component'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; @@ -23,9 +19,9 @@ import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ - selector: 'ds-browse-by-date-page', - styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'], - templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html' + selector: 'ds-browse-by-date', + styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'], + templateUrl: '../browse-by-metadata/browse-by-metadata.component.html', }) /** * Component for browsing items by metadata definition of type 'date' @@ -33,21 +29,22 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; * An example would be 'dateissued' for 'dc.date.issued' */ @rendersBrowseBy(BrowseByDataType.Date) -export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent implements OnInit { +export class BrowseByDateComponent extends BrowseByMetadataComponent implements OnInit { /** * The default metadata keys to use for determining the lower limit of the StartsWith dropdown options */ defaultMetadataKeys = ['dc.date.issued']; - public constructor(protected route: ActivatedRoute, - protected browseService: BrowseService, - protected dsoService: DSpaceObjectDataService, - protected router: Router, - protected paginationService: PaginationService, - protected cdRef: ChangeDetectorRef, - @Inject(APP_CONFIG) public appConfig: AppConfig, - public dsoNameService: DSONameService, + public constructor( + protected route: ActivatedRoute, + protected browseService: BrowseService, + protected dsoService: DSpaceObjectDataService, + protected paginationService: PaginationService, + protected router: Router, + @Inject(APP_CONFIG) public appConfig: AppConfig, + public dsoNameService: DSONameService, + protected cdRef: ChangeDetectorRef, ) { super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService); } @@ -60,19 +57,17 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent imp 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, + observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.route.data, this.currentPagination$, this.currentSort$]).pipe( - map(([routeParams, queryParams, data, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort]; + map(([routeParams, queryParams, scope, data, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams, data), scope, currentPage, currentSort]; }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + ).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => { const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys; this.browseId = params.id || this.defaultBrowseId; this.startsWith = +params.startsWith || params.startsWith; - const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails); + const searchOptions = browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails); this.updatePageWithItems(searchOptions, this.value, undefined); - this.updateParent(params.scope); - this.updateLogo(); this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope); })); } @@ -88,12 +83,21 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent imp * @param scope The scope under which to fetch the earliest item for */ updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) { - const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC); - const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC); + const firstItemRD$: Observable> = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC); + const lastItemRD$: Observable> = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC); + this.loading$ = observableCombineLatest([ + firstItemRD$, + lastItemRD$, + ]).pipe( + map(([firstItemRD, lastItemRD]: [RemoteData, RemoteData]) => firstItemRD.isLoading || lastItemRD.isLoading) + ); this.subs.push( - observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => { - let lowerLimit: number = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit); - let upperLimit: number = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear()); + observableCombineLatest([ + firstItemRD$, + lastItemRD$, + ]).subscribe(([firstItemRD, lastItemRD]: [RemoteData, RemoteData]) => { + let lowerLimit: number = this.getLimit(firstItemRD, metadataKeys, this.appConfig.browseBy.defaultLowerLimit); + let upperLimit: number = this.getLimit(lastItemRD, metadataKeys, new Date().getUTCFullYear()); const options: number[] = []; const oneYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5; const fiveYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10; diff --git a/src/app/browse-by/browse-by-guard.spec.ts b/src/app/browse-by/browse-by-guard.spec.ts index c7d3e1e0c0..c3840470c6 100644 --- a/src/app/browse-by/browse-by-guard.spec.ts +++ b/src/app/browse-by/browse-by-guard.spec.ts @@ -1,17 +1,13 @@ import { first } from 'rxjs/operators'; import { BrowseByGuard } from './browse-by-guard'; -import { of as observableOf } from 'rxjs'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model'; -import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock'; -import { DSONameService } from '../core/breadcrumbs/dso-name.service'; import { RouterStub } from '../shared/testing/router.stub'; describe('BrowseByGuard', () => { describe('canActivate', () => { let guard: BrowseByGuard; - let dsoService: any; let translateService: any; let browseDefinitionService: any; let router: any; @@ -25,10 +21,6 @@ describe('BrowseByGuard', () => { const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] }); beforeEach(() => { - dsoService = { - findById: (dsoId: string) => observableOf({ payload: { name: name }, hasSucceeded: true }) - }; - translateService = { instant: () => field }; @@ -39,7 +31,7 @@ describe('BrowseByGuard', () => { router = new RouterStub() as any; - guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService, new DSONameServiceMock() as DSONameService, router); + guard = new BrowseByGuard(translateService, browseDefinitionService, router); }); it('should return true, and sets up the data correctly, with a scope and value', () => { @@ -48,6 +40,7 @@ describe('BrowseByGuard', () => { title: field, browseDefinition, }, + parent: null, params: { id, }, @@ -64,7 +57,7 @@ describe('BrowseByGuard', () => { title, id, browseDefinition, - collection: name, + scope, field, value: '"' + value + '"' }; @@ -97,7 +90,7 @@ describe('BrowseByGuard', () => { title, id, browseDefinition, - collection: name, + scope, field, value: '' }; @@ -108,12 +101,48 @@ describe('BrowseByGuard', () => { ); }); + it('should return true, and sets up the data correctly using the community/collection page id, with a scope and without value', () => { + const scopedNoValueRoute = { + data: { + title: field, + browseDefinition, + }, + parent: { + params: { + id: scope, + }, + }, + params: { + id, + }, + queryParams: { + }, + }; + + guard.canActivate(scopedNoValueRoute as any, undefined).pipe( + first(), + ).subscribe((canActivate) => { + const result = { + title, + id, + browseDefinition, + scope, + field, + value: '', + }; + expect(scopedNoValueRoute.data).toEqual(result); + expect(router.navigate).not.toHaveBeenCalled(); + expect(canActivate).toEqual(true); + }); + }); + it('should return true, and sets up the data correctly, without a scope and with a value', () => { const route = { data: { title: field, browseDefinition, }, + parent: null, params: { id, }, @@ -129,7 +158,7 @@ describe('BrowseByGuard', () => { title, id, browseDefinition, - collection: '', + scope: undefined, field, value: '"' + value + '"' }; @@ -147,6 +176,7 @@ describe('BrowseByGuard', () => { data: { title: field, }, + parent: null, params: { id, }, diff --git a/src/app/browse-by/browse-by-guard.ts b/src/app/browse-by/browse-by-guard.ts index dd87699a84..cca9a6a675 100644 --- a/src/app/browse-by/browse-by-guard.ts +++ b/src/app/browse-by/browse-by-guard.ts @@ -1,15 +1,12 @@ -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, Data } from '@angular/router'; import { Injectable } from '@angular/core'; -import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service'; import { hasNoValue, hasValue } from '../shared/empty.util'; import { map, switchMap } from 'rxjs/operators'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { TranslateService } from '@ngx-translate/core'; import { Observable, of as observableOf } from 'rxjs'; import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service'; import { BrowseDefinition } from '../core/shared/browse-definition.model'; -import { DSONameService } from '../core/breadcrumbs/dso-name.service'; -import { DSpaceObject } from '../core/shared/dspace-object.model'; import { RemoteData } from '../core/data/remote-data'; import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths'; @@ -19,11 +16,10 @@ import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths'; */ export class BrowseByGuard implements CanActivate { - constructor(protected dsoService: DSpaceObjectDataService, - protected translate: TranslateService, - protected browseDefinitionService: BrowseDefinitionDataService, - protected dsoNameService: DSONameService, - protected router: Router, + constructor( + protected translate: TranslateService, + protected browseDefinitionService: BrowseDefinitionDataService, + protected router: Router, ) { } @@ -39,25 +35,14 @@ export class BrowseByGuard implements CanActivate { } else { browseDefinition$ = observableOf(route.data.browseDefinition); } - const scope = route.queryParams.scope; + const scope = route.queryParams.scope ?? route.parent?.params.id; const value = route.queryParams.value; const metadataTranslated = this.translate.instant(`browse.metadata.${id}`); return browseDefinition$.pipe( switchMap((browseDefinition: BrowseDefinition | undefined) => { if (hasValue(browseDefinition)) { - if (hasValue(scope)) { - const dso$: Observable = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteDataPayload()); - return dso$.pipe( - map((dso: DSpaceObject) => { - const name = this.dsoNameService.getName(dso); - route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route); - return true; - }) - ); - } else { - route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route); - return observableOf(true); - } + route.data = this.createData(title, id, browseDefinition, metadataTranslated, value, route, scope); + return observableOf(true); } else { void this.router.navigate([PAGE_NOT_FOUND_PATH]); return observableOf(false); @@ -66,14 +51,14 @@ export class BrowseByGuard implements CanActivate { ); } - private createData(title, id, browseDefinition, collection, field, value, route) { + private createData(title: string, id: string, browseDefinition: BrowseDefinition, field: string, value: string, route: ActivatedRouteSnapshot, scope: string): Data { return Object.assign({}, route.data, { title: title, id: id, browseDefinition: browseDefinition, - collection: collection, field: field, - value: hasValue(value) ? `"${value}"` : '' + value: hasValue(value) ? `"${value}"` : '', + scope: 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 deleted file mode 100644 index cfc2cbe305..0000000000 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ /dev/null @@ -1,67 +0,0 @@ -
- - -
- -
- - - - - - - - - - - - - - - -
- -
- - -
- -
- -
- - -
-
- - - - -
-
-
-
-
diff --git a/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.html b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.html new file mode 100644 index 0000000000..52ef06206f --- /dev/null +++ b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.html @@ -0,0 +1,21 @@ +
+ +
diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.scss similarity index 100% rename from src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss rename to src/app/browse-by/browse-by-metadata/browse-by-metadata.component.scss 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/browse-by-metadata.component.spec.ts similarity index 91% rename from src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts rename to src/app/browse-by/browse-by-metadata/browse-by-metadata.component.spec.ts index a5beeb8a45..9fef2b1a35 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/browse-by-metadata.component.spec.ts @@ -1,8 +1,8 @@ import { - BrowseByMetadataPageComponent, + BrowseByMetadataComponent, browseParamsToOptions, getBrowseSearchOptions -} from './browse-by-metadata-page.component'; +} from './browse-by-metadata.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { BrowseService } from '../../core/browse/browse.service'; import { CommonModule } from '@angular/common'; @@ -30,10 +30,11 @@ import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { APP_CONFIG } from '../../../config/app-config.interface'; +import { cold } from 'jasmine-marbles'; -describe('BrowseByMetadataPageComponent', () => { - let comp: BrowseByMetadataPageComponent; - let fixture: ComponentFixture; +describe('BrowseByMetadataComponent', () => { + let comp: BrowseByMetadataComponent; + let fixture: ComponentFixture; let browseService: BrowseService; let route: ActivatedRoute; let paginationService; @@ -103,7 +104,7 @@ describe('BrowseByMetadataPageComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BrowseByMetadataPageComponent, EnumKeysPipe, VarDirective], + declarations: [BrowseByMetadataComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, @@ -117,7 +118,7 @@ describe('BrowseByMetadataPageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(BrowseByMetadataPageComponent); + fixture = TestBed.createComponent(BrowseByMetadataComponent); comp = fixture.componentInstance; fixture.detectChanges(); browseService = (comp as any).browseService; @@ -144,20 +145,18 @@ describe('BrowseByMetadataPageComponent', () => { route.params = observableOf(paramsWithValue); comp.ngOnInit(); - comp.updateParent('fake-scope'); - comp.updateLogo(); fixture.detectChanges(); }); - it('should fetch items', () => { + it('should fetch items', (done: DoneFn) => { + expect(comp.loading$).toBeObservable(cold('(a|)', { + a: false, + })); comp.items$.subscribe((result) => { expect(result.payload.page).toEqual(mockItems); + done(); }); }); - - it('should fetch the logo', () => { - expect(comp.logo$).toBeTruthy(); - }); }); describe('when calling browseParamsToOptions', () => { @@ -176,7 +175,7 @@ describe('BrowseByMetadataPageComponent', () => { field: 'fake-field', }; - result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author', comp.fetchThumbnails); + result = browseParamsToOptions(paramsScope, 'fake-scope', paginationOptions, sortOptions, 'author', comp.fetchThumbnails); }); it('should return BrowseEntrySearchOptions with the correct properties', () => { 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/browse-by-metadata.component.ts similarity index 77% rename from src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts rename to src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts index 8555fd3426..00cfedba88 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts @@ -1,5 +1,5 @@ -import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; -import { Component, Inject, OnInit, OnDestroy, Input } from '@angular/core'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs'; +import { Component, Inject, OnInit, OnDestroy, Input, OnChanges } from '@angular/core'; 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,14 +12,9 @@ import { Item } from '../../core/shared/item.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { getFirstSucceededRemoteData } from '../../core/shared/operators'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { filter, map, mergeMap } from 'rxjs/operators'; -import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { Collection } from '../../core/shared/collection.model'; -import { Community } from '../../core/shared/community.model'; +import { map } from 'rxjs/operators'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; @@ -29,9 +24,9 @@ import { Context } from '../../core/shared/context.model'; export const BBM_PAGINATION_ID = 'bbm'; @Component({ - selector: 'ds-browse-by-metadata-page', - styleUrls: ['./browse-by-metadata-page.component.scss'], - templateUrl: './browse-by-metadata-page.component.html' + selector: 'ds-browse-by-metadata', + styleUrls: ['./browse-by-metadata.component.scss'], + templateUrl: './browse-by-metadata.component.html', }) /** * Component for browsing (items) by metadata definition. @@ -40,7 +35,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ @rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { +export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy { /** * The optional context @@ -52,6 +47,18 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ @Input() browseByType: BrowseByDataType; + /** + * The ID of the {@link Community} or {@link Collection} of the scope to display + */ + @Input() scope: string; + + /** + * Display the h1 title in the section + */ + @Input() displayTitle = true; + + scope$: BehaviorSubject = new BehaviorSubject(undefined); + /** * The list of browse-entries to display */ @@ -62,16 +69,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ items$: Observable>>; - /** - * The current Community or Collection we're browsing metadata/items in - */ - parent$: Observable>; - - /** - * The logo of the current Community or Collection - */ - logo$: Observable>; - /** * The pagination config used to display the values */ @@ -112,7 +109,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { * The list of StartsWith options * Should be defined after ngOnInit is called! */ - startsWithOptions; + startsWithOptions: (string | number)[]; /** * The value we're browsing items for @@ -136,6 +133,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ fetchThumbnails: boolean; + /** + * Observable determining if the loading animation needs to be shown + */ + loading$ = observableOf(true); + public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, @@ -160,12 +162,12 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { 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.currentPagination$, this.currentSort$]).pipe( - map(([routeParams, queryParams, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; + observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.currentPagination$, this.currentSort$]).pipe( + map(([routeParams, queryParams, scope, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort]; }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { - this.browseId = params.id || this.defaultBrowseId; + ).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => { + this.browseId = params.id || this.defaultBrowseId; this.authority = params.authority; if (typeof params.value === 'string'){ @@ -183,18 +185,19 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { } if (isNotEmpty(this.value)) { - this.updatePageWithItems( - browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority); + this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority); } else { - this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false)); + this.updatePage(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, false)); } - this.updateParent(params.scope); - this.updateLogo(); })); this.updateStartsWithTextOptions(); } + ngOnChanges(): void { + this.scope$.next(this.scope); + } + /** * Update the StartsWith options with text values * It adds the value "0-9" as well as all letters from A to Z @@ -213,6 +216,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ updatePage(searchOptions: BrowseEntrySearchOptions) { this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions); + this.loading$ = this.browseEntries$.pipe( + map((browseEntriesRD: RemoteData>) => browseEntriesRD.isLoading), + ); this.items$ = undefined; } @@ -227,37 +233,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) { this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions); - } - - /** - * Update the parent Community or Collection using their scope - * @param scope The UUID of the Community or Collection to fetch - */ - updateParent(scope: string) { - if (hasValue(scope)) { - const linksToFollow = () => { - return [followLink('logo')]; - }; - this.parent$ = this.dsoService.findById(scope, - true, - true, - ...linksToFollow() as FollowLinkConfig[]).pipe( - getFirstSucceededRemoteData() - ); - } - } - - /** - * Update the parent Community or Collection logo - */ - updateLogo() { - if (hasValue(this.parent$)) { - this.logo$ = this.parent$.pipe( - map((rd: RemoteData) => rd.payload), - filter((collectionOrCommunity: Collection | Community) => hasValue(collectionOrCommunity.logo)), - mergeMap((collectionOrCommunity: Collection | Community) => collectionOrCommunity.logo) - ); - } + this.loading$ = this.items$.pipe( + map((itemsRD: RemoteData>) => itemsRD.isLoading), + ); } /** @@ -320,12 +298,14 @@ export function getBrowseSearchOptions(defaultBrowseId: string, /** * Function to transform query and url parameters into searchOptions used to fetch browse entries or items * @param params URL and query parameters + * @param scope The scope to show the results * @param paginationConfig Pagination configuration * @param sortConfig Sorting configuration * @param metadata Optional metadata definition to fetch browse entries/items for * @param fetchThumbnail Optional parameter for requesting thumbnail images */ export function browseParamsToOptions(params: any, + scope: string, paginationConfig: PaginationComponentOptions, sortConfig: SortOptions, metadata?: string, @@ -335,7 +315,7 @@ export function browseParamsToOptions(params: any, paginationConfig, sortConfig, params.startsWith, - params.scope, + scope, fetchThumbnail ); } diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.html b/src/app/browse-by/browse-by-page/browse-by-page.component.html index b7b109643b..6e9476e1e9 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.html +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.html @@ -1,2 +1,4 @@ - - +
+ + +
diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 0c200c3453..99b969417e 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -15,6 +15,10 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent< @Input() browseByType: BrowseByDataType; + @Input() displayTitle: boolean; + + @Input() scope: string; + protected inputNamesDependentForComponent: (keyof this & string)[] = [ 'context', 'browseByType', @@ -23,6 +27,8 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent< protected inputNames: (keyof this & string)[] = [ 'context', 'browseByType', + 'displayTitle', + 'scope', ]; public getComponent(): GenericConstructor { diff --git a/src/app/browse-by/browse-by-switcher/dynamic-component-loader.directive.ts b/src/app/browse-by/browse-by-switcher/dynamic-component-loader.directive.ts new file mode 100644 index 0000000000..8c77df1cdb --- /dev/null +++ b/src/app/browse-by/browse-by-switcher/dynamic-component-loader.directive.ts @@ -0,0 +1,16 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +/** + * Directive used as a hook to know where to inject the dynamic loaded component + */ +@Directive({ + selector: '[dsDynamicComponentLoader]' +}) +export class DynamicComponentLoaderDirective { + + constructor( + public viewContainerRef: ViewContainerRef, + ) { + } + +} diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html similarity index 70% rename from src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html rename to src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html index c24ca93403..3dd9b6b25a 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html +++ b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html @@ -1,5 +1,11 @@ -
-

{{ ('browse.taxonomy_' + vocabularyName + '.title') | translate }}

+
+

+ {{ ('browse.title') | translate:{ + field: 'browse.metadata.' + vocabularyName | translate, + startsWith: '', + value: '', + } }} +

{{ 'browse.taxonomy.button' | translate }} -
+
diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.scss similarity index 100% rename from src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss rename to src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.scss diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.spec.ts similarity index 68% rename from src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts rename to src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.spec.ts index c724017b1f..ac8dbff598 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts +++ b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component'; +import { BrowseByTaxonomyComponent } from './browse-by-taxonomy.component'; import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { TranslateModule } from '@ngx-translate/core'; import { NO_ERRORS_SCHEMA } from '@angular/core'; @@ -10,9 +9,9 @@ import { createDataWithBrowseDefinition } from '../browse-by-switcher/browse-by- import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; import { ThemeService } from '../../shared/theme-support/theme.service'; -describe('BrowseByTaxonomyPageComponent', () => { - let component: BrowseByTaxonomyPageComponent; - let fixture: ComponentFixture; +describe('BrowseByTaxonomyComponent', () => { + let component: BrowseByTaxonomyComponent; + let fixture: ComponentFixture; let themeService: ThemeService; let detail1: VocabularyEntryDetail; let detail2: VocabularyEntryDetail; @@ -29,7 +28,9 @@ describe('BrowseByTaxonomyPageComponent', () => { await TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot() ], - declarations: [ BrowseByTaxonomyPageComponent ], + declarations: [ + BrowseByTaxonomyComponent, + ], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: ThemeService, useValue: themeService }, @@ -40,8 +41,9 @@ describe('BrowseByTaxonomyPageComponent', () => { }); beforeEach(() => { - fixture = TestBed.createComponent(BrowseByTaxonomyPageComponent); + fixture = TestBed.createComponent(BrowseByTaxonomyComponent); component = fixture.componentInstance; + spyOn(component, 'updateQueryParams').and.callThrough(); fixture.detectChanges(); detail1 = new VocabularyEntryDetail(); detail2 = new VocabularyEntryDetail(); @@ -61,6 +63,7 @@ describe('BrowseByTaxonomyPageComponent', () => { expect(component.selectedItems).toContain(detail1); expect(component.selectedItems.length).toBe(1); expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] ); + expect(component.updateQueryParams).toHaveBeenCalled(); }); it('should handle select event with multiple selected items', () => { @@ -70,6 +73,7 @@ describe('BrowseByTaxonomyPageComponent', () => { expect(component.selectedItems).toContain(detail1, detail2); expect(component.selectedItems.length).toBe(2); expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] ); + expect(component.updateQueryParams).toHaveBeenCalled(); }); it('should handle deselect event', () => { @@ -82,6 +86,33 @@ describe('BrowseByTaxonomyPageComponent', () => { expect(component.selectedItems).toContain(detail2); expect(component.selectedItems.length).toBe(1); expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] ); + expect(component.updateQueryParams).toHaveBeenCalled(); + }); + + describe('updateQueryParams', () => { + beforeEach(() => { + component.facetType = 'subject'; + component.filterValues = ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals']; + }); + + it('should update the queryParams with the selected filterValues', () => { + component.updateQueryParams(); + + expect(component.queryParams).toEqual({ + 'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'], + }); + }); + + it('should include the scope if present', () => { + component.scope = '67f849f1-2499-4872-8c61-9e2b47d71068'; + + component.updateQueryParams(); + + expect(component.queryParams).toEqual({ + 'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'], + 'scope': '67f849f1-2499-4872-8c61-9e2b47d71068', + }); + }); }); afterEach(() => { diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.ts similarity index 69% rename from src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts rename to src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.ts index fb2f28c8c5..d23a3dd15c 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.ts @@ -1,25 +1,26 @@ -import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Component, OnInit, OnChanges, OnDestroy, Input } from '@angular/core'; import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; -import { ActivatedRoute } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; +import { ActivatedRoute, Params } from '@angular/router'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; import { Context } from '../../core/shared/context.model'; +import { hasValue } from '../../shared/empty.util'; @Component({ - selector: 'ds-browse-by-taxonomy-page', - templateUrl: './browse-by-taxonomy-page.component.html', - styleUrls: ['./browse-by-taxonomy-page.component.scss'] + selector: 'ds-browse-by-taxonomy', + templateUrl: './browse-by-taxonomy.component.html', + styleUrls: ['./browse-by-taxonomy.component.scss'], }) /** * Component for browsing items by metadata in a hierarchical controlled vocabulary */ @rendersBrowseBy(BrowseByDataType.Hierarchy) -export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { +export class BrowseByTaxonomyComponent implements OnInit, OnChanges, OnDestroy { /** * The optional context @@ -31,6 +32,18 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { */ @Input() browseByType: BrowseByDataType; + /** + * The ID of the {@link Community} or {@link Collection} of the scope to display + */ + @Input() scope: string; + + /** + * Display the h1 title in the section + */ + @Input() displayTitle = true; + + scope$: BehaviorSubject = new BehaviorSubject(undefined); + /** * The {@link VocabularyOptions} object */ @@ -59,7 +72,7 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { /** * The parameters used in the URL */ - queryParams: any; + queryParams: Params; /** * Resolved browse-by definition @@ -87,6 +100,13 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { this.vocabularyName = browseDefinition.vocabulary; this.vocabularyOptions = { name: this.vocabularyName, closed: true }; })); + this.subs.push(this.scope$.subscribe(() => { + this.updateQueryParams(); + })); + } + + ngOnChanges(): void { + this.scope$.next(this.scope); } /** @@ -96,9 +116,9 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { * @param detail VocabularyEntryDetail to be added */ onSelect(detail: VocabularyEntryDetail): void { - this.selectedItems.push(detail); - this.filterValues = this.selectedItems - .map((item: VocabularyEntryDetail) => `${item.value},equals`); + this.selectedItems.push(detail); + this.filterValues = this.selectedItems + .map((item: VocabularyEntryDetail) => `${item.value},equals`); this.updateQueryParams(); } @@ -108,18 +128,25 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { * @param detail VocabularyEntryDetail to be removed */ onDeselect(detail: VocabularyEntryDetail): void { - this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry.id !== detail.id; }); - this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; }); + this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { + return entry.id !== detail.id; + }); + this.filterValues = this.filterValues.filter((value: string) => { + return value !== `${detail.value},equals`; + }); this.updateQueryParams(); } /** * Updates queryParams based on the current facetType and filterValues. */ - private updateQueryParams(): void { + updateQueryParams(): void { this.queryParams = { ['f.' + this.facetType]: this.filterValues }; + if (hasValue(this.scope)) { + this.queryParams.scope = this.scope; + } } ngOnDestroy(): void { 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 deleted file mode 100644 index 1e18429fa0..0000000000 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { combineLatest as observableCombineLatest } from 'rxjs'; -import { Component, Inject, OnInit } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { - BrowseByMetadataPageComponent, - browseParamsToOptions, getBrowseSearchOptions -} from '../browse-by-metadata-page/browse-by-metadata-page.component'; -import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; -import { BrowseService } from '../../core/browse/browse.service'; -import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { map } from 'rxjs/operators'; -import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; -import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; - -@Component({ - selector: 'ds-browse-by-title-page', - styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'], - templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html' -}) -/** - * Component for browsing items by title (dc.title) - */ -@rendersBrowseBy(BrowseByDataType.Title) -export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent implements OnInit { - - public constructor(protected route: ActivatedRoute, - protected browseService: BrowseService, - protected dsoService: DSpaceObjectDataService, - protected paginationService: PaginationService, - protected router: Router, - @Inject(APP_CONFIG) public appConfig: AppConfig, - public dsoNameService: DSONameService, - ) { - super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService); - } - - ngOnInit(): void { - const sortConfig = new SortOptions('dc.title', SortDirection.ASC); - // include the thumbnail configuration in browse search options - this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails)); - 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.currentPagination$, this.currentSort$]).pipe( - map(([routeParams, queryParams, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; - }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { - this.startsWith = +params.startsWith || params.startsWith; - this.browseId = params.id || this.defaultBrowseId; - this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined); - this.updateParent(params.scope); - this.updateLogo(); - })); - this.updateStartsWithTextOptions(); - } - -} 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/browse-by-title.component.spec.ts similarity index 87% rename from src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts rename to src/app/browse-by/browse-by-title/browse-by-title.component.spec.ts index e32c0ac430..54394087ec 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/browse-by-title.component.spec.ts @@ -9,8 +9,8 @@ import { TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec'; -import { BrowseByTitlePageComponent } from './browse-by-title-page.component'; +import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec'; +import { BrowseByTitleComponent } from './browse-by-title.component'; import { ItemDataService } from '../../core/data/item-data.service'; import { Community } from '../../core/shared/community.model'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; @@ -24,9 +24,9 @@ import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; -describe('BrowseByTitlePageComponent', () => { - let comp: BrowseByTitlePageComponent; - let fixture: ComponentFixture; +describe('BrowseByTitleComponent', () => { + let comp: BrowseByTitleComponent; + let fixture: ComponentFixture; let itemDataService: ItemDataService; let route: ActivatedRoute; @@ -71,7 +71,7 @@ describe('BrowseByTitlePageComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BrowseByTitlePageComponent, EnumKeysPipe, VarDirective], + declarations: [BrowseByTitleComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, @@ -85,7 +85,7 @@ describe('BrowseByTitlePageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(BrowseByTitlePageComponent); + fixture = TestBed.createComponent(BrowseByTitleComponent); comp = fixture.componentInstance; fixture.detectChanges(); itemDataService = (comp as any).itemDataService; diff --git a/src/app/browse-by/browse-by-title/browse-by-title.component.ts b/src/app/browse-by/browse-by-title/browse-by-title.component.ts new file mode 100644 index 0000000000..7b603af48b --- /dev/null +++ b/src/app/browse-by/browse-by-title/browse-by-title.component.ts @@ -0,0 +1,44 @@ +import { combineLatest as observableCombineLatest } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; +import { Params } from '@angular/router'; +import { + BrowseByMetadataComponent, + browseParamsToOptions, getBrowseSearchOptions +} from '../browse-by-metadata/browse-by-metadata.component'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; + +@Component({ + selector: 'ds-browse-by-title', + styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'], + templateUrl: '../browse-by-metadata/browse-by-metadata.component.html' +}) +/** + * Component for browsing items by title (dc.title) + */ +@rendersBrowseBy(BrowseByDataType.Title) +export class BrowseByTitleComponent extends BrowseByMetadataComponent implements OnInit { + + ngOnInit(): void { + const sortConfig = new SortOptions('dc.title', SortDirection.ASC); + // include the thumbnail configuration in browse search options + this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails)); + 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.scope$, this.currentPagination$, this.currentSort$]).pipe( + map(([routeParams, queryParams, scope, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort]; + }) + ).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => { + this.startsWith = +params.startsWith || params.startsWith; + this.browseId = params.id || this.defaultBrowseId; + this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined); + })); + this.updateStartsWithTextOptions(); + } + +} diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index 5772063742..b1263595b3 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -1,11 +1,10 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { BrowseByTitlePageComponent } from './browse-by-title-page/browse-by-title-page.component'; -import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-by-metadata-page.component'; -import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component'; +import { BrowseByTitleComponent } from './browse-by-title/browse-by-title.component'; +import { BrowseByMetadataComponent } from './browse-by-metadata/browse-by-metadata.component'; +import { BrowseByDateComponent } from './browse-by-date/browse-by-date.component'; import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component'; -import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; -import { ComcolModule } from '../shared/comcol/comcol.module'; +import { BrowseByTaxonomyComponent } from './browse-by-taxonomy/browse-by-taxonomy.component'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { FormModule } from '../shared/form/form.module'; @@ -17,17 +16,16 @@ const DECLARATIONS = [ const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator - BrowseByTitlePageComponent, - BrowseByMetadataPageComponent, - BrowseByDatePageComponent, - BrowseByTaxonomyPageComponent, + BrowseByTitleComponent, + BrowseByMetadataComponent, + BrowseByDateComponent, + BrowseByTaxonomyComponent, ]; @NgModule({ imports: [ SharedBrowseByModule, CommonModule, - ComcolModule, DsoPageModule, FormModule, SharedModule, diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index 9dc25b778e..5ddef6ca68 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -22,6 +22,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { ThemedCollectionPageComponent } from './themed-collection-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; +import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component'; +import { BrowseByGuard } from '../browse-by/browse-by-guard'; +import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver'; +import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component'; @NgModule({ imports: [ @@ -65,7 +69,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; { path: '', component: ThemedCollectionPageComponent, - pathMatch: 'full', + children: [ + { + path: '', + pathMatch: 'full', + component: CollectionRecentlyAddedComponent, + }, + { + path: 'browse/:id', + pathMatch: 'full', + component: ComcolBrowseByComponent, + canActivate: [BrowseByGuard], + resolve: { + breadcrumb: BrowseByI18nBreadcrumbResolver, + }, + data: { breadcrumbKey: 'browse.metadata' }, + }, + ], } ], data: { diff --git a/src/app/collection-page/collection-page.component.html b/src/app/collection-page/collection-page.component.html index 9a5414952f..21cc94af68 100644 --- a/src/app/collection-page/collection-page.component.html +++ b/src/app/collection-page/collection-page.component.html @@ -1,78 +1,61 @@
-
-
-
- -
-
- - - - - - +
+
+
+ +
+
+ + + + + + - - - - - - - - - -
- -
-
- - - + + + + + + + + + +
+ +
+
+ + + - -
-

{{'collection.page.browse.recent.head' | translate}}

- - -
- - - -
-
-
+ + +
+ [content]="collection.copyrightText" + [hasInnerHtml]="true">
- - + +
diff --git a/src/app/collection-page/collection-page.component.ts b/src/app/collection-page/collection-page.component.ts index 16704cef52..6b3cbbe64e 100644 --- a/src/app/collection-page/collection-page.component.ts +++ b/src/app/collection-page/collection-page.component.ts @@ -1,36 +1,21 @@ -import { ChangeDetectionStrategy, Component, OnInit, Inject } from '@angular/core'; +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 } from 'rxjs/operators'; -import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model'; -import { SearchService } from '../core/shared/search/search.service'; -import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { CollectionDataService } from '../core/data/collection-data.service'; -import { PaginatedList } from '../core/data/paginated-list.model'; +import { Observable } from 'rxjs'; +import { filter, map, mergeMap, take } from 'rxjs/operators'; +import { SortOptions } from '../core/cache/models/sort-options.model'; import { RemoteData } from '../core/data/remote-data'; import { Bitstream } from '../core/shared/bitstream.model'; - import { Collection } from '../core/shared/collection.model'; -import { DSpaceObjectType } from '../core/shared/dspace-object-type.model'; -import { Item } from '../core/shared/item.model'; -import { - getAllSucceededRemoteDataPayload, - getFirstSucceededRemoteData, - toDSpaceObjectListRD -} from '../core/shared/operators'; - +import { getAllSucceededRemoteDataPayload } from '../core/shared/operators'; import { fadeIn, fadeInOut } from '../shared/animations/fade'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { AuthService } from '../core/auth/auth.service'; -import { PaginationService } from '../core/pagination/pagination.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { getCollectionPageRoute } from './collection-page-routing-paths'; import { redirectOn4xx } from '../core/shared/authorized.operators'; -import { BROWSE_LINKS_TO_FOLLOW } from '../core/browse/browse.service'; import { DSONameService } from '../core/breadcrumbs/dso-name.service'; -import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface'; @Component({ selector: 'ds-collection-page', @@ -44,14 +29,9 @@ import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface' }) export class CollectionPageComponent implements OnInit { collectionRD$: Observable>; - itemRD$: Observable>>; logoRD$: Observable>; paginationConfig: PaginationComponentOptions; sortConfig: SortOptions; - private paginationChanges$: Subject<{ - paginationConfig: PaginationComponentOptions, - sortConfig: SortOptions - }>; /** * Whether the current user is a Community admin @@ -64,23 +44,12 @@ export class CollectionPageComponent implements OnInit { collectionPageRoute$: Observable; constructor( - private collectionDataService: CollectionDataService, - private searchService: SearchService, - private route: ActivatedRoute, - private router: Router, - private authService: AuthService, - private paginationService: PaginationService, - private authorizationDataService: AuthorizationDataService, + protected route: ActivatedRoute, + protected router: Router, + protected authService: AuthService, + protected authorizationDataService: AuthorizationDataService, public dsoNameService: DSONameService, - @Inject(APP_CONFIG) public appConfig: AppConfig, ) { - this.paginationConfig = Object.assign(new PaginationComponentOptions(), { - id: 'cp', - currentPage: 1, - pageSize: this.appConfig.browseBy.pageSize, - }); - - this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC); } ngOnInit(): void { @@ -96,33 +65,6 @@ export class CollectionPageComponent implements OnInit { ); this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin); - this.paginationChanges$ = new BehaviorSubject({ - paginationConfig: this.paginationConfig, - sortConfig: this.sortConfig - }); - - 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: currentPagination, - sort: currentSort, - dsoTypes: [DSpaceObjectType.ITEM] - }), null, true, true, ...BROWSE_LINKS_TO_FOLLOW) - .pipe(toDSpaceObjectListRD()) as Observable>>; - }), - startWith(undefined) // Make sure switching pages shows loading component - ) - ) - ); - this.collectionPageRoute$ = this.collectionRD$.pipe( getAllSucceededRemoteDataPayload(), map((collection) => getCollectionPageRoute(collection.id)) @@ -133,9 +75,5 @@ export class CollectionPageComponent implements OnInit { return isNotEmpty(object); } - ngOnDestroy(): void { - this.paginationService.clearPagination(this.paginationConfig.id); - } - } diff --git a/src/app/collection-page/collection-page.module.ts b/src/app/collection-page/collection-page.module.ts index 6bcefed2b7..8782be0a45 100644 --- a/src/app/collection-page/collection-page.module.ts +++ b/src/app/collection-page/collection-page.module.ts @@ -18,6 +18,19 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen import { ComcolModule } from '../shared/comcol/comcol.module'; import { DsoSharedModule } from '../dso-shared/dso-shared.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; +import { BrowseByPageModule } from '../browse-by/browse-by-page.module'; +import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component'; + +const DECLARATIONS = [ + CollectionPageComponent, + ThemedCollectionPageComponent, + CreateCollectionPageComponent, + DeleteCollectionPageComponent, + EditItemTemplatePageComponent, + ThemedEditItemTemplatePageComponent, + CollectionItemMapperComponent, + CollectionRecentlyAddedComponent, +]; @NgModule({ imports: [ @@ -30,15 +43,10 @@ import { DsoPageModule } from '../shared/dso-page/dso-page.module'; ComcolModule, DsoSharedModule, DsoPageModule, + BrowseByPageModule, ], declarations: [ - CollectionPageComponent, - ThemedCollectionPageComponent, - CreateCollectionPageComponent, - DeleteCollectionPageComponent, - EditItemTemplatePageComponent, - ThemedEditItemTemplatePageComponent, - CollectionItemMapperComponent + ...DECLARATIONS, ], providers: [ SearchService, diff --git a/src/app/collection-page/sections/recently-added/collection-recently-added.component.html b/src/app/collection-page/sections/recently-added/collection-recently-added.component.html new file mode 100644 index 0000000000..002b8cceda --- /dev/null +++ b/src/app/collection-page/sections/recently-added/collection-recently-added.component.html @@ -0,0 +1,18 @@ + +
+

{{'collection.page.browse.recent.head' | translate}}

+ + +
+ + + +
diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss b/src/app/collection-page/sections/recently-added/collection-recently-added.component.scss similarity index 100% rename from src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss rename to src/app/collection-page/sections/recently-added/collection-recently-added.component.scss diff --git a/src/app/collection-page/sections/recently-added/collection-recently-added.component.spec.ts b/src/app/collection-page/sections/recently-added/collection-recently-added.component.spec.ts new file mode 100644 index 0000000000..4acc24e3f5 --- /dev/null +++ b/src/app/collection-page/sections/recently-added/collection-recently-added.component.spec.ts @@ -0,0 +1,53 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CollectionRecentlyAddedComponent } from './collection-recently-added.component'; +import { APP_CONFIG } from '../../../../config/app-config.interface'; +import { environment } from '../../../../environments/environment.test'; +import { ActivatedRoute } from '@angular/router'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { SearchServiceStub } from '../../../shared/testing/search-service.stub'; +import { SearchService } from '../../../core/shared/search/search.service'; +import { VarDirective } from '../../../shared/utils/var.directive'; +import { TranslateModule } from '@ngx-translate/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; + +describe('CollectionRecentlyAddedComponent', () => { + let component: CollectionRecentlyAddedComponent; + let fixture: ComponentFixture; + + let activatedRoute: ActivatedRouteStub; + let paginationService: PaginationServiceStub; + let searchService: SearchServiceStub; + + beforeEach(async () => { + activatedRoute = new ActivatedRouteStub(); + paginationService = new PaginationServiceStub(); + searchService = new SearchServiceStub(); + + await TestBed.configureTestingModule({ + declarations: [ + CollectionRecentlyAddedComponent, + VarDirective, + ], + imports: [ + TranslateModule.forRoot(), + ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: APP_CONFIG, useValue: environment }, + { provide: PaginationService, useValue: paginationService }, + { provide: SearchService, useValue: SearchServiceStub }, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(CollectionRecentlyAddedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/collection-page/sections/recently-added/collection-recently-added.component.ts b/src/app/collection-page/sections/recently-added/collection-recently-added.component.ts new file mode 100644 index 0000000000..65af77a63b --- /dev/null +++ b/src/app/collection-page/sections/recently-added/collection-recently-added.component.ts @@ -0,0 +1,82 @@ +import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; +import { Observable, combineLatest as observableCombineLatest } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { Item } from '../../../core/shared/item.model'; +import { switchMap, map, startWith, take } from 'rxjs/operators'; +import { getFirstSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators'; +import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model'; +import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; +import { BROWSE_LINKS_TO_FOLLOW } from '../../../core/browse/browse.service'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortOptions, SortDirection } from '../../../core/cache/models/sort-options.model'; +import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface'; +import { SearchService } from '../../../core/shared/search/search.service'; +import { Collection } from '../../../core/shared/collection.model'; +import { ActivatedRoute, Data } from '@angular/router'; +import { fadeIn } from '../../../shared/animations/fade'; + +@Component({ + selector: 'ds-collection-recently-added', + templateUrl: './collection-recently-added.component.html', + styleUrls: ['./collection-recently-added.component.scss'], + animations: [fadeIn], +}) +export class CollectionRecentlyAddedComponent implements OnInit, OnDestroy { + + paginationConfig: PaginationComponentOptions; + + sortConfig: SortOptions; + + collectionRD$: Observable>; + + itemRD$: Observable>>; + + constructor( + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected paginationService: PaginationService, + protected route: ActivatedRoute, + protected searchService: SearchService, + ) { + this.paginationConfig = Object.assign(new PaginationComponentOptions(), { + id: 'cp', + currentPage: 1, + pageSize: this.appConfig.browseBy.pageSize, + }); + + this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC); + } + + ngOnInit(): void { + this.collectionRD$ = this.route.data.pipe( + map((data: Data) => data.dso as RemoteData), + take(1), + ); + + this.itemRD$ = observableCombineLatest([ + this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig), + this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig), + ]).pipe( + switchMap(([currentPagination, currentSort]: [PaginationComponentOptions, SortOptions]) => this.collectionRD$.pipe( + getFirstSucceededRemoteData(), + map((rd: RemoteData) => rd.payload.id), + switchMap((id: string) => this.searchService.search( + new PaginatedSearchOptions({ + scope: id, + pagination: currentPagination, + sort: currentSort, + dsoTypes: [DSpaceObjectType.ITEM] + }), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe( + toDSpaceObjectListRD() + ) as Observable>>), + startWith(undefined), // Make sure switching pages shows loading component + )), + ); + } + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.paginationConfig.id); + } + +} diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index c37f8832f8..5ca544bb54 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -15,6 +15,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { ThemedCommunityPageComponent } from './themed-community-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; +import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component'; +import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver'; +import { BrowseByGuard } from '../browse-by/browse-by-guard'; +import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component'; @NgModule({ imports: [ @@ -48,7 +52,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; { path: '', component: ThemedCommunityPageComponent, - pathMatch: 'full', + children: [ + { + path: '', + pathMatch: 'full', + component: SubComColSectionComponent, + }, + { + path: 'browse/:id', + pathMatch: 'full', + component: ComcolBrowseByComponent, + canActivate: [BrowseByGuard], + resolve: { + breadcrumb: BrowseByI18nBreadcrumbResolver, + }, + data: { breadcrumbKey: 'browse.metadata' }, + }, + ], } ], data: { diff --git a/src/app/community-page/community-page.component.html b/src/app/community-page/community-page.component.html index 194fb747d0..b3e577af7d 100644 --- a/src/app/community-page/community-page.component.html +++ b/src/app/community-page/community-page.component.html @@ -17,7 +17,7 @@ + [title]="'community.page.news'"> @@ -28,10 +28,9 @@ - - + -