Merge branch 'DSpace:main' into main

This commit is contained in:
jeffmorin
2024-02-15 09:00:53 -05:00
committed by GitHub
111 changed files with 1591 additions and 887 deletions

View File

@@ -5,9 +5,9 @@ describe('Browse By Author', () => {
cy.visit('/browse/author');
// Wait for <ds-browse-by-metadata-page> to be visible
cy.get('ds-browse-by-metadata-page').should('be.visible');
cy.get('ds-browse-by-metadata').should('be.visible');
// Analyze <ds-browse-by-metadata-page> for accessibility
testA11y('ds-browse-by-metadata-page');
testA11y('ds-browse-by-metadata');
});
});

View File

@@ -5,9 +5,9 @@ describe('Browse By Date Issued', () => {
cy.visit('/browse/dateissued');
// Wait for <ds-browse-by-date-page> to be visible
cy.get('ds-browse-by-date-page').should('be.visible');
cy.get('ds-browse-by-date').should('be.visible');
// Analyze <ds-browse-by-date-page> for accessibility
testA11y('ds-browse-by-date-page');
testA11y('ds-browse-by-date');
});
});

View File

@@ -5,9 +5,9 @@ describe('Browse By Subject', () => {
cy.visit('/browse/subject');
// Wait for <ds-browse-by-metadata-page> to be visible
cy.get('ds-browse-by-metadata-page').should('be.visible');
cy.get('ds-browse-by-metadata').should('be.visible');
// Analyze <ds-browse-by-metadata-page> for accessibility
testA11y('ds-browse-by-metadata-page');
testA11y('ds-browse-by-metadata');
});
});

View File

@@ -5,9 +5,9 @@ describe('Browse By Title', () => {
cy.visit('/browse/title');
// Wait for <ds-browse-by-title-page> to be visible
cy.get('ds-browse-by-title-page').should('be.visible');
cy.get('ds-browse-by-title').should('be.visible');
// Analyze <ds-browse-by-title-page> for accessibility
testA11y('ds-browse-by-title-page');
testA11y('ds-browse-by-title');
});
});

View File

@@ -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,

View File

@@ -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<BrowseByDatePageComponent>;
describe('BrowseByDateComponent', () => {
let comp: BrowseByDateComponent;
let fixture: ComponentFixture<BrowseByDateComponent>;
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();
});
});

View File

@@ -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,
public constructor(
protected route: ActivatedRoute,
protected browseService: BrowseService,
protected dsoService: DSpaceObjectDataService,
protected router: Router,
protected paginationService: PaginationService,
protected cdRef: ChangeDetectorRef,
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<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
const lastItemRD$: Observable<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
this.loading$ = observableCombineLatest([
firstItemRD$,
lastItemRD$,
]).pipe(
map(([firstItemRD, lastItemRD]: [RemoteData<Item>, RemoteData<Item>]) => 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<Item>, RemoteData<Item>]) => {
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;

View File

@@ -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,
},

View File

@@ -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,10 +16,9 @@ import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
*/
export class BrowseByGuard implements CanActivate {
constructor(protected dsoService: DSpaceObjectDataService,
constructor(
protected translate: TranslateService,
protected browseDefinitionService: BrowseDefinitionDataService,
protected dsoNameService: DSONameService,
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<DSpaceObject> = 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);
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,
});
}
}

View File

@@ -1,67 +0,0 @@
<div class="container">
<ng-container *ngVar="(parent$ | async) as parent">
<ng-container *ngIf="parent?.payload as parentContext">
<div class="d-flex flex-row border-bottom mb-4 pb-4">
<header class="comcol-header mr-auto">
<!-- Parent Name -->
<ds-comcol-page-header [name]="dsoNameService.getName(parentContext)">
</ds-comcol-page-header>
<!-- Collection logo -->
<ds-comcol-page-logo *ngIf="logo$"
[logo]="(logo$ | async)?.payload"
[alternateText]="'Community or Collection Logo'">
</ds-comcol-page-logo>
<!-- Handle -->
<ds-themed-comcol-page-handle
[content]="parentContext.handle"
[title]="parentContext.type+'.page.handle'" >
</ds-themed-comcol-page-handle>
<!-- Introductory text -->
<ds-comcol-page-content [content]="parentContext.introductoryText" [hasInnerHtml]="true">
</ds-comcol-page-content>
<!-- News -->
<ds-comcol-page-content [content]="parentContext.sidebarText" [hasInnerHtml]="true" [title]="'community.page.news'">
</ds-comcol-page-content>
</header>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<!-- Browse-By Links -->
<ds-themed-comcol-page-browse-by [id]="parentContext.id" [contentType]="parentContext.type"></ds-themed-comcol-page-browse-by>
</ng-container></ng-container>
<section class="comcol-page-browse-section">
<div class="browse-by-metadata w-100">
<ds-themed-browse-by *ngIf="startsWithOptions" class="col-xs-12 w-100"
title="{{'browse.title' | translate:
{
collection: dsoNameService.getName((parent$ | async)?.payload),
field: 'browse.metadata.' + browseId | translate,
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '&quot;' + startsWith + '&quot;' }) : '',
value: (value)? '&quot;' + value + '&quot;': ''
} }}"
parentname="{{dsoNameService.getName((parent$ | async)?.payload)}}"
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
[paginationConfig]="(currentPagination$ |async)"
[sortConfig]="(currentSort$ |async)"
[type]="startsWithType"
[startsWithOptions]="startsWithOptions"
(prev)="goPrev()"
(next)="goNext()">
</ds-themed-browse-by>
<ds-themed-loading *ngIf="!startsWithOptions" message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
</div>
</section>
<ng-container *ngVar="(parent$ | async) as parent">
<ng-container *ngIf="parent?.payload as parentContext">
<footer *ngIf="parentContext.copyrightText" class="border-top my-5 pt-4">
<div >
<!-- Copyright -->
<ds-comcol-page-content [content]="parentContext.copyrightText" [hasInnerHtml]="true">
</ds-comcol-page-content>
</div>
</footer>
</ng-container>
</ng-container>
</div>

View File

@@ -0,0 +1,21 @@
<section class="comcol-page-browse-section">
<div class="browse-by-metadata w-100">
<ds-themed-browse-by *ngIf="!(loading$ | async)" class="col-xs-12 w-100"
title="{{'browse.title' | translate:{
field: 'browse.metadata.' + browseId | translate,
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '&quot;' + startsWith + '&quot;' }) : '',
value: (value)? '&quot;' + value + '&quot;': ''
} }}"
[displayTitle]="displayTitle"
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
[paginationConfig]="(currentPagination$ |async)"
[sortConfig]="(currentSort$ |async)"
[type]="startsWithType"
[startsWithOptions]="startsWithOptions"
(prev)="goPrev()"
(next)="goNext()">
</ds-themed-browse-by>
<ds-themed-loading *ngIf="loading$ | async"
message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
</div>
</section>

View File

@@ -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<BrowseByMetadataPageComponent>;
describe('BrowseByMetadataComponent', () => {
let comp: BrowseByMetadataComponent;
let fixture: ComponentFixture<BrowseByMetadataComponent>;
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', () => {

View File

@@ -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<string> = new BehaviorSubject(undefined);
/**
* The list of browse-entries to display
*/
@@ -62,16 +69,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
*/
items$: Observable<RemoteData<PaginatedList<Item>>>;
/**
* The current Community or Collection we're browsing metadata/items in
*/
parent$: Observable<RemoteData<DSpaceObject>>;
/**
* The logo of the current Community or Collection
*/
logo$: Observable<RemoteData<Bitstream>>;
/**
* 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,11 +162,11 @@ 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]) => {
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
this.browseId = params.id || this.defaultBrowseId;
this.authority = params.authority;
@@ -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<PaginatedList<BrowseEntry>>) => browseEntriesRD.isLoading),
);
this.items$ = undefined;
}
@@ -227,38 +233,10 @@ 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<DSpaceObject>[]).pipe(
getFirstSucceededRemoteData()
this.loading$ = this.items$.pipe(
map((itemsRD: RemoteData<PaginatedList<Item>>) => itemsRD.isLoading),
);
}
}
/**
* Update the parent Community or Collection logo
*/
updateLogo() {
if (hasValue(this.parent$)) {
this.logo$ = this.parent$.pipe(
map((rd: RemoteData<Collection | Community>) => rd.payload),
filter((collectionOrCommunity: Collection | Community) => hasValue(collectionOrCommunity.logo)),
mergeMap((collectionOrCommunity: Collection | Community) => collectionOrCommunity.logo)
);
}
}
/**
* Navigate to the previous page
@@ -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
);
}

View File

@@ -1,2 +1,4 @@
<ds-browse-by-switcher [browseByType]="browseByType$ | async">
</ds-browse-by-switcher>
<div class="container">
<ds-browse-by-switcher [browseByType]="browseByType$ | async">
</ds-browse-by-switcher>
</div>

View File

@@ -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<Component> {

View File

@@ -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,
) {
}
}

View File

@@ -1,5 +1,11 @@
<div class="container">
<h1>{{ ('browse.taxonomy_' + vocabularyName + '.title') | translate }}</h1>
<section>
<h1 *ngIf="displayTitle">
{{ ('browse.title') | translate:{
field: 'browse.metadata.' + vocabularyName | translate,
startsWith: '',
value: '',
} }}
</h1>
<div class="mb-3">
<ds-vocabulary-treeview [vocabularyOptions]=vocabularyOptions
[multiSelect]="true"
@@ -12,4 +18,4 @@
[queryParams]="queryParams"
[queryParamsHandling]="'merge'">
{{ 'browse.taxonomy.button' | translate }}</a>
</div>
</section>

View File

@@ -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<BrowseByTaxonomyPageComponent>;
describe('BrowseByTaxonomyComponent', () => {
let component: BrowseByTaxonomyComponent;
let fixture: ComponentFixture<BrowseByTaxonomyComponent>;
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(() => {

View File

@@ -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<string> = 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);
}
/**
@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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<BrowseByTitlePageComponent>;
describe('BrowseByTitleComponent', () => {
let comp: BrowseByTitleComponent;
let fixture: ComponentFixture<BrowseByTitleComponent>;
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;

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -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,
children: [
{
path: '',
pathMatch: 'full',
component: CollectionRecentlyAddedComponent,
},
{
path: 'browse/:id',
pathMatch: 'full',
component: ComcolBrowseByComponent,
canActivate: [BrowseByGuard],
resolve: {
breadcrumb: BrowseByI18nBreadcrumbResolver,
},
data: { breadcrumbKey: 'browse.metadata' },
},
],
}
],
data: {

View File

@@ -19,7 +19,7 @@
<!-- Handle -->
<ds-themed-comcol-page-handle
[content]="collection.handle"
[title]="'collection.page.handle'" >
[title]="'collection.page.handle'">
</ds-themed-comcol-page-handle>
<!-- Introductory text -->
<ds-comcol-page-content
@@ -42,24 +42,7 @@
[contentType]="collection.type">
</ds-themed-comcol-page-browse-by>
<ng-container *ngVar="(itemRD$ | async) as itemRD">
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
<ds-viewable-collection
[config]="paginationConfig"
[sortConfig]="sortConfig"
[objects]="itemRD"
[hideGear]="true">
</ds-viewable-collection>
</div>
<ds-error *ngIf="itemRD?.hasFailed"
message="{{'error.recent-submissions' | translate}}"></ds-error>
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
{{'collection.page.browse.recent.empty' | translate}}
</div>
</ng-container>
<router-outlet></router-outlet>
</section>
<footer *ngIf="collection.copyrightText" class="border-top my-5 pt-4">
<!-- Copyright -->

View File

@@ -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<RemoteData<Collection>>;
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
logoRD$: Observable<RemoteData<Bitstream>>;
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<string>;
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<Item>(
new PaginatedSearchOptions({
scope: id,
pagination: currentPagination,
sort: currentSort,
dsoTypes: [DSpaceObjectType.ITEM]
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW)
.pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
}),
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);
}
}

View File

@@ -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,

View File

@@ -0,0 +1,18 @@
<ng-container *ngVar="(itemRD$ | async) as itemRD">
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
<ds-viewable-collection
[config]="paginationConfig"
[sortConfig]="sortConfig"
[objects]="itemRD"
[hideGear]="true">
</ds-viewable-collection>
</div>
<ds-error *ngIf="itemRD?.hasFailed"
message="{{'error.recent-submissions' | translate}}"></ds-error>
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
{{'collection.page.browse.recent.empty' | translate}}
</div>
</ng-container>

View File

@@ -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<CollectionRecentlyAddedComponent>;
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();
});
});

View File

@@ -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<RemoteData<Collection>>;
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
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<Collection>),
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<Collection>) => rd.payload.id),
switchMap((id: string) => this.searchService.search<Item>(
new PaginatedSearchOptions({
scope: id,
pagination: currentPagination,
sort: currentSort,
dsoTypes: [DSpaceObjectType.ITEM]
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe(
toDSpaceObjectListRD()
) as Observable<RemoteData<PaginatedList<Item>>>),
startWith(undefined), // Make sure switching pages shows loading component
)),
);
}
ngOnDestroy(): void {
this.paginationService.clearPagination(this.paginationConfig.id);
}
}

View File

@@ -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,
children: [
{
path: '',
pathMatch: 'full',
component: SubComColSectionComponent,
},
{
path: 'browse/:id',
pathMatch: 'full',
component: ComcolBrowseByComponent,
canActivate: [BrowseByGuard],
resolve: {
breadcrumb: BrowseByI18nBreadcrumbResolver,
},
data: { breadcrumbKey: 'browse.metadata' },
},
],
}
],
data: {

View File

@@ -28,8 +28,7 @@
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
</ds-themed-comcol-page-browse-by>
<ds-themed-community-page-sub-community-list [community]="communityPayload"></ds-themed-community-page-sub-community-list>
<ds-themed-community-page-sub-collection-list [community]="communityPayload"></ds-themed-community-page-sub-collection-list>
<router-outlet></router-outlet>
</section>
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
<!-- Copyright -->

View File

@@ -1,16 +1,10 @@
import { mergeMap, filter, map } from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { CommunityDataService } from '../core/data/community-data.service';
import { RemoteData } from '../core/data/remote-data';
import { Bitstream } from '../core/shared/bitstream.model';
import { Community } from '../core/shared/community.model';
import { MetadataService } from '../core/metadata/metadata.service';
import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util';
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
@@ -53,8 +47,6 @@ export class CommunityPageComponent implements OnInit {
communityPageRoute$: Observable<string>;
constructor(
private communityDataService: CommunityDataService,
private metadata: MetadataService,
private route: ActivatedRoute,
private router: Router,
private authService: AuthService,

View File

@@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common';
import { SharedModule } from '../shared/shared.module';
import { CommunityPageComponent } from './community-page.component';
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
import { CommunityPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component';
import { CommunityPageRoutingModule } from './community-page-routing.module';
import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component';
import { CommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component';
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
import { StatisticsModule } from '../statistics/statistics.module';
@@ -15,20 +15,25 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
import { ComcolModule } from '../shared/comcol/comcol.module';
import {
ThemedCommunityPageSubCommunityListComponent
} from './sub-community-list/themed-community-page-sub-community-list.component';
} from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component';
import {
ThemedCollectionPageSubCollectionListComponent
} from './sub-collection-list/themed-community-page-sub-collection-list.component';
} from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component';
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
const DECLARATIONS = [CommunityPageComponent,
const DECLARATIONS = [
CommunityPageComponent,
ThemedCommunityPageComponent,
ThemedCommunityPageSubCommunityListComponent,
CommunityPageSubCollectionListComponent,
ThemedCollectionPageSubCollectionListComponent,
CommunityPageSubCommunityListComponent,
CreateCommunityPageComponent,
DeleteCommunityPageComponent];
DeleteCommunityPageComponent,
SubComColSectionComponent,
];
@NgModule({
imports: [
@@ -39,6 +44,7 @@ const DECLARATIONS = [CommunityPageComponent,
CommunityFormModule,
ComcolModule,
DsoPageModule,
BrowseByPageModule,
],
declarations: [
...DECLARATIONS

View File

@@ -1,6 +1,6 @@
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
<h3>{{'community.sub-collection-list.head' | translate}}</h3>
<ds-viewable-collection
[config]="config"
[sortConfig]="sortConfig"

View File

@@ -8,27 +8,27 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
import { Community } from '../../core/shared/community.model';
import { SharedModule } from '../../shared/shared.module';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { buildPaginatedList } from '../../core/data/paginated-list.model';
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 { 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';
import { FindListOptions } from '../../core/data/find-list-options.model';
import { GroupDataService } from '../../core/eperson/group-data.service';
import { LinkHeadService } from '../../core/services/link-head.service';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
import { Community } from '../../../../core/shared/community.model';
import { SharedModule } from '../../../../shared/shared.module';
import { CollectionDataService } from '../../../../core/data/collection-data.service';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
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 { 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';
import { FindListOptions } from '../../../../core/data/find-list-options.model';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { LinkHeadService } from '../../../../core/services/link-head.service';
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
import { createPaginatedList } from '../../../../shared/testing/utils.test';
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
describe('CommunityPageSubCollectionList Component', () => {
let comp: CommunityPageSubCollectionListComponent;

View File

@@ -1,19 +1,17 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model';
import { Community } from '../../core/shared/community.model';
import { fadeIn } from '../../shared/animations/fade';
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 { PaginationService } from '../../core/pagination/pagination.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { Collection } from '../../../../core/shared/collection.model';
import { Community } from '../../../../core/shared/community.model';
import { fadeIn } from '../../../../shared/animations/fade';
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 { PaginationService } from '../../../../core/pagination/pagination.service';
import { switchMap } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util';
import { hasValue } from '../../../../shared/empty.util';
@Component({
selector: 'ds-community-page-sub-collection-list',

View File

@@ -1,12 +1,12 @@
import { ThemedComponent } from '../../shared/theme-support/themed.component';
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
import { Component, Input } from '@angular/core';
import { Community } from '../../core/shared/community.model';
import { Community } from '../../../../core/shared/community.model';
@Component({
selector: 'ds-themed-community-page-sub-collection-list',
styleUrls: [],
templateUrl: '../../shared/theme-support/themed.component.html',
templateUrl: '../../../../shared/theme-support/themed.component.html',
})
export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent<CommunityPageSubCollectionListComponent> {
@Input() community: Community;
@@ -18,7 +18,7 @@ export class ThemedCollectionPageSubCollectionListComponent extends ThemedCompon
}
protected importThemedComponent(themeName: string): Promise<any> {
return import(`../../../themes/${themeName}/app/community-page/sub-collection-list/community-page-sub-collection-list.component`);
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component`);
}
protected importUnthemedComponent(): Promise<any> {

View File

@@ -0,0 +1,8 @@
<ng-container *ngIf="(community$ | async) as community">
<ds-themed-community-page-sub-community-list
[community]="community">
</ds-themed-community-page-sub-community-list>
<ds-themed-community-page-sub-collection-list
[community]="community">
</ds-themed-community-page-sub-collection-list>
</ng-container>

View File

@@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SubComColSectionComponent } from './sub-com-col-section.component';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
describe('SubComColSectionComponent', () => {
let component: SubComColSectionComponent;
let fixture: ComponentFixture<SubComColSectionComponent>;
let activatedRoute: ActivatedRouteStub;
beforeEach(async () => {
activatedRoute = new ActivatedRouteStub();
await TestBed.configureTestingModule({
declarations: [
SubComColSectionComponent,
],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
],
}).compileComponents();
fixture = TestBed.createComponent(SubComColSectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,28 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { Community } from '../../../core/shared/community.model';
import { ActivatedRoute, Data } from '@angular/router';
import { map } from 'rxjs/operators';
@Component({
selector: 'ds-sub-com-col-section',
templateUrl: './sub-com-col-section.component.html',
styleUrls: ['./sub-com-col-section.component.scss'],
})
export class SubComColSectionComponent implements OnInit {
community$: Observable<Community>;
constructor(
private route: ActivatedRoute,
) {
}
ngOnInit(): void {
this.community$ = this.route.data.pipe(
map((data: Data) => (data.dso as RemoteData<Community>).payload),
);
}
}

View File

@@ -1,6 +1,6 @@
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
<h2>{{'community.sub-community-list.head' | translate}}</h2>
<h3>{{'community.sub-community-list.head' | translate}}</h3>
<ds-viewable-collection
[config]="config"
[sortConfig]="sortConfig"

View File

@@ -8,27 +8,27 @@ import { By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
import { Community } from '../../core/shared/community.model';
import { buildPaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model';
import { SharedModule } from '../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
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 { 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';
import { FindListOptions } from '../../core/data/find-list-options.model';
import { GroupDataService } from '../../core/eperson/group-data.service';
import { LinkHeadService } from '../../core/services/link-head.service';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { Community } from '../../../../core/shared/community.model';
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { SharedModule } from '../../../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
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 { 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';
import { FindListOptions } from '../../../../core/data/find-list-options.model';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { LinkHeadService } from '../../../../core/services/link-head.service';
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
import { createPaginatedList } from '../../../../shared/testing/utils.test';
describe('CommunityPageSubCommunityListComponent Component', () => {
let comp: CommunityPageSubCommunityListComponent;

View File

@@ -3,16 +3,16 @@ import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model';
import { fadeIn } from '../../shared/animations/fade';
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 { CommunityDataService } from '../../core/data/community-data.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { Community } from '../../../../core/shared/community.model';
import { fadeIn } from '../../../../shared/animations/fade';
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 { CommunityDataService } from '../../../../core/data/community-data.service';
import { switchMap } from 'rxjs/operators';
import { PaginationService } from '../../core/pagination/pagination.service';
import { hasValue } from '../../shared/empty.util';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { hasValue } from '../../../../shared/empty.util';
@Component({
selector: 'ds-community-page-sub-community-list',

View File

@@ -1,12 +1,12 @@
import { ThemedComponent } from '../../shared/theme-support/themed.component';
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
import { Component, Input } from '@angular/core';
import { Community } from '../../core/shared/community.model';
import { Community } from '../../../../core/shared/community.model';
@Component({
selector: 'ds-themed-community-page-sub-community-list',
styleUrls: [],
templateUrl: '../../shared/theme-support/themed.component.html',
templateUrl: '../../../../shared/theme-support/themed.component.html',
})
export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent<CommunityPageSubCommunityListComponent> {
@@ -19,7 +19,7 @@ export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponen
}
protected importThemedComponent(themeName: string): Promise<any> {
return import(`../../../themes/${themeName}/app/community-page/sub-community-list/community-page-sub-community-list.component`);
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component`);
}
protected importUnthemedComponent(): Promise<any> {

View File

@@ -34,5 +34,6 @@ export enum FeatureID {
CanEditItem = 'canEditItem',
CanRegisterDOI = 'canRegisterDOI',
CanSubscribe = 'canSubscribeDso',
CanSeeQA = 'canSeeQA'
EPersonForgotPassword = 'epersonForgotPassword',
CanSeeQA = 'canSeeQA',
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
import { FeatureID } from '../data/feature-authorization/feature-id';
import {
SingleFeatureAuthorizationGuard
} from '../data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard';
import { AuthService } from '../auth/auth.service';
@Injectable({
providedIn: 'root'
})
/**
* Guard that checks if the forgot-password feature is enabled
*/
export class ForgotPasswordCheckGuard extends SingleFeatureAuthorizationGuard {
constructor(
protected readonly authorizationService: AuthorizationDataService,
protected readonly router: Router,
protected readonly authService: AuthService
) {
super(authorizationService, router, authService);
}
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
return of(FeatureID.EPersonForgotPassword);
}
}

View File

@@ -32,6 +32,18 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-7">
<ds-item-page-img-field
[fields]="['organization.identifier.ror']"
[img]="{
URI: './assets/images/ror-icon.svg',
alt: 'item.page.image.alt.ROR',
heightVar: '--ds-item-page-img-field-ror-inline-height'
}"
[item]="object"
[label]="'orgunit.page.ror'"
[urlRegex]="'(.*)ror.org'"
>
</ds-item-page-img-field>
<ds-related-items
[parentItem]="object"
[relationType]="'isPublicationOfOrgUnit'"

View File

@@ -3,8 +3,8 @@
<!--
Choose a template. Priority: markdown, link, browse link.
-->
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? link : (hasBrowseDefinition() ? browselink : simple)));
context: {value: mdValue.value}">
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? (img != null ? linkImg : link) : (hasBrowseDefinition() ? browselink : simple)));
context: {value: mdValue.value, img}">
</ng-container>
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
</ng-container>
@@ -23,6 +23,17 @@
</a>
</ng-template>
<!-- Render value as a link with icon -->
<ng-template #linkImg let-img="img" let-value="value">
<a [href]="value" class="link-anchor dont-break-out ds-simple-metadata-link" target="_blank">
<img class="link-logo"
[alt]="img.alt | translate"
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
[src]="img.URI"/>
{{value}}
</a>
</ng-template>
<!-- Render simple value in a span -->
<ng-template #simple let-value="value">
<span class="dont-break-out preserve-line-breaks">{{value}}</span>

View File

@@ -4,6 +4,7 @@ import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
import { BrowseDefinition } from '../../../core/shared/browse-definition.model';
import { hasValue } from '../../../shared/empty.util';
import { VALUE_LIST_BROWSE_DEFINITION } from '../../../core/shared/value-list-browse-definition.resource-type';
import { ImageField } from '../../simple/field-components/specific-field/item-page-field.component';
/**
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
@@ -55,6 +56,11 @@ export class MetadataValuesComponent implements OnChanges {
@Input() browseDefinition?: BrowseDefinition;
/**
* Optional {@code ImageField} reference that represents an image to be displayed inline.
*/
@Input() img?: ImageField;
ngOnChanges(changes: SimpleChanges): void {
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
}

View File

@@ -1,21 +1,36 @@
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
import {
RelatedEntitiesSearchComponent
} from './simple/related-entities/related-entities-search/related-entities-search.component';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import { SearchModule } from '../shared/search/search.module';
import { SharedModule } from '../shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
import {
dsDynamicFormControlMapFn
} from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
import {
TabbedRelatedEntitiesSearchComponent
} from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
import {
ItemVersionsDeleteModalComponent
} from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
import {
ItemVersionsSummaryModalComponent
} from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component';
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
import {
GenericItemPageFieldComponent
} from './simple/field-components/specific-field/generic/generic-item-page-field.component';
import {
MetadataRepresentationListComponent
} from './simple/metadata-representation-list/metadata-representation-list.component';
import { RelatedItemsComponent } from './simple/related-items/related-items-component';
import {
ThemedMetadataRepresentationListComponent
} from './simple/metadata-representation-list/themed-metadata-representation-list.component';
import { ItemPageImgFieldComponent } from './simple/field-components/specific-field/img/item-page-img-field.component';
const ENTRY_COMPONENTS = [
ItemVersionsDeleteModalComponent,
@@ -32,6 +47,7 @@ const COMPONENTS = [
MetadataRepresentationListComponent,
ThemedMetadataRepresentationListComponent,
RelatedItemsComponent,
ItemPageImgFieldComponent,
];
@NgModule({
@@ -42,7 +58,8 @@ const COMPONENTS = [
CommonModule,
SearchModule,
SharedModule,
TranslateModule
TranslateModule,
NgOptimizedImage
],
exports: [
...COMPONENTS

View File

@@ -0,0 +1,85 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemPageImgFieldComponent } from './item-page-img-field.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
import { GenericItemPageFieldComponent } from '../generic/generic-item-page-field.component';
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
import { By } from '@angular/platform-browser';
import { ImageField } from '../item-page-field.component';
let component: ItemPageImgFieldComponent;
let fixture: ComponentFixture<ItemPageImgFieldComponent>;
const mockField = 'organization.identifier.ror';
const mockValue = 'http://ror.org/awesome-identifier';
const mockLabel = 'ROR label';
const mockUrlRegex = '(.*)ror.org';
const mockImg = {
URI: './assets/images/ror-icon.svg',
alt: 'item.page.image.alt.ROR',
heightVar: '--ds-item-page-img-field-ror-inline-height'
} as ImageField;
describe('ItemPageImgFieldComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})],
providers: [
{ provide: APP_CONFIG, useValue: environment },
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
],
declarations: [ItemPageImgFieldComponent, GenericItemPageFieldComponent, MetadataValuesComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.overrideComponent(GenericItemPageFieldComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents();
fixture = TestBed.createComponent(ItemPageImgFieldComponent);
component = fixture.componentInstance;
component.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
component.fields = [mockField];
component.label = mockLabel;
component.urlRegex = mockUrlRegex;
component.img = mockImg;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display display img tag', () => {
const image = fixture.debugElement.query(By.css('img.link-logo'));
expect(image).not.toBeNull();
});
it('should have right attributes', () => {
const image = fixture.debugElement.query(By.css('img.link-logo'));
expect(image.attributes.src).toEqual(mockImg.URI);
expect(image.attributes.alt).toEqual(mockImg.alt);
const imageEl = image.nativeElement;
expect(imageEl.style.height).toContain(mockImg.heightVar);
});
it('should have the right value', () => {
const imageAnchor = fixture.debugElement.query(By.css('a.link-anchor'));
const anchorEl = imageAnchor.nativeElement;
expect(anchorEl.innerHTML).toContain(mockValue);
});
});

View File

@@ -0,0 +1,46 @@
import { Component, Input } from '@angular/core';
import { ImageField, ItemPageFieldComponent } from '../item-page-field.component';
import { Item } from '../../../../../core/shared/item.model';
@Component({
selector: 'ds-item-page-img-field',
templateUrl: '../item-page-field.component.html'
})
/**
* Component that renders an inline image for a given field.
* This component uses a given {@code ImageField} configuration to correctly render the img.
*/
export class ItemPageImgFieldComponent extends ItemPageFieldComponent {
/**
* The item to display metadata for
*/
@Input() item: Item;
/**
* Separator string between multiple values of the metadata fields defined
* @type {string}
*/
@Input() separator: string;
/**
* Fields (schema.element.qualifier) used to render their values.
*/
@Input() fields: string[];
/**
* Label i18n key for the rendered metadata
*/
@Input() label: string;
/**
* Image Configuration
*/
@Input() img: ImageField;
/**
* Whether any valid HTTP(S) URL should be rendered as a link
*/
@Input() urlRegex?: string;
}

View File

@@ -6,5 +6,6 @@
[enableMarkdown]="enableMarkdown"
[urlRegex]="urlRegex"
[browseDefinition]="browseDefinition|async"
[img]="img"
></ds-metadata-values>
</div>

View File

@@ -6,6 +6,25 @@ import { BrowseDefinition } from '../../../../core/shared/browse-definition.mode
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
import { getRemoteDataPayload } from '../../../../core/shared/operators';
/**
* Interface that encapsulate Image configuration for this component.
*/
export interface ImageField {
/**
* URI that is used to retrieve the image.
*/
URI: string;
/**
* i18n Key that represents the alt text to display
*/
alt: string;
/**
* CSS variable that contains the height of the inline image.
*/
heightVar: string;
}
/**
* This component can be used to represent metadata on a simple item page.
* It expects one input parameter of type Item to which the metadata belongs.
@@ -51,6 +70,11 @@ export class ItemPageFieldComponent {
*/
urlRegex?: string;
/**
* Image Configuration
*/
img: ImageField;
/**
* Return browse definition that matches any field used in this component if it is configured as a browse
* link in dspace.cfg (webui.browse.link.<n>)

View File

@@ -1,6 +1,7 @@
<ng-container *ngVar="(objects$ | async) as objects">
<h1 [ngClass]="{'sr-only': parentname }">{{title | translate}}</h1>
<ng-container *ngComponentOutlet="getStartsWithComponent(); injector: objectInjector;"></ng-container>
<h1 *ngIf="displayTitle">{{title | translate}}</h1>
<ds-starts-with-loader [paginationId]="paginationConfig?.id" [startsWithOptions]="startsWithOptions" [type]="type">
</ds-starts-with-loader>
<div *ngIf="objects?.hasSucceeded && !objects?.isLoading && objects?.payload?.page.length > 0" @fadeIn>
<div *ngIf="shouldDisplayResetButton$ |async" class="mb-2 reset">
<ds-themed-results-back-button [back]="back" [buttonLabel]="buttonLabel"></ds-themed-results-back-button>

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output, OnChanges } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
@@ -6,7 +6,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
import { fadeIn, fadeInOut } from '../animations/fade';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import { ListableObject } from '../object-collection/shared/listable-object.model';
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
import { StartsWithType } from '../starts-with/starts-with-decorator';
import { PaginationService } from '../../core/pagination/pagination.service';
import { ViewMode } from '../../core/shared/view-mode.model';
import { RouteService } from '../../core/services/route.service';
@@ -26,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core';
/**
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent implements OnInit, OnDestroy {
export class BrowseByComponent implements OnInit, OnChanges, OnDestroy {
/**
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
@@ -39,9 +39,10 @@ export class BrowseByComponent implements OnInit, OnDestroy {
@Input() title: string;
/**
* The parent name
* Whether the title should be displayed
*/
@Input() parentname: string;
@Input() displayTitle = true;
/**
* The list of objects to display
*/
@@ -66,7 +67,7 @@ export class BrowseByComponent implements OnInit, OnDestroy {
/**
* The list of options to render for the StartsWith component
*/
@Input() startsWithOptions = [];
@Input() startsWithOptions: (string | number)[] = [];
/**
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
@@ -98,16 +99,6 @@ export class BrowseByComponent implements OnInit, OnDestroy {
*/
@Output() sortDirectionChange = new EventEmitter<SortDirection>();
/**
* An object injector used to inject the startsWithOptions to the switchable StartsWith component
*/
objectInjector: Injector;
/**
* Declare SortDirection enumeration to use it in the template
*/
public sortDirections = SortDirection;
/**
* Observable that tracks if the back button should be displayed based on the path parameters
*/
@@ -141,7 +132,7 @@ export class BrowseByComponent implements OnInit, OnDestroy {
*/
back = () => {
const page = +this.previousPage$.value > 1 ? +this.previousPage$.value : 1;
this.paginationService.updateRoute(this.paginationConfig.id, {page: page}, {[this.paginationConfig.id + '.return']: null, value: null, startsWith: null});
this.paginationService.updateRoute(this.paginationConfig.id, { page: page }, { [this.paginationConfig.id + '.return']: null, value: null, startsWith: null });
};
/**
@@ -158,44 +149,19 @@ export class BrowseByComponent implements OnInit, OnDestroy {
this.next.emit(true);
}
/**
* Change the page size
* @param size
*/
doPageSizeChange(size) {
this.paginationService.updateRoute(this.paginationConfig.id,{pageSize: size});
}
/**
* Change the sort direction
* @param direction
*/
doSortDirectionChange(direction) {
this.paginationService.updateRoute(this.paginationConfig.id,{sortDirection: direction});
}
/**
* Get the switchable StartsWith component dependant on the type
*/
getStartsWithComponent() {
return getStartsWithComponent(this.type);
}
ngOnInit(): void {
this.objectInjector = Injector.create({
providers: [
{ provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] },
{ provide: 'paginationId', useFactory: () => (this.paginationConfig?.id), deps:[] }
],
parent: this.injector
});
const startsWith$ = this.routeService.getQueryParameterValue('startsWith');
const value$ = this.routeService.getQueryParameterValue('value');
this.shouldDisplayResetButton$ = observableCombineLatest([startsWith$, value$]).pipe(
map(([startsWith, value]) => hasValue(startsWith) || hasValue(value))
);
}
ngOnChanges(): void {
if (this.sub) {
this.sub.unsubscribe();
}
this.sub = this.routeService.getQueryParameterValue(this.paginationConfig.id + '.return').subscribe(this.previousPage$);
}

View File

@@ -21,7 +21,7 @@ export class ThemedBrowseByComponent extends ThemedComponent<BrowseByComponent>
@Input() title: string;
@Input() parentname: string;
@Input() displayTitle: boolean;
@Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>;
@@ -31,7 +31,7 @@ export class ThemedBrowseByComponent extends ThemedComponent<BrowseByComponent>
@Input() type: StartsWithType;
@Input() startsWithOptions: number[];
@Input() startsWithOptions: (string | number)[];
@Input() showPaginator: boolean;
@@ -47,7 +47,7 @@ export class ThemedBrowseByComponent extends ThemedComponent<BrowseByComponent>
protected inAndOutputNames: (keyof BrowseByComponent & keyof this)[] = [
'title',
'parentname',
'displayTitle',
'objects$',
'paginationConfig',
'sortConfig',

View File

@@ -1,13 +1,17 @@
<h2 class="comcol-browse-label h5">{{'browse.comcol.head' | translate}}</h2>
<nav class="comcol-browse mb-4" aria-label="Browse Community or Collection">
<h2 class="comcol-browse-label">{{'browse.comcol.head' | translate}}</h2>
<nav *ngIf="(allOptions$ | async) as allOptions" class="comcol-browse mb-4" aria-label="Browse Community or Collection">
<div class="d-none d-sm-block">
<div class="list-group list-group-horizontal">
<div class="list-group list-group-horizontal" role="tablist">
<a *ngFor="let option of allOptions"
[attr.aria-current]="(currentOption$ | async)?.id === option.id"
class="list-group-item"
role="tab"
[routerLink]="option.routerLink"
[queryParams]="option.params"
routerLinkActive="active">{{ option.label | translate }}</a>
[class.active]="(currentOption$ | async)?.id === option.id">
{{ option.label | translate }}
</a>
</div>
</div>
@@ -15,10 +19,12 @@
<select name="browse-type"
class="form-control"
aria-label="Browse Community or Collection"
(ngModelChange)="onSelectChange($event)" [ngModel]="currentOptionId$ | async">
(change)="onSelectChange($event)">
<option *ngFor="let option of allOptions"
[ngValue]="option.id"
[attr.selected]="(currentOptionId$ | async) === option.id ? 'selected' : null">{{ option.label | translate }}</option>
[value]="option.id"
[attr.selected]="(currentOption$ | async)?.id === option.id ? 'selected' : null">
{{ option.label | translate }}
</option>
</select>
</div>
</nav>

View File

@@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { Router, EventType, Scroll } from '@angular/router';
import { getCommunityPageRoute } from '../../../community-page/community-page-routing-paths';
import { getCollectionPageRoute } from '../../../collection-page/collection-page-routing-paths';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@@ -26,62 +26,87 @@ export interface ComColPageNavOption {
styleUrls: ['./comcol-page-browse-by.component.scss'],
templateUrl: './comcol-page-browse-by.component.html'
})
export class ComcolPageBrowseByComponent implements OnInit {
export class ComcolPageBrowseByComponent implements OnDestroy, OnInit {
/**
* The ID of the Community or Collection
*/
@Input() id: string;
@Input() contentType: string;
allOptions: ComColPageNavOption[];
allOptions$: Observable<ComColPageNavOption[]>;
currentOptionId$: Observable<string>;
currentOption$: BehaviorSubject<ComColPageNavOption> = new BehaviorSubject(undefined);
subs: Subscription[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
public router: Router,
private browseService: BrowseService
) {
}
ngOnInit(): void {
this.browseService.getBrowseDefinitions()
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
this.allOptions$ = this.browseService.getBrowseDefinitions().pipe(
getFirstCompletedRemoteData(),
map((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
const allOptions: ComColPageNavOption[] = [];
if (browseDefListRD.hasSucceeded) {
this.allOptions = browseDefListRD.payload.page
.map((config: BrowseDefinition) => ({
id: config.id,
label: `browse.comcol.by.${config.id}`,
routerLink: `/browse/${config.id}`,
params: { scope: this.id }
}));
let comColRoute: string;
if (this.contentType === 'collection') {
this.allOptions = [{
id: this.id,
comColRoute = getCollectionPageRoute(this.id);
allOptions.push({
id: 'recent_submissions',
label: 'collection.page.browse.recent.head',
routerLink: getCollectionPageRoute(this.id)
}, ...this.allOptions];
routerLink: comColRoute,
});
} else if (this.contentType === 'community') {
this.allOptions = [{
id: this.id,
comColRoute = getCommunityPageRoute(this.id);
allOptions.push({
id: 'comcols',
label: 'community.all-lists.head',
routerLink: getCommunityPageRoute(this.id)
}, ...this.allOptions];
routerLink: comColRoute,
});
}
allOptions.push(...browseDefListRD.payload.page.map((config: BrowseDefinition) => ({
id: `browse_${config.id}`,
label: `browse.comcol.by.${config.id}`,
routerLink: `${comColRoute}/browse/${config.id}`,
})));
}
return allOptions;
}),
);
this.subs.push(combineLatest([
this.allOptions$,
this.router.events,
]).subscribe(([navOptions, scrollEvent]: [ComColPageNavOption[], Scroll]) => {
if (scrollEvent.type === EventType.Scroll) {
for (let option of navOptions) {
if (option.routerLink === scrollEvent.routerEvent.urlAfterRedirects.split('?')[0]) {
this.currentOption$.next(option);
}
}
}
}));
}
ngOnDestroy(): void {
this.subs.forEach((sub: Subscription) => sub.unsubscribe());
}
onSelectChange(event: any): void {
this.allOptions$.pipe(
take(1),
).subscribe((allOptions: ComColPageNavOption[]) => {
for (let option of allOptions) {
if (option.id === event.target.value) {
this.currentOption$.next(option[0]);
void this.router.navigate([option.routerLink], { queryParams: option.params });
break;
}
}
});
this.currentOptionId$ = this.route.params.pipe(
map((params: Params) => params.id)
);
}
onSelectChange(newId: string) {
const selectedOption = this.allOptions
.find((option: ComColPageNavOption) => option.id === newId);
this.router.navigate([selectedOption.routerLink], { queryParams: selectedOption.params });
}
}

View File

@@ -16,6 +16,8 @@ import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role
import { SharedModule } from '../shared.module';
import { FormModule } from '../form/form.module';
import { UploadModule } from '../upload/upload.module';
import { ComcolBrowseByComponent } from './sections/comcol-browse-by/comcol-browse-by.component';
import { BrowseByModule } from '../../browse-by/browse-by.module';
const COMPONENTS = [
ComcolPageContentComponent,
@@ -29,7 +31,8 @@ const COMPONENTS = [
ComcolPageBrowseByComponent,
ThemedComcolPageBrowseByComponent,
ComcolRoleComponent,
ThemedComcolPageHandleComponent
ThemedComcolPageHandleComponent,
ComcolBrowseByComponent,
];
@NgModule({
@@ -41,6 +44,7 @@ const COMPONENTS = [
FormModule,
SharedModule,
UploadModule,
BrowseByModule,
],
exports: [
...COMPONENTS,

View File

@@ -0,0 +1,4 @@
<ds-browse-by-switcher [browseByType]="browseByType$ | async"
[displayTitle]="false"
[scope]="scope$ | async">
</ds-browse-by-switcher>

View File

@@ -0,0 +1,69 @@
// eslint-disable-next-line max-classes-per-file
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComcolBrowseByComponent } from './comcol-browse-by.component';
import { rendersBrowseBy } from '../../../../browse-by/browse-by-switcher/browse-by-decorator';
import { BrowseByDataType } from '../../../../browse-by/browse-by-switcher/browse-by-data-type';
import { Component } from '@angular/core';
import { BrowseDefinition } from '../../../../core/shared/browse-definition.model';
import { ActivatedRouteStub } from '../../../testing/active-router.stub';
import { ThemeService } from '../../../theme-support/theme.service';
import { getMockThemeService } from '../../../mocks/theme-service.mock';
import { BrowseBySwitcherComponent } from '../../../../browse-by/browse-by-switcher/browse-by-switcher.component';
import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive';
import { ActivatedRoute } from '@angular/router';
import { By } from '@angular/platform-browser';
@rendersBrowseBy('ComcolBrowseByComponent' as BrowseByDataType)
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: '',
template: '<span id="ComcolBrowseByComponent"></span>',
})
class BrowseByTestComponent {
}
class TestBrowseByPageBrowseDefinition extends BrowseDefinition {
getRenderType(): BrowseByDataType {
return 'ComcolBrowseByComponent' as BrowseByDataType;
}
}
describe('ComcolBrowseByComponent', () => {
let component: ComcolBrowseByComponent;
let fixture: ComponentFixture<ComcolBrowseByComponent>;
let activatedRoute: ActivatedRouteStub;
let themeService: ThemeService;
beforeEach(async () => {
activatedRoute = new ActivatedRouteStub();
themeService = getMockThemeService();
await TestBed.configureTestingModule({
declarations: [
ComcolBrowseByComponent,
BrowseBySwitcherComponent,
DynamicComponentLoaderDirective,
],
providers: [
BrowseByTestComponent,
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ThemeService, useValue: themeService },
],
}).compileComponents();
fixture = TestBed.createComponent(ComcolBrowseByComponent);
component = fixture.componentInstance;
});
it('should create the correct browse section based on the route browseDefinition', () => {
activatedRoute.testData = {
browseDefinition: new TestBrowseByPageBrowseDefinition(),
};
fixture.detectChanges();
expect(component).toBeTruthy();
expect(fixture.debugElement.query(By.css('#ComcolBrowseByComponent'))).not.toBeNull();
});
});

View File

@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BrowseByDataType } from '../../../../browse-by/browse-by-switcher/browse-by-data-type';
import { ActivatedRoute, Data } from '@angular/router';
import { map } from 'rxjs/operators';
import { BrowseDefinition } from '../../../../core/shared/browse-definition.model';
@Component({
selector: 'ds-comcol-browse-by',
templateUrl: './comcol-browse-by.component.html',
styleUrls: ['./comcol-browse-by.component.scss'],
})
export class ComcolBrowseByComponent implements OnInit {
browseByType$: Observable<BrowseByDataType>;
scope$: Observable<string>;
constructor(
protected route: ActivatedRoute,
) {
}
/**
* Fetch the correct browse-by component by using the relevant config from the route data
*/
ngOnInit(): void {
this.browseByType$ = this.route.data.pipe(
map((data: { browseDefinition: BrowseDefinition }) => data.browseDefinition.getRenderType()),
);
this.scope$ = this.route.data.pipe(
map((data: Data) => data.scope),
);
}
}

View File

@@ -27,11 +27,11 @@
[disabled]="!form.valid"><i class="fas fa-sign-in-alt"></i> {{"login.form.submit" | translate}}</button>
</form>
<div class="mt-2">
<a class="dropdown-item" *ngIf="canRegister$ | async" [routerLink]="[getRegisterRoute()]" [attr.data-test]="'register' | dsBrowserOnly">
{{ 'login.form.new-user' | translate }}
</a>
<a class="dropdown-item" [routerLink]="[getForgotRoute()]" [attr.data-test]="'forgot' | dsBrowserOnly">
{{ 'login.form.forgot-password' | translate }}
</a>
</div>
<ng-container *ngIf="canShowDivider$ | async">
<div class="mt-2">
<a class="dropdown-item" *ngIf="canRegister$ | async" [routerLink]="[getRegisterRoute()]"
[attr.data-test]="'register' | dsBrowserOnly">{{"login.form.new-user" | translate}}</a>
<a class="dropdown-item" *ngIf="canForgot$ | async" [routerLink]="[getForgotRoute()]"
[attr.data-test]="'forgot' | dsBrowserOnly">{{"login.form.forgot-password" | translate}}</a>
</div>
</ng-container>

View File

@@ -1,9 +1,9 @@
import { map } from 'rxjs/operators';
import { combineLatest, Observable, shareReplay } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { Component, Inject, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../../../core/auth/auth.actions';
import { getAuthenticationError, getAuthenticationInfo, } from '../../../../core/auth/selectors';
@@ -64,7 +64,7 @@ export class LogInPasswordComponent implements OnInit {
/**
* The authentication form.
* @type {FormGroup}
* @type {UntypedFormGroup}
*/
public form: UntypedFormGroup;
@@ -73,6 +73,17 @@ export class LogInPasswordComponent implements OnInit {
*/
public canRegister$: Observable<boolean>;
/**
* Whether or not the current user (or anonymous) is authorized to register an account
*/
canForgot$: Observable<boolean>;
/**
* Shows the divider only if contains at least one link to show
*/
canShowDivider$: Observable<boolean>;
constructor(
@Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
@Inject('isStandalonePage') public isStandalonePage: boolean,
@@ -115,7 +126,14 @@ export class LogInPasswordComponent implements OnInit {
})
);
this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration);
this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration).pipe(shareReplay(1));
this.canForgot$ = this.authorizationService.isAuthorized(FeatureID.EPersonForgotPassword).pipe(shareReplay(1));
this.canShowDivider$ =
combineLatest([this.canRegister$, this.canForgot$])
.pipe(
map(([canRegister, canForgot]) => canRegister || canForgot),
filter(Boolean)
);
}
getRegisterRoute() {

View File

@@ -6,7 +6,7 @@ import { ViewMode } from '../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { Params } from '@angular/router';
import { BBM_PAGINATION_ID } from '../../../browse-by/browse-by-metadata-page/browse-by-metadata-page.component';
import { BBM_PAGINATION_ID } from '../../../browse-by/browse-by-metadata/browse-by-metadata.component';
import { RouteService } from 'src/app/core/services/route.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

View File

@@ -282,6 +282,7 @@ import { NgxPaginationModule } from 'ngx-pagination';
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
import { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive';
import { StartsWithLoaderComponent } from './starts-with/starts-with-loader.component';
const MODULES = [
CommonModule,
@@ -377,7 +378,7 @@ const COMPONENTS = [
ThemedStatusBadgeComponent,
BadgesComponent,
ThemedBadgesComponent,
StartsWithLoaderComponent,
ItemSelectComponent,
CollectionSelectComponent,
MetadataRepresentationLoaderComponent,

View File

@@ -3,9 +3,8 @@ import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { By } from '@angular/platform-browser';
import { StartsWithDateComponent } from './starts-with-date.component';
import { ActivatedRouteStub } from '../../testing/active-router.stub';
@@ -17,29 +16,25 @@ import { PaginationServiceStub } from '../../testing/pagination-service.stub';
describe('StartsWithDateComponent', () => {
let comp: StartsWithDateComponent;
let fixture: ComponentFixture<StartsWithDateComponent>;
let route: ActivatedRoute;
let router: Router;
let paginationService;
let route: ActivatedRouteStub;
let paginationService: PaginationServiceStub;
let router: RouterStub;
const options = [2019, 2018, 2017, 2016, 2015];
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
params: observableOf({}),
queryParams: observableOf({})
});
beforeEach(waitForAsync(async () => {
route = new ActivatedRouteStub();
router = new RouterStub();
paginationService = new PaginationServiceStub();
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
await 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: ActivatedRoute, useValue: route },
{ provide: PaginationService, useValue: paginationService },
{ provide: Router, useValue: new RouterStub() }
{ provide: Router, useValue: router },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -48,9 +43,9 @@ describe('StartsWithDateComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(StartsWithDateComponent);
comp = fixture.componentInstance;
comp.paginationId = 'page-id';
comp.startsWithOptions = options;
fixture.detectChanges();
route = (comp as any).route;
router = (comp as any).router;
});
it('should create a FormGroup containing a startsWith FormControl', () => {
@@ -156,10 +151,6 @@ describe('StartsWithDateComponent', () => {
describe('when filling in the input form', () => {
let form;
const expectedValue = '2015';
const extras: NavigationExtras = {
queryParams: Object.assign({ startsWith: expectedValue }),
queryParamsHandling: 'merge'
};
beforeEach(() => {
form = fixture.debugElement.query(By.css('form'));

View File

@@ -1,10 +1,7 @@
import { Component, Inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
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".
@@ -16,7 +13,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
templateUrl: './starts-with-date.component.html'
})
@renderStartsWithFor(StartsWithType.date)
export class StartsWithDateComponent extends StartsWithAbstractComponent {
export class StartsWithDateComponent extends StartsWithAbstractComponent implements OnInit {
/**
* A list of options for months to select from
@@ -33,14 +30,6 @@ 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, paginationId, paginationService, route, router);
}
ngOnInit() {
this.monthOptions = [
'none',
@@ -133,13 +122,6 @@ export class StartsWithDateComponent extends StartsWithAbstractComponent {
}
}
/**
* Get startsWithYear as a number;
*/
getStartsWithYear() {
return this.startsWithYear;
}
/**
* Get startsWithMonth as a number;
*/

View File

@@ -1,9 +1,10 @@
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { hasValue } from '../empty.util';
import { PaginationService } from '../../core/pagination/pagination.service';
import { StartsWithType } from './starts-with-decorator';
/**
* An abstract component to render StartsWith options
@@ -13,6 +14,13 @@ import { PaginationService } from '../../core/pagination/pagination.service';
template: ''
})
export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy {
@Input() paginationId: string;
@Input() startsWithOptions: (string | number)[];
@Input() type: StartsWithType;
/**
* The currently selected startsWith in string format
*/
@@ -28,11 +36,11 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy {
*/
subs: Subscription[] = [];
public constructor(@Inject('startsWithOptions') public startsWithOptions: any[],
@Inject('paginationId') public paginationId: string,
public constructor(
protected paginationService: PaginationService,
protected route: ActivatedRoute,
protected router: Router) {
protected router: Router,
) {
}
ngOnInit(): void {
@@ -55,15 +63,6 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy {
return this.startsWith;
}
/**
* Set the startsWith by event
* @param event
*/
setStartsWithEvent(event: Event) {
this.startsWith = (event.target as HTMLInputElement).value;
this.setStartsWithParam();
}
/**
* Set the startsWith by string
* @param startsWith
@@ -82,7 +81,7 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy {
if (resetPage) {
this.paginationService.updateRoute(this.paginationId, {page: 1}, { startsWith: this.startsWith });
} else {
this.router.navigate([], {
void this.router.navigate([], {
queryParams: Object.assign({ startsWith: this.startsWith }),
queryParamsHandling: 'merge'
});

View File

@@ -0,0 +1,71 @@
import { StartsWithLoaderComponent } from './starts-with-loader.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { DynamicComponentLoaderDirective } from '../abstract-component-loader/dynamic-component-loader.directive';
import { TranslateModule } from '@ngx-translate/core';
import { StartsWithTextComponent } from './text/starts-with-text.component';
import { Router, ActivatedRoute } from '@angular/router';
import { RouterStub } from '../testing/router.stub';
import { ThemeService } from '../theme-support/theme.service';
import { getMockThemeService } from '../mocks/theme-service.mock';
import { StartsWithType } from './starts-with-decorator';
import { ActivatedRouteStub } from '../testing/active-router.stub';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../testing/pagination-service.stub';
describe('StartsWithLoaderComponent', () => {
let comp: StartsWithLoaderComponent;
let fixture: ComponentFixture<StartsWithLoaderComponent>;
let paginationService: PaginationServiceStub;
let route: ActivatedRouteStub;
let themeService: ThemeService;
const type: StartsWithType = StartsWithType.text;
beforeEach(waitForAsync(() => {
paginationService = new PaginationServiceStub();
route = new ActivatedRouteStub();
themeService = getMockThemeService('dspace');
void TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
],
declarations: [
StartsWithLoaderComponent,
StartsWithTextComponent,
DynamicComponentLoaderDirective,
],
providers: [
{ provide: PaginationService, useValue: paginationService },
{ provide: ActivatedRoute, useValue: route },
{ provide: Router, useValue: new RouterStub() },
{ provide: ThemeService, useValue: themeService },
],
schemas: [NO_ERRORS_SCHEMA],
}).overrideComponent(StartsWithLoaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
entryComponents: [StartsWithTextComponent],
}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(StartsWithLoaderComponent);
comp = fixture.componentInstance;
comp.type = type;
comp.paginationId = 'bbm';
comp.startsWithOptions = [];
spyOn(comp, 'getComponent').and.returnValue(StartsWithTextComponent);
fixture.detectChanges();
}));
describe('When the component is rendered', () => {
it('should call the getComponent function', () => {
expect(comp.getComponent).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,33 @@
import { Component, Input, } from '@angular/core';
import { AbstractComponentLoaderComponent } from '../abstract-component-loader/abstract-component-loader.component';
import { GenericConstructor } from '../../core/shared/generic-constructor';
import { getStartsWithComponent, StartsWithType } from './starts-with-decorator';
import { StartsWithAbstractComponent } from './starts-with-abstract.component';
/**
* Component for loading a {@link StartsWithAbstractComponent} depending on the "type" input
*/
@Component({
selector: 'ds-starts-with-loader',
templateUrl: '../abstract-component-loader/abstract-component-loader.component.html',
})
export class StartsWithLoaderComponent extends AbstractComponentLoaderComponent<StartsWithAbstractComponent> {
@Input() paginationId: string;
@Input() startsWithOptions: (string | number)[];
@Input() type: StartsWithType;
protected inputNames: (keyof this & string)[] = [
...this.inputNames,
'paginationId',
'startsWithOptions',
'type',
];
public getComponent(): GenericConstructor<StartsWithAbstractComponent> {
return getStartsWithComponent(this.type);
}
}

View File

@@ -10,25 +10,31 @@ import { By } from '@angular/platform-browser';
import { StartsWithTextComponent } from './starts-with-text.component';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../testing/pagination-service.stub';
import { ActivatedRouteStub } from '../../testing/active-router.stub';
import { RouterStub } from '../../testing/router.stub';
describe('StartsWithTextComponent', () => {
let comp: StartsWithTextComponent;
let fixture: ComponentFixture<StartsWithTextComponent>;
let route: ActivatedRoute;
let router: Router;
let paginationService: PaginationServiceStub;
let route: ActivatedRouteStub;
let router: RouterStub;
const options = ['0-9', 'A', 'B', 'C'];
const paginationService = new PaginationServiceStub();
beforeEach(waitForAsync(async () => {
paginationService = new PaginationServiceStub();
route = new ActivatedRouteStub();
router = new RouterStub();
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
await TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
declarations: [StartsWithTextComponent, EnumKeysPipe],
providers: [
{ provide: 'startsWithOptions', useValue: options },
{ provide: 'paginationId', useValue: 'page-id' },
{ provide: PaginationService, useValue: paginationService }
{ provide: PaginationService, useValue: paginationService },
{ provide: ActivatedRoute, useValue: route },
{ provide: Router, useValue: router },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -37,10 +43,9 @@ describe('StartsWithTextComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(StartsWithTextComponent);
comp = fixture.componentInstance;
comp.paginationId = 'page-id';
comp.startsWithOptions = options;
fixture.detectChanges();
route = (comp as any).route;
router = (comp as any).router;
spyOn(router, 'navigate');
});
it('should create a FormGroup containing a startsWith FormControl', () => {

View File

@@ -35,15 +35,4 @@ export class StartsWithTextComponent extends StartsWithAbstractComponent {
super.setStartsWithParam(resetPage);
}
/**
* Checks whether the provided option is equal to the current startsWith
* @param option
*/
isSelectedOption(option: string): boolean {
if (this.startsWith === '0' && option === '0-9') {
return true;
}
return option === this.startsWith;
}
}

View File

@@ -1025,6 +1025,8 @@
"browse.metadata.title": "Title",
"browse.metadata.srsc": "Subject Category",
"browse.metadata.author.breadcrumbs": "Browse by Author",
"browse.metadata.dateissued.breadcrumbs": "Browse by Date",
@@ -1091,13 +1093,11 @@
"browse.startsWith.input": "Filter",
"browse.taxonomy_srsc.title": "Browsing by Subject Category",
"browse.taxonomy.button": "Browse",
"browse.title": "Browsing {{ collection }} by {{ field }}{{ startsWith }} {{ value }}",
"browse.title": "Browsing by {{ field }}{{ startsWith }} {{ value }}",
"browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}",
"browse.title.page": "Browsing by {{ field }} {{ value }}",
"search.browse.item-back": "Back to Results",
@@ -2711,6 +2711,8 @@
"item.page.claim.tooltip": "Claim this item as profile",
"item.page.image.alt.ROR": "ROR logo",
"item.preview.dc.identifier.uri": "Identifier:",
"item.preview.dc.contributor.author": "Authors:",
@@ -2763,6 +2765,20 @@
"item.preview.oaire.fundingStream": "Funding Stream:",
"item.preview.oairecerif.identifier.url": "URL",
"item.preview.organization.address.addressCountry": "Country",
"item.preview.organization.foundingDate": "Founding Date",
"item.preview.organization.identifier.crossrefid": "CrossRef ID",
"item.preview.organization.identifier.isni": "ISNI",
"item.preview.organization.identifier.ror": "ROR ID",
"item.preview.organization.legalName": "Legal Name",
"item.select.confirm": "Confirm selected",
"item.select.empty": "No items to show",
@@ -3519,6 +3535,8 @@
"orgunit.page.titleprefix": "Organizational Unit: ",
"orgunit.page.ror": "ROR Identifier",
"pagination.options.description": "Pagination options",
"pagination.results-per-page": "Results Per Page",
@@ -4487,6 +4505,8 @@
"submission.import-external.source.lcname": "Library of Congress Names",
"submission.import-external.source.ror": "Research Organization Registry (ROR)",
"submission.import-external.preview.title": "Item Preview",
"submission.import-external.preview.title.Publication": "Publication Preview",
@@ -4579,6 +4599,8 @@
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.arxiv": "Importing from arXiv",
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.ror": "Import from ROR",
"submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Import",
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal",
@@ -4601,6 +4623,12 @@
"submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:",
"submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.title": "Import Remote Organization",
"submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.added.local-entity": "Successfully added local organization to the selection",
"submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.added.new-entity": "Successfully imported and added external organization to the selection",
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
@@ -4657,6 +4685,8 @@
"submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.ror": "ROR ({{ count }})",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.orcidWorks": "ORCID ({{ count }})",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.crossref": "CrossRef ({{ count }})",
@@ -4681,6 +4711,8 @@
"submission.sections.describe.relationship-lookup.search-tab.tab-title.isPublicationOfAuthor": "Publication of the Author",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.isOrgUnitOfProject": "OrgUnit of the Project",
"submission.sections.describe.relationship-lookup.selection-tab.title.openAIREFunding": "Funding OpenAIRE API",
"submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Project",
@@ -4729,6 +4761,8 @@
"submission.sections.describe.relationship-lookup.title.isPublicationOfAuthor": "Publication",
"submission.sections.describe.relationship-lookup.title.isOrgUnitOfProject": "OrgUnit",
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
@@ -4791,6 +4825,8 @@
"submission.sections.describe.relationship-lookup.selection-tab.title.wos": "Search Results",
"submission.sections.describe.relationship-lookup.selection-tab.title.ror": "Search Results",
"submission.sections.describe.relationship-lookup.selection-tab.title": "Search Results",
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don't you can still use it for this submission.",
@@ -5263,6 +5299,8 @@
"supervision.search.results.head": "Workflow and Workspace tasks",
"orgunit.search.results.head": "Organizational Unit Search Results",
"workflow-item.edit.breadcrumbs": "Edit workflowitem",
"workflow-item.edit.title": "Edit workflowitem",

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="100%" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" version="1.1"
viewBox="0 0 156 111" width="100%"
xml:space="preserve" xmlns="http://www.w3.org/2000/svg"
>
<g transform="matrix(1,0,0,1,-4.945,-4.16)">
<path d="M68.65,4.16L56.52,22.74L44.38,4.16L68.65,4.16Z" style="fill:rgb(83,186,161);fill-rule:nonzero;"/>
<path d="M119.41,4.16L107.28,22.74L95.14,4.16L119.41,4.16Z" style="fill:rgb(83,186,161);fill-rule:nonzero;"/>
<path d="M44.38,115.47L56.52,96.88L68.65,115.47L44.38,115.47Z" style="fill:rgb(83,186,161);fill-rule:nonzero;"/>
<path d="M95.14,115.47L107.28,96.88L119.41,115.47L95.14,115.47Z" style="fill:rgb(83,186,161);fill-rule:nonzero;"/>
<path
d="M145.53,63.71C149.83,62.91 153.1,61 155.33,57.99C157.57,54.98 158.68,51.32 158.68,47.03C158.68,43.47 158.06,40.51 156.83,38.13C155.6,35.75 153.93,33.86 151.84,32.45C149.75,31.05 147.31,30.04 144.53,29.44C141.75,28.84 138.81,28.54 135.72,28.54L112.16,28.54L112.16,47.37C111.97,46.82 111.77,46.28 111.55,45.74C109.92,41.79 107.64,38.42 104.71,35.64C101.78,32.86 98.32,30.72 94.3,29.23C90.29,27.74 85.9,26.99 81.14,26.99C76.38,26.99 72,27.74 67.98,29.23C63.97,30.72 60.5,32.86 57.57,35.64C54.95,38.13 52.85,41.1 51.27,44.54C51.04,42.07 50.46,39.93 49.53,38.13C48.3,35.75 46.63,33.86 44.54,32.45C42.45,31.05 40.01,30.04 37.23,29.44C34.45,28.84 31.51,28.54 28.42,28.54L4.87,28.54L4.87,89.42L18.28,89.42L18.28,65.08L24.9,65.08L37.63,89.42L53.71,89.42L38.24,63.71C42.54,62.91 45.81,61 48.04,57.99C48.14,57.85 48.23,57.7 48.33,57.56C48.31,58.03 48.3,58.5 48.3,58.98C48.3,63.85 49.12,68.27 50.75,72.22C52.38,76.17 54.66,79.54 57.59,82.32C60.51,85.1 63.98,87.24 68,88.73C72.01,90.22 76.4,90.97 81.16,90.97C85.92,90.97 90.3,90.22 94.32,88.73C98.33,87.24 101.8,85.1 104.73,82.32C107.65,79.54 109.93,76.17 111.57,72.22C111.79,71.69 111.99,71.14 112.18,70.59L112.18,89.42L125.59,89.42L125.59,65.08L132.21,65.08L144.94,89.42L161.02,89.42L145.53,63.71ZM36.39,50.81C35.67,51.73 34.77,52.4 33.68,52.83C32.59,53.26 31.37,53.52 30.03,53.6C28.68,53.69 27.41,53.73 26.2,53.73L18.29,53.73L18.29,39.89L27.06,39.89C28.26,39.89 29.5,39.98 30.76,40.15C32.02,40.32 33.14,40.65 34.11,41.14C35.08,41.63 35.89,42.33 36.52,43.25C37.15,44.17 37.47,45.4 37.47,46.95C37.47,48.6 37.11,49.89 36.39,50.81ZM98.74,66.85C97.85,69.23 96.58,71.29 94.91,73.04C93.25,74.79 91.26,76.15 88.93,77.13C86.61,78.11 84.01,78.59 81.15,78.59C78.28,78.59 75.69,78.1 73.37,77.13C71.05,76.16 69.06,74.79 67.39,73.04C65.73,71.29 64.45,69.23 63.56,66.85C62.67,64.47 62.23,61.85 62.23,58.98C62.23,56.17 62.67,53.56 63.56,51.15C64.45,48.74 65.72,46.67 67.39,44.92C69.05,43.17 71.04,41.81 73.37,40.83C75.69,39.86 78.28,39.37 81.15,39.37C84.02,39.37 86.61,39.86 88.93,40.83C91.25,41.8 93.24,43.17 94.91,44.92C96.57,46.67 97.85,48.75 98.74,51.15C99.63,53.56 100.07,56.17 100.07,58.98C100.07,61.85 99.63,64.47 98.74,66.85ZM143.68,50.81C142.96,51.73 142.06,52.4 140.97,52.83C139.88,53.26 138.66,53.52 137.32,53.6C135.97,53.69 134.7,53.73 133.49,53.73L125.58,53.73L125.58,39.89L134.35,39.89C135.55,39.89 136.79,39.98 138.05,40.15C139.31,40.32 140.43,40.65 141.4,41.14C142.37,41.63 143.18,42.33 143.81,43.25C144.44,44.17 144.76,45.4 144.76,46.95C144.76,48.6 144.4,49.89 143.68,50.81Z"
style="fill:rgb(32,40,38);fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -105,4 +105,7 @@
--ds-comcol-logo-max-width: 500px;
--ds-comcol-logo-max-height: 500px;
--ds-item-page-img-field-default-inline-height: 24px;
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-date-page',
// styleUrls: ['./browse-by-date-page.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'],
// templateUrl: './browse-by-date-page.component.html'
templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html'
})
@rendersBrowseBy(BrowseByDataType.Date, Context.Any, 'custom')
export class BrowseByDatePageComponent extends BaseComponent {
}

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { BrowseByDateComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date/browse-by-date.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-date',
// styleUrls: ['./browse-by-date.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-metadata/browse-by-metadata.component.scss'],
// templateUrl: './browse-by-date.component.html',
templateUrl: '../../../../../app/browse-by/browse-by-metadata/browse-by-metadata.component.html',
})
@rendersBrowseBy(BrowseByDataType.Date, Context.Any, 'custom')
export class BrowseByDateComponent extends BaseComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-metadata-page',
// styleUrls: ['./browse-by-metadata-page.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'],
// templateUrl: './browse-by-metadata-page.component.html'
templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html'
})
@rendersBrowseBy(BrowseByDataType.Metadata, Context.Any, 'custom')
export class BrowseByMetadataPageComponent extends BaseComponent {
}

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { BrowseByMetadataComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata/browse-by-metadata.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-metadata',
// styleUrls: ['./browse-by-metadata.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-metadata/browse-by-metadata.component.scss'],
// templateUrl: './browse-by-metadata.component.html',
templateUrl: '../../../../../app/browse-by/browse-by-metadata/browse-by-metadata.component.html',
})
@rendersBrowseBy(BrowseByDataType.Metadata, Context.Any, 'custom')
export class BrowseByMetadataComponent extends BaseComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-taxonomy-page',
// templateUrl: './browse-by-taxonomy-page.component.html',
templateUrl: '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html',
// styleUrls: ['./browse-by-taxonomy-page.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'],
})
@rendersBrowseBy(BrowseByDataType.Hierarchy, Context.Any, 'custom')
export class BrowseByTaxonomyPageComponent extends BaseComponent {
}

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { BrowseByTaxonomyComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-taxonomy',
// templateUrl: './browse-by-taxonomy.component.html',
templateUrl: '../../../../../app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html',
// styleUrls: ['./browse-by-taxonomy.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.scss'],
})
@rendersBrowseBy(BrowseByDataType.Hierarchy, Context.Any, 'custom')
export class BrowseByTaxonomyComponent extends BaseComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component';
import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type';
import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator';
import { Context } from '../../../../../app/core/shared/context.model';
@Component({
selector: 'ds-browse-by-title-page',
// styleUrls: ['./browse-by-title-page.component.scss'],
styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'],
// templateUrl: './browse-by-title-page.component.html'
templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html'
})
@rendersBrowseBy(BrowseByDataType.Title, Context.Any, 'custom')
export class BrowseByTitlePageComponent extends BaseComponent {
}

Some files were not shown because too many files have changed in this diff Show More